├── lib ├── log.dart ├── src │ ├── cache │ │ ├── download_future.dart │ │ ├── download_error.dart │ │ ├── cache_delegate.dart │ │ ├── default_cache_delegate.dart │ │ ├── cache_manager.dart │ │ └── download_manager.dart │ ├── util │ │ └── log.dart │ ├── default_error_widget.dart │ ├── request_helper.dart │ └── ok_image.dart └── ok_image.dart ├── example ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ └── main │ │ │ │ ├── res │ │ │ │ ├── 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 │ │ │ │ └── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.java │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ └── build.gradle ├── ios │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner │ │ ├── AppDelegate.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── main.m │ │ ├── AppDelegate.m │ │ ├── Info.plist │ │ └── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── WorkspaceSettings.xcsettings │ └── Podfile ├── README.md ├── .metadata ├── .gitignore ├── pubspec.yaml └── lib │ └── main.dart ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── how-to-use-it.md │ └── bug_report.md ├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── LICENSE ├── CHANGELOG.md ├── pubspec.yaml ├── README.md └── pubspec.lock /lib/log.dart: -------------------------------------------------------------------------------- 1 | export 'src/util/log.dart'; 2 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | # Feature 2 | 3 | 4 | -------------------------------------------------------------------------------- /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/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | For help getting started with Flutter, view our online 8 | [documentation](https://flutter.io/). 9 | -------------------------------------------------------------------------------- /lib/src/cache/download_future.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class DownloadFuture { 4 | String url; 5 | Completer completer; 6 | 7 | DownloadFuture(this.url, this.completer); 8 | } 9 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFlutter/flutter_ok_image/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFlutter/flutter_ok_image/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFlutter/flutter_ok_image/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/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/OpenFlutter/flutter_ok_image/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFlutter/flutter_ok_image/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFlutter/flutter_ok_image/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/src/cache/download_error.dart: -------------------------------------------------------------------------------- 1 | class DownloadError extends Error { 2 | String msg; 3 | Error originError; 4 | StackTrace stackTrace; 5 | String url; 6 | 7 | DownloadError(this.url, this.msg, this.originError, [this.stackTrace]); 8 | } 9 | -------------------------------------------------------------------------------- /lib/ok_image.dart: -------------------------------------------------------------------------------- 1 | library ok_image; 2 | 3 | export 'src/ok_image.dart'; 4 | export 'src/cache/cache_delegate.dart'; 5 | export 'src/cache/cache_manager.dart'; 6 | export 'src/cache/download_error.dart'; 7 | export 'src/request_helper.dart' show ImageCodeError; 8 | -------------------------------------------------------------------------------- /lib/src/cache/cache_delegate.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:typed_data'; 3 | 4 | typedef Future DefaultFuture(); 5 | 6 | typedef Future CacheDelegate( 7 | String url, DefaultFuture createDefaultFuture, 8 | {bool followRedirects}); 9 | -------------------------------------------------------------------------------- /lib/src/util/log.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class Log { 4 | Log._(); 5 | 6 | static bool showLog = false; 7 | 8 | static void log(Object msg) { 9 | if (showLog == true) debugPrint(msg ?? "null", wrapWidth: 900); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /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-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/src/default_error_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DefaultErrorWidget extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Icon( 7 | Icons.error, 8 | size: 55.0, 9 | color: Theme.of(context).primaryColor, 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | ios/.generated/ 9 | ios/Flutter/Generated.xcconfig 10 | ios/Runner/GeneratedPluginRegistrant.* 11 | android/local.properties 12 | simple_net_image.iml 13 | android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java 14 | test/simple_net_image_test.dart 15 | -------------------------------------------------------------------------------- /.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: d44aa57c120c30d523c937a0455a6af30e743da9 8 | channel: dev 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /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: d44aa57c120c30d523c937a0455a6af30e743da9 8 | channel: dev 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Flutter", 9 | "request": "launch", 10 | "type": "dart", 11 | "program": "example/lib/main.dart" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/how-to-use-it.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## flutter version 4 | 5 | 13 | 14 | 15 | 16 | ```bash 17 | 18 | ``` 19 | 20 | ## libary version 21 | 22 | 23 | 24 | ## question 25 | 26 | 27 | -------------------------------------------------------------------------------- /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/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.example; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | # Bug 2 | 3 | ## flutter version 4 | 5 | 13 | 14 | 15 | 16 | ```bash 17 | 18 | ``` 19 | 20 | ## library version 21 | 22 | 25 | 26 | ## screenshot 27 | 28 | 31 | 32 | ## error log 33 | 34 | ```bash 35 | ``` 36 | 37 | ## code 38 | 39 | 40 | 41 | ```dart 42 | 43 | ``` 44 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/src/cache/default_cache_delegate.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:ok_image/src/cache/cache_delegate.dart'; 6 | import 'package:ok_image/src/cache/cache_manager.dart'; 7 | 8 | Future defaultCache(String url, DefaultFuture createDefaultFuture, 9 | {bool followRedirects}) async { 10 | var icm = ImageCacheManager(); 11 | await icm.init(); 12 | return icm.getImageBytes(url); 13 | } 14 | 15 | Uint8List getImageBytes(String url) { 16 | var icm = ImageCacheManager(); 17 | if (!icm.isInit) return null; 18 | 19 | return icm.getImageBytesSync(url); 20 | } 21 | 22 | bool isDownloaded(String url) => 23 | ImageCacheManager().isInit && ImageCacheManager().isDownload(url); 24 | 25 | File getCacheImageFile(String url) { 26 | if (!isDownloaded(url)) { 27 | return null; 28 | } 29 | return ImageCacheManager().getLocalFile(url); 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 caijinglong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## [0.4.0] 4 | 5 | **Breaking change**: 6 | Params `errorWidget` change type to `ErrorWidgetBuilder`. 7 | 8 | fix: 9 | 10 | - Download error bug. 11 | 12 | ## [0.3.1] upgrade dependencies version 13 | 14 | - path_provider 15 | - rx_dart 16 | - http 17 | 18 | ## [0.3.0] upgrade path_provider 19 | 20 | **breaking change** 21 | because path_provider migrate from android support to androidX, so your other plugin also need be migrated. 22 | upgrade rxdart 23 | 24 | ## [0.2.3] add remove cache 25 | 26 | ImageCache.removeCache(String url); 27 | 28 | ## [0.2.2] request 29 | 30 | add a parameter to delete the cache whose last access time exceeds duration 31 | 32 | add onImageLoadState callback 33 | 34 | ## [0.2.1] un-ext image load error 35 | 36 | Fix: Unextended images cannot be loaded 37 | 38 | ## [0.2.0] 39 | 40 | global error and loading widget 41 | 42 | add a clear cache method 43 | 44 | upgrade rxdart version 45 | 46 | fix bug for downloading 47 | 48 | ## [0.1.1] fix load bug 49 | 50 | fix error bug 51 | 52 | ## [0.1.0] update request and cache 53 | 54 | Now the same URL will share a download, waiting for the download to complete and return together. 55 | 56 | ### +1 57 | 58 | update readme 59 | 60 | ## [0.0.3] add cache delegate 61 | 62 | add a default cache delegate to cache image 63 | 64 | ## [0.0.2] ADD LICENSE 65 | 66 | use MIT 67 | 68 | ## [0.0.1] first version 69 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | 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 | 45 | 46 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ok_image 2 | description: Easy to use this library to build a network image widget, you can build different layouts according to the error/load, also set up the image cache delegate. 3 | version: 0.4.0 4 | author: caijinglong 5 | homepage: https://github.com/Openflutter/flutter_ok_image 6 | 7 | environment: 8 | sdk: ">=2.0.0-dev.68.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | http: ^0.12.0+2 15 | rxdart: ^0.22.1 16 | path_provider: ^1.2.0 17 | crypto: ^2.0.6 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | 23 | # For information on the generic Dart part of this file, see the 24 | # following page: https://www.dartlang.org/tools/pub/pubspec 25 | 26 | # The following section is specific to Flutter. 27 | flutter: 28 | # To add assets to your package, add an assets section, like this: 29 | # assets: 30 | # - images/a_dot_burr.jpeg 31 | # - images/a_dot_ham.jpeg 32 | # 33 | # For details regarding assets in packages, see 34 | # https://flutter.io/assets-and-images/#from-packages 35 | # 36 | # An image asset can refer to one or more resolution-specific "variants", see 37 | # https://flutter.io/assets-and-images/#resolution-aware. 38 | # To add custom fonts to your package, add a fonts section here, 39 | # in this "flutter" section. Each entry in this list should have a 40 | # "family" key with the font family name, and a "fonts" key with a 41 | # list giving the asset and other descriptors for the font. For 42 | # example: 43 | # fonts: 44 | # - family: Schyler 45 | # fonts: 46 | # - asset: fonts/Schyler-Regular.ttf 47 | # - asset: fonts/Schyler-Italic.ttf 48 | # style: italic 49 | # - family: Trajan Pro 50 | # fonts: 51 | # - asset: fonts/TrajanPro.ttf 52 | # - asset: fonts/TrajanPro_Bold.ttf 53 | # weight: 700 54 | # 55 | # For details regarding fonts in packages, see 56 | # https://flutter.io/custom-fonts/#from-packages 57 | -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 27 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.example.example" 37 | minSdkVersion 16 38 | targetSdkVersion 27 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /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 parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 37 | # referring to absolute paths on developers' machines. 38 | system('rm -rf .symlinks') 39 | system('mkdir -p .symlinks/plugins') 40 | 41 | # Flutter Pods 42 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 43 | if generated_xcode_build_settings.empty? 44 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 45 | end 46 | generated_xcode_build_settings.map { |p| 47 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 48 | symlink = File.join('.symlinks', 'flutter') 49 | File.symlink(File.dirname(p[:path]), symlink) 50 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 51 | end 52 | } 53 | 54 | # Plugin Pods 55 | plugin_pods = parse_KV_file('../.flutter-plugins') 56 | plugin_pods.map { |p| 57 | symlink = File.join('.symlinks', 'plugins', p[:name]) 58 | File.symlink(p[:path], symlink) 59 | pod p[:name], :path => File.join(symlink, 'ios') 60 | } 61 | end 62 | 63 | post_install do |installer| 64 | installer.pods_project.targets.each do |target| 65 | target.build_configurations.each do |config| 66 | config.build_settings['ENABLE_BITCODE'] = 'NO' 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/src/cache/cache_manager.dart: -------------------------------------------------------------------------------- 1 | library net_image; 2 | 3 | import 'dart:async'; 4 | import 'dart:io'; 5 | import 'dart:typed_data'; 6 | 7 | import 'package:path_provider/path_provider.dart'; 8 | 9 | import 'download_manager.dart' as dm; 10 | 11 | class ImageCacheManager { 12 | static ImageCacheManager _instance; 13 | 14 | ImageCacheManager._(); 15 | 16 | factory ImageCacheManager() { 17 | if (_instance == null) { 18 | _instance = ImageCacheManager._(); 19 | } 20 | return _instance; 21 | } 22 | 23 | Future init() async { 24 | imgDir = await _imgDir; 25 | } 26 | 27 | bool get isInit => imgDir != null; 28 | 29 | Future getImageBytes(String url) async { 30 | var targetDir = imgDir; 31 | var file = await dm.requestImage(url, targetDir); 32 | if (file == null || file.lengthSync() == 0) { 33 | return null; 34 | } 35 | return Uint8List.fromList(file.readAsBytesSync()); 36 | } 37 | 38 | bool isDownload(String url) { 39 | return dm.exists(url, imgDir); 40 | } 41 | 42 | Directory imgDir; 43 | 44 | Future get _imgDir async { 45 | var dir = await getTemporaryDirectory(); 46 | dir.createSync(); 47 | var imgDir = Directory(dir.absolute.path + "/.tmp_img")..createSync(); 48 | return imgDir; 49 | } 50 | 51 | File getLocalFile(String url) { 52 | return dm.getLocalFile(url, imgDir); 53 | } 54 | 55 | Uint8List getImageBytesSync(String url) { 56 | if (!isDownload(url)) { 57 | return null; 58 | } 59 | 60 | return Uint8List.fromList(getLocalFile(url).readAsBytesSync()); 61 | } 62 | 63 | Future clearAllCache({Duration duration}) async { 64 | await init(); 65 | if (duration != null) { 66 | var now = DateTime.now(); 67 | for (var file in imgDir.listSync()) { 68 | var stat = file.statSync(); 69 | var compareDateTime = now.subtract(duration); 70 | if (compareDateTime.isAfter(stat.accessed)) { 71 | file.deleteSync(recursive: true); 72 | } 73 | } 74 | } else { 75 | for (var file in imgDir.listSync()) { 76 | file.deleteSync(recursive: true); 77 | } 78 | } 79 | } 80 | 81 | /// remove file cache with url. 82 | /// 83 | /// If you load the specified URL at the same time, unexpected errors may occur 84 | Future removeCache(String url) async { 85 | await init(); 86 | var file = getLocalFile(url); 87 | if (file.existsSync()) { 88 | file.deleteSync(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /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/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | environment: 13 | sdk: ">=2.0.0-dev.68.0 <3.0.0" 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | 19 | # The following adds the Cupertino Icons font to your application. 20 | # Use with the CupertinoIcons class for iOS style icons. 21 | cupertino_icons: ^0.1.2 22 | ok_image: 23 | path: ../ 24 | 25 | dev_dependencies: 26 | flutter_test: 27 | sdk: flutter 28 | 29 | 30 | # For information on the generic Dart part of this file, see the 31 | # following page: https://www.dartlang.org/tools/pub/pubspec 32 | 33 | # The following section is specific to Flutter. 34 | flutter: 35 | 36 | # The following line ensures that the Material Icons font is 37 | # included with your application, so that you can use the icons in 38 | # the material Icons class. 39 | uses-material-design: true 40 | 41 | # To add assets to your application, add an assets section, like this: 42 | # assets: 43 | # - images/a_dot_burr.jpeg 44 | # - images/a_dot_ham.jpeg 45 | 46 | # An image asset can refer to one or more resolution-specific "variants", see 47 | # https://flutter.io/assets-and-images/#resolution-aware. 48 | 49 | # For details regarding adding assets from package dependencies, see 50 | # https://flutter.io/assets-and-images/#from-packages 51 | 52 | # To add custom fonts to your application, add a fonts section here, 53 | # in this "flutter" section. Each entry in this list should have a 54 | # "family" key with the font family name, and a "fonts" key with a 55 | # list giving the asset and other descriptors for the font. For 56 | # example: 57 | # fonts: 58 | # - family: Schyler 59 | # fonts: 60 | # - asset: fonts/Schyler-Regular.ttf 61 | # - asset: fonts/Schyler-Italic.ttf 62 | # style: italic 63 | # - family: Trajan Pro 64 | # fonts: 65 | # - asset: fonts/TrajanPro.ttf 66 | # - asset: fonts/TrajanPro_Bold.ttf 67 | # weight: 700 68 | # 69 | # For details regarding fonts from package dependencies, 70 | # see https://flutter.io/custom-fonts/#from-packages 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OKImage 2 | 3 | Easy to use this library to build a network image widget, you can build different layouts according to the error/load, also set up the image cache delegate. 4 | 5 | [![ok_image](https://img.shields.io/badge/OpenFlutter-OKImage-blue.svg)](https://github.com/OpenFlutter/flutter_ok_image) 6 | [![pub package](https://img.shields.io/pub/v/ok_image.svg)](https://pub.dartlang.org/packages/ok_image) 7 | ![GitHub](https://img.shields.io/github/license/OpenFlutter/flutter_ok_image.svg) 8 | [![GitHub stars](https://img.shields.io/github/stars/OpenFlutter/flutter_ok_image.svg?style=social&label=Stars)](https://github.com/OpenFlutter/flutter_ok_image) 9 | 10 | ## use 11 | 12 | 1. add to your pubspec.yaml 13 | 14 | ```yaml 15 | ok_image: ^0.4.0 16 | ``` 17 | 18 | 2. import 19 | 20 | ```dart 21 | import "package:ok_image/ok_image.dart"; 22 | ``` 23 | 24 | 3. use 25 | 26 | ```dart 27 | import "package:ok_image/ok_image.dart"; 28 | createWidget(){ 29 | return OKImage( 30 | url: "https://ws1.sinaimg.cn/large/844036b9ly1fxfo76hzd4j20zk0nc48i.jpg", 31 | width: 200, 32 | height: 200, 33 | timeout: Duration(seconds: 20), 34 | fit: fit, 35 | ); 36 | } 37 | ``` 38 | 39 | 4. params 40 | 41 | ```markdown 42 | url: image net url 43 | width: width 44 | height: height 45 | fit: show BoxFit 46 | followRedirects: whether image redirection is allowed. 47 | loadingWidget: display on loading 48 | errorWidget: display when image load error / timeout. 49 | retry: retry to load image count. 50 | timeout: timeout duration. 51 | onErrorTap: when loadErrorWidget show ,onTap it. 52 | cacheDelegate: you can use the param to delegate loadImage 53 | ``` 54 | 55 | Experimental: Signatures, return values, parameters and other information may be modified in the future. 56 | 57 | ```md 58 | onLoadStateChanged: will be call on the load state changed. 59 | ``` 60 | 61 | 5. global config 62 | 63 | edit `OKImage.buildErrorWidget` to config global OKImage errorWidget. 64 | 65 | edit `OKImage.buildLoadingWidget` to config global OKImage loading. 66 | 67 | ## about other library 68 | 69 | under BSD 3: 70 | 71 | 1. This library uses [http 0.12.0+2](https://pub.dartlang.org/packages/http) as a framework for network access. 72 | 2. using [path_provider 1.2.0](https://pub.dartlang.org/packages/path_provider) to get default catch path. 73 | 3. using [crypto 2.0.6](https://pub.dartlang.org/packages/crypto) to make and check md5. 74 | 75 | Apache 2.0: 76 | 77 | 1. Using [rxdart 0.22.1](https://pub.dartlang.org/packages/rxdart) processing logic 78 | 79 | thanks to open source. 80 | 81 | If you are using older versions of these open source libraries, which cause incompatibility, please update your. 82 | If it is incompatible with me, please contact me and I will update the version number when appropriate. 83 | -------------------------------------------------------------------------------- /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.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /lib/src/request_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:http/http.dart' as http; 5 | import 'package:ok_image/src/cache/cache_delegate.dart'; 6 | import 'package:ok_image/src/util/log.dart'; 7 | import 'package:rxdart/rxdart.dart'; 8 | 9 | class RequestHelper { 10 | static Future requestImage( 11 | String url, 12 | int retry, 13 | Duration duration, { 14 | bool followRedirects = false, 15 | CacheDelegate cacheDelegate, 16 | }) async { 17 | Log.log("准备获取图片: $url"); 18 | Completer completer = Completer(); 19 | 20 | Observable.retry( 21 | () { 22 | Log.log("$url 试一下"); 23 | return _createStream(url, followRedirects, cacheDelegate); 24 | }, 25 | retry, 26 | ).timeout( 27 | duration, 28 | onTimeout: (sink) { 29 | Log.log("超时了"); 30 | completer.completeError(TimeoutError()); 31 | sink.close(); 32 | }, 33 | ).listen( 34 | (data) { 35 | Log.log("获取成功 图片大小 ${data.length}"); 36 | completer.complete(data); 37 | }, 38 | onError: (err) { 39 | if (err is ImageCodeError) { 40 | Log.log("获取图片出错 $err"); 41 | completer.completeError(err); 42 | } else if (err is RetryError) { 43 | Log.log("获取图片出错 重试$retry次后 \n ${err.errors.last}"); 44 | completer.completeError(err.errors.last); 45 | } else { 46 | Log.log("获取图片出错 $err"); 47 | completer.completeError(err); 48 | } 49 | }, 50 | onDone: () {}, 51 | ); 52 | 53 | return completer.future; 54 | } 55 | 56 | static Stream _createStream( 57 | String url, bool followRedirects, CacheDelegate cacheDelegate) { 58 | Future future; 59 | 60 | Future createDefault() { 61 | return _requestImage( 62 | url, 63 | followRedirects: followRedirects, 64 | ); 65 | } 66 | 67 | if (cacheDelegate != null) { 68 | future = cacheDelegate( 69 | url, 70 | createDefault, 71 | followRedirects: followRedirects, 72 | ); 73 | } else { 74 | future = createDefault(); 75 | } 76 | 77 | return Stream.fromFuture(future); 78 | } 79 | 80 | static Future _requestImage( 81 | String url, { 82 | ProgressHandler handler, 83 | bool followRedirects, 84 | }) async { 85 | Log.log("真实下载请求开始 $url"); 86 | var completer = Completer(); 87 | var baseRequest = http.Request("GET", Uri.parse(url)); 88 | baseRequest.followRedirects = followRedirects; 89 | var streamResponse = await baseRequest.send(); 90 | 91 | await Future.delayed(Duration(seconds: 2)); 92 | 93 | if (streamResponse.statusCode != 200) { 94 | Log.log("真实下载出错 $url, 响应码 ${streamResponse.statusCode}"); 95 | completer.completeError(ImageCodeError(streamResponse.statusCode)); 96 | return completer.future; 97 | // throw ImageCodeError(streamResponse.statusCode); 98 | } 99 | var maxLength = streamResponse.contentLength; 100 | List content = []; 101 | streamResponse.stream.asBroadcastStream().listen((data) { 102 | content.addAll(data); 103 | var progress = content.length / maxLength; 104 | handler?.call(progress); 105 | if (progress == 1.0) { 106 | completer.complete(Uint8List.fromList(content)); 107 | } 108 | }); 109 | return completer.future; 110 | } 111 | } 112 | 113 | class TimeoutError extends Error { 114 | TimeoutError(); 115 | } 116 | 117 | class ImageCodeError extends Error { 118 | int code; 119 | 120 | ImageCodeError(this.code); 121 | 122 | @override 123 | String toString() { 124 | return "ImageCodeError{ code : $code}"; 125 | } 126 | } 127 | 128 | typedef ProgressHandler(double progress); 129 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.1.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "1.0.4" 18 | charcode: 19 | dependency: transitive 20 | description: 21 | name: charcode 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.2" 25 | collection: 26 | dependency: transitive 27 | description: 28 | name: collection 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.14.11" 32 | convert: 33 | dependency: transitive 34 | description: 35 | name: convert 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "2.1.1" 39 | crypto: 40 | dependency: "direct main" 41 | description: 42 | name: crypto 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "2.0.6" 46 | flutter: 47 | dependency: "direct main" 48 | description: flutter 49 | source: sdk 50 | version: "0.0.0" 51 | flutter_test: 52 | dependency: "direct dev" 53 | description: flutter 54 | source: sdk 55 | version: "0.0.0" 56 | http: 57 | dependency: "direct main" 58 | description: 59 | name: http 60 | url: "https://pub.flutter-io.cn" 61 | source: hosted 62 | version: "0.12.0+2" 63 | http_parser: 64 | dependency: transitive 65 | description: 66 | name: http_parser 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "3.1.3" 70 | matcher: 71 | dependency: transitive 72 | description: 73 | name: matcher 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "0.12.5" 77 | meta: 78 | dependency: transitive 79 | description: 80 | name: meta 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "1.1.6" 84 | path: 85 | dependency: transitive 86 | description: 87 | name: path 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "1.6.2" 91 | path_provider: 92 | dependency: "direct main" 93 | description: 94 | name: path_provider 95 | url: "https://pub.flutter-io.cn" 96 | source: hosted 97 | version: "1.2.0" 98 | pedantic: 99 | dependency: transitive 100 | description: 101 | name: pedantic 102 | url: "https://pub.flutter-io.cn" 103 | source: hosted 104 | version: "1.5.0" 105 | quiver: 106 | dependency: transitive 107 | description: 108 | name: quiver 109 | url: "https://pub.flutter-io.cn" 110 | source: hosted 111 | version: "2.0.2" 112 | rxdart: 113 | dependency: "direct main" 114 | description: 115 | name: rxdart 116 | url: "https://pub.flutter-io.cn" 117 | source: hosted 118 | version: "0.22.1+1" 119 | sky_engine: 120 | dependency: transitive 121 | description: flutter 122 | source: sdk 123 | version: "0.0.99" 124 | source_span: 125 | dependency: transitive 126 | description: 127 | name: source_span 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.5.5" 131 | stack_trace: 132 | dependency: transitive 133 | description: 134 | name: stack_trace 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.9.3" 138 | stream_channel: 139 | dependency: transitive 140 | description: 141 | name: stream_channel 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "2.0.0" 145 | string_scanner: 146 | dependency: transitive 147 | description: 148 | name: string_scanner 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "1.0.4" 152 | term_glyph: 153 | dependency: transitive 154 | description: 155 | name: term_glyph 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "1.1.0" 159 | test_api: 160 | dependency: transitive 161 | description: 162 | name: test_api 163 | url: "https://pub.flutter-io.cn" 164 | source: hosted 165 | version: "0.2.4" 166 | typed_data: 167 | dependency: transitive 168 | description: 169 | name: typed_data 170 | url: "https://pub.flutter-io.cn" 171 | source: hosted 172 | version: "1.1.6" 173 | vector_math: 174 | dependency: transitive 175 | description: 176 | name: vector_math 177 | url: "https://pub.flutter-io.cn" 178 | source: hosted 179 | version: "2.0.8" 180 | sdks: 181 | dart: ">=2.2.0 <3.0.0" 182 | flutter: ">=0.1.4 <2.0.0" 183 | -------------------------------------------------------------------------------- /lib/src/ok_image.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:ok_image/src/cache/cache_delegate.dart'; 6 | import 'package:ok_image/src/cache/default_cache_delegate.dart'; 7 | import 'package:ok_image/src/default_error_widget.dart'; 8 | import 'package:ok_image/src/request_helper.dart'; 9 | import 'package:ok_image/src/cache/download_error.dart'; 10 | 11 | /// error maybe [ImageCodeError] or [DownloadError] 12 | typedef ErrorWidgetBuilder(Object error); 13 | 14 | class OKImage extends StatefulWidget { 15 | /// update error widget for global 16 | static ValueGetter buildErrorWidget = () => DefaultErrorWidget(); 17 | 18 | /// update loading widget for global 19 | static ValueGetter buildLoadingWidget = () => ProgressWidget(); 20 | 21 | /// net image url 22 | final String url; 23 | 24 | /// like [Image.width] 25 | final double width; 26 | 27 | /// like [Image.height] 28 | final double height; 29 | 30 | /// like [Image.fit] 31 | final BoxFit boxFit; 32 | 33 | /// loading widget 34 | final Widget loadingWidget; 35 | 36 | /// on load error widget 37 | final ErrorWidgetBuilder errorWidget; 38 | 39 | /// Number of retries 40 | final int retry; 41 | 42 | /// timeout for load 43 | final Duration timeout; 44 | 45 | /// After an error occurs, click on the event 46 | final Function onErrorTap; 47 | 48 | /// you can use your self cache delegate 49 | final CacheDelegate cacheDelegate; 50 | 51 | /// When followRedirects occurs, whether to continue accessing or not, and when followRedirects occurs, false is regarded as a failure of accessing. 52 | final bool followRedirects; 53 | 54 | /// Experimental , Callback when the loading state changes 55 | final ValueChanged onLoadStateChanged; 56 | 57 | const OKImage({ 58 | Key key, 59 | @required this.url, 60 | this.width, 61 | this.height, 62 | this.loadingWidget, 63 | this.errorWidget, 64 | this.retry = 5, 65 | BoxFit fit, 66 | this.timeout = const Duration(seconds: 15), 67 | this.onErrorTap, 68 | this.cacheDelegate, 69 | this.followRedirects = false, 70 | this.onLoadStateChanged, 71 | }) : boxFit = fit ?? BoxFit.cover, 72 | super(key: key); 73 | 74 | @override 75 | _OKImageState createState() => _OKImageState(); 76 | } 77 | 78 | class _OKImageState extends State { 79 | Widget get loadingWidget => 80 | widget.loadingWidget ?? OKImage.buildLoadingWidget(); 81 | 82 | Widget errorWidget(err) => Container( 83 | width: width, 84 | height: height, 85 | child: widget.errorWidget?.call(err) ?? OKImage.buildErrorWidget(), 86 | ); 87 | 88 | double get width => widget.width; 89 | 90 | double get height => widget.height; 91 | 92 | void _onLoadStateChanged(OnLoadState state) { 93 | widget.onLoadStateChanged?.call(state); 94 | } 95 | 96 | @override 97 | void initState() { 98 | super.initState(); 99 | _onLoadStateChanged(OnLoadState.loadStart); 100 | } 101 | 102 | @override 103 | Widget build(BuildContext context) { 104 | CacheDelegate delegate = widget.cacheDelegate ?? defaultCache; 105 | 106 | if (isDownloaded(widget.url) && 107 | (getCacheImageFile(widget.url)?.existsSync() == true)) { 108 | _onLoadStateChanged(OnLoadState.loadEnd); 109 | return SizedBox( 110 | width: width, 111 | height: height, 112 | child: Image.file( 113 | getCacheImageFile(widget.url), 114 | fit: widget.boxFit, 115 | ), 116 | ); 117 | } 118 | 119 | return FutureBuilder( 120 | builder: _buildImage, 121 | future: RequestHelper.requestImage( 122 | widget.url, 123 | widget.retry, 124 | widget.timeout, 125 | cacheDelegate: delegate, 126 | followRedirects: widget.followRedirects, 127 | ), 128 | ); 129 | } 130 | 131 | Widget _buildImage(BuildContext context, AsyncSnapshot snapshot) { 132 | Widget w; 133 | if (snapshot.hasError) { 134 | w = errorWidget(snapshot.error); 135 | w = wrapErrorTap(w); 136 | _onLoadStateChanged(OnLoadState.loadFail); 137 | } else if (snapshot.data != null) { 138 | w = Image.memory( 139 | snapshot.data, 140 | fit: widget.boxFit, 141 | ); 142 | _onLoadStateChanged(OnLoadState.loadEnd); 143 | } else { 144 | w = loadingWidget; 145 | _onLoadStateChanged(OnLoadState.loading); 146 | } 147 | 148 | return SizedBox( 149 | width: width, 150 | height: height, 151 | child: w, 152 | ); 153 | } 154 | 155 | Widget wrapErrorTap(Widget child) { 156 | if (widget.onErrorTap == null) { 157 | return child; 158 | } 159 | return GestureDetector( 160 | onTap: widget.onErrorTap, 161 | behavior: HitTestBehavior.translucent, 162 | child: child, 163 | ); 164 | } 165 | } 166 | 167 | class ProgressWidget extends StatelessWidget { 168 | final double width; 169 | final double height; 170 | 171 | const ProgressWidget({ 172 | Key key, 173 | this.width = 40.0, 174 | this.height = 40.0, 175 | }) : super(key: key); 176 | 177 | @override 178 | Widget build(BuildContext context) { 179 | Widget w; 180 | var platform = Theme.of(context).platform; 181 | if (platform == TargetPlatform.iOS) { 182 | w = CupertinoActivityIndicator(); 183 | } else { 184 | w = CircularProgressIndicator(); 185 | } 186 | 187 | return Center( 188 | child: SizedBox( 189 | child: w, 190 | width: width, 191 | height: height, 192 | ), 193 | ); 194 | } 195 | } 196 | 197 | /// Experimental 198 | enum OnLoadState { 199 | loadStart, 200 | loading, 201 | loadEnd, 202 | loadFail, 203 | } 204 | -------------------------------------------------------------------------------- /lib/src/cache/download_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:crypto/crypto.dart'; 5 | import 'package:http/io_client.dart' as io_client; 6 | import 'package:ok_image/src/cache/download_error.dart'; 7 | import 'package:ok_image/src/cache/download_future.dart'; 8 | import 'package:ok_image/src/util/log.dart'; 9 | 10 | //import 'package:http/http.dart' as http; 11 | 12 | StreamController _downloadStreamController = StreamController(); 13 | 14 | class DownloadManager { 15 | static DownloadManager _instance; 16 | 17 | List futures = []; 18 | 19 | DownloadManager._() { 20 | _downloadStreamController.stream.listen( 21 | onReceive, 22 | onError: _onDownloadStreamError, 23 | ); 24 | } 25 | 26 | factory DownloadManager() { 27 | _instance ??= DownloadManager._(); 28 | return _instance; 29 | } 30 | 31 | bool isDownloading(String url) { 32 | for (var future in futures) { 33 | if (future.url == url) { 34 | return true; 35 | } 36 | } 37 | return false; 38 | } 39 | 40 | void tryDownload(String url, File targetFile) async { 41 | io_client.IOClient client = io_client.IOClient(); //确定下载再创建client 42 | 43 | Log.log("尝试下载 $url"); 44 | 45 | if (isDownloading(url)) { 46 | Log.log("下载中 $url "); 47 | return; 48 | } 49 | 50 | Log.log("$url 准备下载中"); 51 | 52 | // 先创建一个临时文件,用于告知 53 | var tmpFile = File(_getTmpPath(targetFile))..createSync(); 54 | 55 | try { 56 | var response = await client.get(url); 57 | if (response.statusCode == 200 && !response.isRedirect) { 58 | var bytes = response.bodyBytes; 59 | Log.log("下载完成"); 60 | // 成功,先写tmp文件中 61 | await targetFile.writeAsBytes(bytes); 62 | Log.log("写入完成"); 63 | _downloadStreamController.add(url); 64 | } else { 65 | var downloadError = DownloadError( 66 | url, "download error", StateError("code = ${response.statusCode}")); 67 | _downloadStreamController.addError(downloadError); 68 | Log.log("$url 不存在"); 69 | } 70 | } catch (e) { 71 | if (e is Exception) { 72 | _downloadStreamController.addError(e); 73 | } else { 74 | _downloadStreamController 75 | .addError(DownloadError(url, "download error", e)); 76 | } 77 | } finally { 78 | // 写完后删除临时文件 79 | tmpFile.deleteSync(); 80 | Log.log("删除临时文件"); 81 | client.close(); 82 | } 83 | } 84 | 85 | void onReceive(String url) { 86 | futures.toList().forEach((future) { 87 | if (future.url == url) { 88 | future.completer.complete(url); 89 | futures.remove(future); 90 | } 91 | }); 92 | } 93 | 94 | _onDownloadStreamError(Object error) { 95 | if (error is DownloadError) { 96 | onError(error.url, error); 97 | } else { 98 | onError(null, error); 99 | } 100 | } 101 | 102 | void onError(String url, Object error) { 103 | futures.toList().forEach((future) { 104 | if (future.url == url) { 105 | if (error is Error) { 106 | future.completer.completeError( 107 | DownloadError(url, "download error", error, error.stackTrace)); 108 | } else { 109 | future.completer 110 | .completeError(DownloadError(url, "download error", error, null)); 111 | } 112 | futures.remove(future); 113 | } 114 | }); 115 | } 116 | 117 | Future waitForDownloadResult(String url, File targetFile) { 118 | Completer completer = Completer(); 119 | tryDownload(url, targetFile); 120 | futures.add(DownloadFuture(url, completer)); 121 | return completer.future; 122 | } 123 | } 124 | 125 | Future requestImage(String url, Directory targetDir) async { 126 | File targetFile = _downloadFilePath(url, targetDir); 127 | Log.log("下载文件为 $targetFile"); 128 | 129 | bool isDownloaded() { 130 | var tmpPath = _getTmpPath(targetFile); 131 | if (File(tmpPath).existsSync()) { 132 | return false; 133 | } 134 | return targetFile.existsSync(); 135 | } 136 | 137 | // bool isDownloading() { 138 | // return File(_getTmpPath(targetFile)).existsSync(); 139 | // } 140 | 141 | if (isDownloaded()) { 142 | Log.log("文件已下载,直接返回缓存"); 143 | return targetFile; 144 | } 145 | 146 | Log.log("图片未下载,加入下载队列"); 147 | 148 | await DownloadManager().waitForDownloadResult(url, targetFile); 149 | return targetFile; 150 | } 151 | 152 | File _downloadFilePath(String url, Directory targetDir) { 153 | var name = md5.convert(url.codeUnits).toString(); 154 | var lastName = url.split("/").last; 155 | if (!lastName.contains(".")) { 156 | return File(targetDir.absolute.path + "/" + name); 157 | } 158 | 159 | var extName = url.split("\.").last; 160 | var path = targetDir.absolute.path + "/" + "$name.$extName"; 161 | var targetFile = File(path); 162 | return targetFile; 163 | } 164 | 165 | String _getTmpPath(File targetFile) { 166 | var _pathList = targetFile.absolute.path.split("/"); 167 | var _name = _pathList.last; 168 | _pathList[_pathList.length - 1] = "tmp_$_name"; 169 | return _pathList.join("/"); 170 | } 171 | 172 | bool exists(String url, Directory targetDir) { 173 | File targetFile = _downloadFilePath(url, targetDir); 174 | return targetFile.existsSync(); 175 | } 176 | 177 | File getLocalFile(String url, Directory targetDir) { 178 | var name = md5.convert(url.codeUnits).toString(); 179 | var extName = url.split("\.").last; 180 | var path = targetDir.absolute.path + "/" + "$name.$extName"; 181 | var targetFile = File(path); 182 | if (targetFile.existsSync()) { 183 | return targetFile; 184 | } else { 185 | return null; 186 | } 187 | } 188 | 189 | void removeTmpPath(String url, Directory targetDir) { 190 | try { 191 | var targetFile = _downloadFilePath(url, targetDir); 192 | targetFile.deleteSync(); 193 | } catch (e) {} finally {} 194 | } 195 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:ok_image/ok_image.dart'; 3 | import 'package:ok_image/log.dart' as OL; 4 | 5 | void main() => runApp(MyApp()); 6 | 7 | class MyApp extends StatelessWidget { 8 | // This widget is the root of your application. 9 | @override 10 | Widget build(BuildContext context) { 11 | return MaterialApp( 12 | title: 'Flutter Demo', 13 | theme: ThemeData( 14 | // This is the theme of your application. 15 | // 16 | // Try running your application with "flutter run". You'll see the 17 | // application has a blue toolbar. Then, without quitting the app, try 18 | // changing the primarySwatch below to Colors.green and then invoke 19 | // "hot reload" (press "r" in the console where you ran "flutter run", 20 | // or simply save your changes to "hot reload" in a Flutter IDE). 21 | // Notice that the counter didn't reset back to zero; the application 22 | // is not restarted. 23 | primarySwatch: Colors.blue, 24 | ), 25 | home: MyHomePage(title: 'Flutter Demo Home Page'), 26 | ); 27 | } 28 | } 29 | 30 | class MyHomePage extends StatefulWidget { 31 | MyHomePage({Key key, this.title}) : super(key: key); 32 | 33 | // This widget is the home page of your application. It is stateful, meaning 34 | // that it has a State object (defined below) that contains fields that affect 35 | // how it looks. 36 | 37 | // This class is the configuration for the state. It holds the values (in this 38 | // case the title) provided by the parent (in this case the App widget) and 39 | // used by the build method of the State. Fields in a Widget subclass are 40 | // always marked "final". 41 | 42 | final String title; 43 | 44 | @override 45 | _MyHomePageState createState() => _MyHomePageState(); 46 | } 47 | 48 | class _MyHomePageState extends State { 49 | int _counter = 0; 50 | 51 | void _incrementCounter() { 52 | setState(() { 53 | // This call to setState tells the Flutter framework that something has 54 | // changed in this State, which causes it to rerun the build method below 55 | // so that the display can reflect the updated values. If we changed 56 | // _counter without calling setState(), then the build method would not be 57 | // called again, and so nothing would appear to happen. 58 | _counter++; 59 | }); 60 | } 61 | 62 | @override 63 | void initState() { 64 | super.initState(); 65 | OL.Log.showLog = true; 66 | OKImage.buildErrorWidget = () => Center( 67 | child: Text("I'm error"), 68 | ); 69 | OKImage.buildLoadingWidget = () => Row( 70 | children: [ 71 | Text("wait.."), 72 | ProgressWidget(), 73 | ], 74 | ); 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | // This method is rerun every time setState is called, for instance as done 80 | // by the _incrementCounter method above. 81 | // 82 | // The Flutter framework has been optimized to make rerunning build methods 83 | // fast, so that you can just rebuild anything that needs updating rather 84 | // than having to individually change instances of widgets. 85 | return Scaffold( 86 | appBar: AppBar( 87 | // Here we take the value from the MyHomePage object that was created by 88 | // the App.build method, and use it to set our appbar title. 89 | title: Text(widget.title), 90 | actions: [ 91 | IconButton( 92 | icon: Icon(Icons.clear), 93 | onPressed: () => ImageCacheManager().clearAllCache(), 94 | tooltip: "clear image cache file", 95 | ), 96 | ], 97 | ), 98 | body: Center( 99 | // Center is a layout widget. It takes a single child and positions it 100 | // in the middle of the parent. 101 | child: Column( 102 | // Column is also layout widget. It takes a list of children and 103 | // arranges them vertically. By default, it sizes itself to fit its 104 | // children horizontally, and tries to be as tall as its parent. 105 | // 106 | // Invoke "debug painting" (press "p" in the console, choose the 107 | // "Toggle Debug Paint" action from the Flutter Inspector in Android 108 | // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) 109 | // to see the wireframe for each widget. 110 | // 111 | // Column has various properties to control how it sizes itself and 112 | // how it positions its children. Here we use mainAxisAlignment to 113 | // center the children vertically; the main axis here is the vertical 114 | // axis because Columns are vertical (the cross axis would be 115 | // horizontal). 116 | mainAxisAlignment: MainAxisAlignment.center, 117 | children: [ 118 | Text( 119 | 'You have pushed the button this many times:', 120 | ), 121 | Text( 122 | '$_counter', 123 | style: Theme.of(context).textTheme.display1, 124 | ), 125 | buildNetImage(fit: BoxFit.contain), 126 | ], 127 | ), 128 | ), 129 | floatingActionButton: FloatingActionButton( 130 | onPressed: _incrementCounter, 131 | tooltip: 'Increment', 132 | child: Icon(Icons.add), 133 | ), // This trailing comma makes auto-formatting nicer for build methods. 134 | ); 135 | } 136 | 137 | Widget buildNetImage({BoxFit fit}) { 138 | return OKImage( 139 | // url: "https://ws1.sinaimg.cn/large/844036b9ly1fxfo76hzd4j20zk0nc48i.jpg", 140 | url: "http://avator.eastmoney.com/qface/3241045360517340/166", 141 | // loadingWidget: CircularProgressIndicator(), 142 | width: 200, 143 | height: 200, 144 | timeout: Duration(seconds: 20), 145 | fit: fit, 146 | onLoadStateChanged: _onLoadStateChange, 147 | ); 148 | } 149 | 150 | void _onLoadStateChange(OnLoadState value) { 151 | var now = DateTime.now(); 152 | print("$now : on load image state change : $value"); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 14 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 16 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 18 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 19 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 20 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 21 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 22 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 23 | F826683D9259725CB5469C6F /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 157EF007BF9976E8290FBF20 /* libPods-Runner.a */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXCopyFilesBuildPhase section */ 27 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 28 | isa = PBXCopyFilesBuildPhase; 29 | buildActionMask = 2147483647; 30 | dstPath = ""; 31 | dstSubfolderSpec = 10; 32 | files = ( 33 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 34 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 35 | ); 36 | name = "Embed Frameworks"; 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXCopyFilesBuildPhase section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 43 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 44 | 157EF007BF9976E8290FBF20 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 46 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 47 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 48 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 49 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 50 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 51 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 52 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 53 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 54 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 56 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 57 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 58 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 59 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 68 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 69 | F826683D9259725CB5469C6F /* libPods-Runner.a in Frameworks */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | 55CCDFB60B9D3BE58E0A13E6 /* Frameworks */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 157EF007BF9976E8290FBF20 /* libPods-Runner.a */, 80 | ); 81 | name = Frameworks; 82 | sourceTree = ""; 83 | }; 84 | 9740EEB11CF90186004384FC /* Flutter */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, 88 | 3B80C3931E831B6300D905FE /* App.framework */, 89 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 90 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 91 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 92 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 93 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 94 | ); 95 | name = Flutter; 96 | sourceTree = ""; 97 | }; 98 | 97C146E51CF9000F007C117D = { 99 | isa = PBXGroup; 100 | children = ( 101 | 9740EEB11CF90186004384FC /* Flutter */, 102 | 97C146F01CF9000F007C117D /* Runner */, 103 | 97C146EF1CF9000F007C117D /* Products */, 104 | B55585A6DFE6DB3D63419BD4 /* Pods */, 105 | 55CCDFB60B9D3BE58E0A13E6 /* Frameworks */, 106 | ); 107 | sourceTree = ""; 108 | }; 109 | 97C146EF1CF9000F007C117D /* Products */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 97C146EE1CF9000F007C117D /* Runner.app */, 113 | ); 114 | name = Products; 115 | sourceTree = ""; 116 | }; 117 | 97C146F01CF9000F007C117D /* Runner */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 121 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 122 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 123 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 124 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 125 | 97C147021CF9000F007C117D /* Info.plist */, 126 | 97C146F11CF9000F007C117D /* Supporting Files */, 127 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 128 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 129 | ); 130 | path = Runner; 131 | sourceTree = ""; 132 | }; 133 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 97C146F21CF9000F007C117D /* main.m */, 137 | ); 138 | name = "Supporting Files"; 139 | sourceTree = ""; 140 | }; 141 | B55585A6DFE6DB3D63419BD4 /* Pods */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | ); 145 | name = Pods; 146 | sourceTree = ""; 147 | }; 148 | /* End PBXGroup section */ 149 | 150 | /* Begin PBXNativeTarget section */ 151 | 97C146ED1CF9000F007C117D /* Runner */ = { 152 | isa = PBXNativeTarget; 153 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 154 | buildPhases = ( 155 | 55AA8F8C72D0ADDC4B5144FD /* [CP] Check Pods Manifest.lock */, 156 | 9740EEB61CF901F6004384FC /* Run Script */, 157 | 97C146EA1CF9000F007C117D /* Sources */, 158 | 97C146EB1CF9000F007C117D /* Frameworks */, 159 | 97C146EC1CF9000F007C117D /* Resources */, 160 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 161 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 162 | 47D26C6FFFE381F478AEE620 /* [CP] Embed Pods Frameworks */, 163 | ); 164 | buildRules = ( 165 | ); 166 | dependencies = ( 167 | ); 168 | name = Runner; 169 | productName = Runner; 170 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 171 | productType = "com.apple.product-type.application"; 172 | }; 173 | /* End PBXNativeTarget section */ 174 | 175 | /* Begin PBXProject section */ 176 | 97C146E61CF9000F007C117D /* Project object */ = { 177 | isa = PBXProject; 178 | attributes = { 179 | LastUpgradeCheck = 0910; 180 | ORGANIZATIONNAME = "The Chromium Authors"; 181 | TargetAttributes = { 182 | 97C146ED1CF9000F007C117D = { 183 | CreatedOnToolsVersion = 7.3.1; 184 | }; 185 | }; 186 | }; 187 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 188 | compatibilityVersion = "Xcode 3.2"; 189 | developmentRegion = English; 190 | hasScannedForEncodings = 0; 191 | knownRegions = ( 192 | en, 193 | Base, 194 | ); 195 | mainGroup = 97C146E51CF9000F007C117D; 196 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 197 | projectDirPath = ""; 198 | projectRoot = ""; 199 | targets = ( 200 | 97C146ED1CF9000F007C117D /* Runner */, 201 | ); 202 | }; 203 | /* End PBXProject section */ 204 | 205 | /* Begin PBXResourcesBuildPhase section */ 206 | 97C146EC1CF9000F007C117D /* Resources */ = { 207 | isa = PBXResourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 211 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 212 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 213 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 214 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, 215 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXResourcesBuildPhase section */ 220 | 221 | /* Begin PBXShellScriptBuildPhase section */ 222 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 223 | isa = PBXShellScriptBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | ); 227 | inputPaths = ( 228 | ); 229 | name = "Thin Binary"; 230 | outputPaths = ( 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | shellPath = /bin/sh; 234 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 235 | }; 236 | 47D26C6FFFE381F478AEE620 /* [CP] Embed Pods Frameworks */ = { 237 | isa = PBXShellScriptBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | ); 241 | inputFileListPaths = ( 242 | ); 243 | inputPaths = ( 244 | "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 245 | "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", 246 | ); 247 | name = "[CP] Embed Pods Frameworks"; 248 | outputFileListPaths = ( 249 | ); 250 | outputPaths = ( 251 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | shellPath = /bin/sh; 255 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 256 | showEnvVarsInLog = 0; 257 | }; 258 | 55AA8F8C72D0ADDC4B5144FD /* [CP] Check Pods Manifest.lock */ = { 259 | isa = PBXShellScriptBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | ); 263 | inputFileListPaths = ( 264 | ); 265 | inputPaths = ( 266 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 267 | "${PODS_ROOT}/Manifest.lock", 268 | ); 269 | name = "[CP] Check Pods Manifest.lock"; 270 | outputFileListPaths = ( 271 | ); 272 | outputPaths = ( 273 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | shellPath = /bin/sh; 277 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 278 | showEnvVarsInLog = 0; 279 | }; 280 | 9740EEB61CF901F6004384FC /* Run Script */ = { 281 | isa = PBXShellScriptBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | ); 285 | inputPaths = ( 286 | ); 287 | name = "Run Script"; 288 | outputPaths = ( 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | shellPath = /bin/sh; 292 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 293 | }; 294 | /* End PBXShellScriptBuildPhase section */ 295 | 296 | /* Begin PBXSourcesBuildPhase section */ 297 | 97C146EA1CF9000F007C117D /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 302 | 97C146F31CF9000F007C117D /* main.m in Sources */, 303 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | /* End PBXSourcesBuildPhase section */ 308 | 309 | /* Begin PBXVariantGroup section */ 310 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 311 | isa = PBXVariantGroup; 312 | children = ( 313 | 97C146FB1CF9000F007C117D /* Base */, 314 | ); 315 | name = Main.storyboard; 316 | sourceTree = ""; 317 | }; 318 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 319 | isa = PBXVariantGroup; 320 | children = ( 321 | 97C147001CF9000F007C117D /* Base */, 322 | ); 323 | name = LaunchScreen.storyboard; 324 | sourceTree = ""; 325 | }; 326 | /* End PBXVariantGroup section */ 327 | 328 | /* Begin XCBuildConfiguration section */ 329 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 330 | isa = XCBuildConfiguration; 331 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 332 | buildSettings = { 333 | ALWAYS_SEARCH_USER_PATHS = NO; 334 | CLANG_ANALYZER_NONNULL = YES; 335 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 336 | CLANG_CXX_LIBRARY = "libc++"; 337 | CLANG_ENABLE_MODULES = YES; 338 | CLANG_ENABLE_OBJC_ARC = YES; 339 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 340 | CLANG_WARN_BOOL_CONVERSION = YES; 341 | CLANG_WARN_COMMA = YES; 342 | CLANG_WARN_CONSTANT_CONVERSION = YES; 343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 344 | CLANG_WARN_EMPTY_BODY = YES; 345 | CLANG_WARN_ENUM_CONVERSION = YES; 346 | CLANG_WARN_INFINITE_RECURSION = YES; 347 | CLANG_WARN_INT_CONVERSION = YES; 348 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 349 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 351 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 352 | CLANG_WARN_STRICT_PROTOTYPES = YES; 353 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 354 | CLANG_WARN_UNREACHABLE_CODE = YES; 355 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 356 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 357 | COPY_PHASE_STRIP = NO; 358 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 359 | ENABLE_NS_ASSERTIONS = NO; 360 | ENABLE_STRICT_OBJC_MSGSEND = YES; 361 | GCC_C_LANGUAGE_STANDARD = gnu99; 362 | GCC_NO_COMMON_BLOCKS = YES; 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 370 | MTL_ENABLE_DEBUG_INFO = NO; 371 | SDKROOT = iphoneos; 372 | TARGETED_DEVICE_FAMILY = "1,2"; 373 | VALIDATE_PRODUCT = YES; 374 | }; 375 | name = Profile; 376 | }; 377 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 378 | isa = XCBuildConfiguration; 379 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 380 | buildSettings = { 381 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 382 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 383 | DEVELOPMENT_TEAM = S8QB4VV633; 384 | ENABLE_BITCODE = NO; 385 | FRAMEWORK_SEARCH_PATHS = ( 386 | "$(inherited)", 387 | "$(PROJECT_DIR)/Flutter", 388 | ); 389 | INFOPLIST_FILE = Runner/Info.plist; 390 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 391 | LIBRARY_SEARCH_PATHS = ( 392 | "$(inherited)", 393 | "$(PROJECT_DIR)/Flutter", 394 | ); 395 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | VERSIONING_SYSTEM = "apple-generic"; 398 | }; 399 | name = Profile; 400 | }; 401 | 97C147031CF9000F007C117D /* Debug */ = { 402 | isa = XCBuildConfiguration; 403 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 404 | buildSettings = { 405 | ALWAYS_SEARCH_USER_PATHS = NO; 406 | CLANG_ANALYZER_NONNULL = YES; 407 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 408 | CLANG_CXX_LIBRARY = "libc++"; 409 | CLANG_ENABLE_MODULES = YES; 410 | CLANG_ENABLE_OBJC_ARC = YES; 411 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 412 | CLANG_WARN_BOOL_CONVERSION = YES; 413 | CLANG_WARN_COMMA = YES; 414 | CLANG_WARN_CONSTANT_CONVERSION = YES; 415 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 416 | CLANG_WARN_EMPTY_BODY = YES; 417 | CLANG_WARN_ENUM_CONVERSION = YES; 418 | CLANG_WARN_INFINITE_RECURSION = YES; 419 | CLANG_WARN_INT_CONVERSION = YES; 420 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 421 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 422 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 423 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 424 | CLANG_WARN_STRICT_PROTOTYPES = YES; 425 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 426 | CLANG_WARN_UNREACHABLE_CODE = YES; 427 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 428 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 429 | COPY_PHASE_STRIP = NO; 430 | DEBUG_INFORMATION_FORMAT = dwarf; 431 | ENABLE_STRICT_OBJC_MSGSEND = YES; 432 | ENABLE_TESTABILITY = YES; 433 | GCC_C_LANGUAGE_STANDARD = gnu99; 434 | GCC_DYNAMIC_NO_PIC = NO; 435 | GCC_NO_COMMON_BLOCKS = YES; 436 | GCC_OPTIMIZATION_LEVEL = 0; 437 | GCC_PREPROCESSOR_DEFINITIONS = ( 438 | "DEBUG=1", 439 | "$(inherited)", 440 | ); 441 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 442 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 443 | GCC_WARN_UNDECLARED_SELECTOR = YES; 444 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 445 | GCC_WARN_UNUSED_FUNCTION = YES; 446 | GCC_WARN_UNUSED_VARIABLE = YES; 447 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 448 | MTL_ENABLE_DEBUG_INFO = YES; 449 | ONLY_ACTIVE_ARCH = YES; 450 | SDKROOT = iphoneos; 451 | TARGETED_DEVICE_FAMILY = "1,2"; 452 | }; 453 | name = Debug; 454 | }; 455 | 97C147041CF9000F007C117D /* Release */ = { 456 | isa = XCBuildConfiguration; 457 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 458 | buildSettings = { 459 | ALWAYS_SEARCH_USER_PATHS = NO; 460 | CLANG_ANALYZER_NONNULL = YES; 461 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 462 | CLANG_CXX_LIBRARY = "libc++"; 463 | CLANG_ENABLE_MODULES = YES; 464 | CLANG_ENABLE_OBJC_ARC = YES; 465 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 466 | CLANG_WARN_BOOL_CONVERSION = YES; 467 | CLANG_WARN_COMMA = YES; 468 | CLANG_WARN_CONSTANT_CONVERSION = YES; 469 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 470 | CLANG_WARN_EMPTY_BODY = YES; 471 | CLANG_WARN_ENUM_CONVERSION = YES; 472 | CLANG_WARN_INFINITE_RECURSION = YES; 473 | CLANG_WARN_INT_CONVERSION = YES; 474 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 475 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 476 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 477 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 478 | CLANG_WARN_STRICT_PROTOTYPES = YES; 479 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 480 | CLANG_WARN_UNREACHABLE_CODE = YES; 481 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 482 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 483 | COPY_PHASE_STRIP = NO; 484 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 485 | ENABLE_NS_ASSERTIONS = NO; 486 | ENABLE_STRICT_OBJC_MSGSEND = YES; 487 | GCC_C_LANGUAGE_STANDARD = gnu99; 488 | GCC_NO_COMMON_BLOCKS = YES; 489 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 490 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 491 | GCC_WARN_UNDECLARED_SELECTOR = YES; 492 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 493 | GCC_WARN_UNUSED_FUNCTION = YES; 494 | GCC_WARN_UNUSED_VARIABLE = YES; 495 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 496 | MTL_ENABLE_DEBUG_INFO = NO; 497 | SDKROOT = iphoneos; 498 | TARGETED_DEVICE_FAMILY = "1,2"; 499 | VALIDATE_PRODUCT = YES; 500 | }; 501 | name = Release; 502 | }; 503 | 97C147061CF9000F007C117D /* Debug */ = { 504 | isa = XCBuildConfiguration; 505 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 506 | buildSettings = { 507 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 508 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 509 | ENABLE_BITCODE = NO; 510 | FRAMEWORK_SEARCH_PATHS = ( 511 | "$(inherited)", 512 | "$(PROJECT_DIR)/Flutter", 513 | ); 514 | INFOPLIST_FILE = Runner/Info.plist; 515 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 516 | LIBRARY_SEARCH_PATHS = ( 517 | "$(inherited)", 518 | "$(PROJECT_DIR)/Flutter", 519 | ); 520 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 521 | PRODUCT_NAME = "$(TARGET_NAME)"; 522 | VERSIONING_SYSTEM = "apple-generic"; 523 | }; 524 | name = Debug; 525 | }; 526 | 97C147071CF9000F007C117D /* Release */ = { 527 | isa = XCBuildConfiguration; 528 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 529 | buildSettings = { 530 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 531 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 532 | ENABLE_BITCODE = NO; 533 | FRAMEWORK_SEARCH_PATHS = ( 534 | "$(inherited)", 535 | "$(PROJECT_DIR)/Flutter", 536 | ); 537 | INFOPLIST_FILE = Runner/Info.plist; 538 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 539 | LIBRARY_SEARCH_PATHS = ( 540 | "$(inherited)", 541 | "$(PROJECT_DIR)/Flutter", 542 | ); 543 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 544 | PRODUCT_NAME = "$(TARGET_NAME)"; 545 | VERSIONING_SYSTEM = "apple-generic"; 546 | }; 547 | name = Release; 548 | }; 549 | /* End XCBuildConfiguration section */ 550 | 551 | /* Begin XCConfigurationList section */ 552 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 553 | isa = XCConfigurationList; 554 | buildConfigurations = ( 555 | 97C147031CF9000F007C117D /* Debug */, 556 | 97C147041CF9000F007C117D /* Release */, 557 | 249021D3217E4FDB00AE95B9 /* Profile */, 558 | ); 559 | defaultConfigurationIsVisible = 0; 560 | defaultConfigurationName = Release; 561 | }; 562 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 563 | isa = XCConfigurationList; 564 | buildConfigurations = ( 565 | 97C147061CF9000F007C117D /* Debug */, 566 | 97C147071CF9000F007C117D /* Release */, 567 | 249021D4217E4FDB00AE95B9 /* Profile */, 568 | ); 569 | defaultConfigurationIsVisible = 0; 570 | defaultConfigurationName = Release; 571 | }; 572 | /* End XCConfigurationList section */ 573 | }; 574 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 575 | } 576 | --------------------------------------------------------------------------------