Browse Source

update assets_picker

lucky1213 3 years ago
parent
commit
99477c1e58
21 changed files with 2692 additions and 380 deletions
  1. 1
    1
      packages/zefyr/example/ios/Flutter/.last_build_id
  2. 3
    2
      packages/zefyr/example/ios/Flutter/flutter_export_environment.sh
  3. 15
    64
      packages/zefyr/example/ios/Podfile
  4. 14
    20
      packages/zefyr/example/ios/Podfile.lock
  5. 3
    0
      packages/zefyr/example/ios/Runner.xcodeproj/project.pbxproj
  6. 2
    0
      packages/zefyr/example/lib/generated_plugin_registrant.dart
  7. 256
    0
      packages/zefyr/lib/src/extended_assets_picker/ct_asset_picker.dart
  8. 20
    0
      packages/zefyr/lib/src/extended_assets_picker/src/constants/constants.dart
  9. 61
    0
      packages/zefyr/lib/src/extended_assets_picker/src/constants/screens.dart
  10. 582
    0
      packages/zefyr/lib/src/extended_assets_picker/src/delegate/asset_picker_builder_delegate.dart
  11. 534
    0
      packages/zefyr/lib/src/extended_assets_picker/src/delegate/asset_picker_quick_builder_delegate.dart
  12. 65
    0
      packages/zefyr/lib/src/extended_assets_picker/src/delegate/asset_picker_sort_delegate.dart
  13. 132
    0
      packages/zefyr/lib/src/extended_assets_picker/src/delegate/asset_picker_text_delegate.dart
  14. 520
    0
      packages/zefyr/lib/src/extended_assets_picker/src/delegate/asset_picker_viewer_builder_delegate.dart
  15. 10
    0
      packages/zefyr/lib/src/extended_assets_picker/src/entity/asset_picker_entity.dart
  16. 37
    0
      packages/zefyr/lib/src/extended_assets_picker/src/provider/asset_picker_provider.dart
  17. 64
    0
      packages/zefyr/lib/src/extended_assets_picker/src/widget/asset_picker_viewer.dart
  18. 283
    0
      packages/zefyr/lib/src/extended_assets_picker/src/widget/rounded_check_box.dart
  19. 65
    0
      packages/zefyr/lib/src/extended_assets_picker/src/widget/slide_page_transition_builder.dart
  20. 18
    289
      packages/zefyr/lib/src/widgets/buttons.dart
  21. 7
    4
      packages/zefyr/pubspec.yaml

+ 1
- 1
packages/zefyr/example/ios/Flutter/.last_build_id View File

@@ -1 +1 @@
1
-d787e51bb31e0c989f91f8e1d84f586f
1
+8a4c98a3fe9c76aad0683d58a59d0025

+ 3
- 2
packages/zefyr/example/ios/Flutter/flutter_export_environment.sh View File

@@ -2,14 +2,15 @@
2 2
 # This is a generated file; do not edit or check into version control.
3 3
 export "FLUTTER_ROOT=/Users/imac/fvm/versions/1.22.0"
4 4
 export "FLUTTER_APPLICATION_PATH=/Users/imac/Documents/flutter/projects/zefyr/packages/zefyr/example"
5
-export "FLUTTER_TARGET=lib/main.dart"
5
+export "FLUTTER_TARGET=/Users/imac/Documents/flutter/projects/zefyr/packages/zefyr/example/lib/main.dart"
6 6
 export "FLUTTER_BUILD_DIR=build"
7 7
 export "SYMROOT=${SOURCE_ROOT}/../build/ios"
8 8
 export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
9 9
 export "FLUTTER_FRAMEWORK_DIR=/Users/imac/fvm/versions/1.22.0/bin/cache/artifacts/engine/ios"
10 10
 export "FLUTTER_BUILD_NAME=1.0.0"
11 11
 export "FLUTTER_BUILD_NUMBER=1"
12
+export "DART_DEFINES=flutter.inspector.structuredErrors%3Dtrue"
12 13
 export "DART_OBFUSCATION=false"
13
-export "TRACK_WIDGET_CREATION=false"
14
+export "TRACK_WIDGET_CREATION=true"
14 15
 export "TREE_SHAKE_ICONS=false"
15 16
 export "PACKAGE_CONFIG=.packages"

+ 15
- 64
packages/zefyr/example/ios/Podfile View File

@@ -1,5 +1,5 @@
1 1
 # Uncomment this line to define a global platform for your project
2
-platform :ios, '9.0'
2
+# platform :ios, '9.0'
3 3
 
4 4
 # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 5
 ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@@ -10,78 +10,29 @@ project 'Runner', {
10 10
   'Release' => :release,
11 11
 }
12 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 [];
13
+def flutter_root
14
+  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15
+  unless File.exist?(generated_xcode_build_settings_path)
16
+    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 17
   end
18
-  generated_key_values = {}
19
-  skip_line_start_symbols = ["#", "/"]
20
-  File.foreach(file_abs_path) do |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
-      generated_key_values[podname] = podpath
28
-    else
29
-      puts "Invalid plugin specification: #{line}"
30
-    end
31
-  end
32
-  generated_key_values
33
-end
34
-
35
-target 'Runner' do
36
-  # Flutter Pod
37
-
38
-  copied_flutter_dir = File.join(__dir__, 'Flutter')
39
-  copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
40
-  copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
41
-  unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
42
-    # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
43
-    # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
44
-    # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
45 18
 
46
-    generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
47
-    unless File.exist?(generated_xcode_build_settings_path)
48
-      raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
49
-    end
50
-    generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
51
-    cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
52
-
53
-    unless File.exist?(copied_framework_path)
54
-      FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
55
-    end
56
-    unless File.exist?(copied_podspec_path)
57
-      FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
58
-    end
19
+  File.foreach(generated_xcode_build_settings_path) do |line|
20
+    matches = line.match(/FLUTTER_ROOT\=(.*)/)
21
+    return matches[1].strip if matches
59 22
   end
23
+  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24
+end
60 25
 
61
-  # Keep pod path relative so it can be checked into Podfile.lock.
62
-  pod 'Flutter', :path => 'Flutter'
26
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
63 27
 
64
-  # Plugin Pods
28
+flutter_ios_podfile_setup
65 29
 
66
-  # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
67
-  # referring to absolute paths on developers' machines.
68
-  system('rm -rf .symlinks')
69
-  system('mkdir -p .symlinks/plugins')
70
-  plugin_pods = parse_KV_file('../.flutter-plugins')
71
-  plugin_pods.each do |name, path|
72
-    symlink = File.join('.symlinks', 'plugins', name)
73
-    File.symlink(path, symlink)
74
-    pod name, :path => File.join(symlink, 'ios')
75
-  end
30
+target 'Runner' do
31
+  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
76 32
 end
77 33
 
78
-# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
79
-install! 'cocoapods', :disable_input_output_paths => true
80
-
81 34
 post_install do |installer|
82 35
   installer.pods_project.targets.each do |target|
83
-    target.build_configurations.each do |config|
84
-      config.build_settings['ENABLE_BITCODE'] = 'NO'
85
-    end
36
+    flutter_additional_ios_build_settings(target)
86 37
   end
87 38
 end

+ 14
- 20
packages/zefyr/example/ios/Podfile.lock View File

@@ -1,52 +1,46 @@
1 1
 PODS:
2 2
   - Flutter (1.0.0)
3
-  - flutter_plugin_android_lifecycle (0.0.1):
4
-    - Flutter
5 3
   - image_picker (0.0.1):
6 4
     - Flutter
5
+  - path_provider (0.0.1):
6
+    - Flutter
7 7
   - photo_manager (0.0.1):
8 8
     - Flutter
9 9
   - url_launcher (0.0.1):
10 10
     - Flutter
11
-  - url_launcher_macos (0.0.1):
12
-    - Flutter
13
-  - url_launcher_web (0.0.1):
11
+  - video_player (0.0.1):
14 12
     - Flutter
15 13
 
16 14
 DEPENDENCIES:
17 15
   - Flutter (from `Flutter`)
18
-  - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`)
19 16
   - image_picker (from `.symlinks/plugins/image_picker/ios`)
17
+  - path_provider (from `.symlinks/plugins/path_provider/ios`)
20 18
   - photo_manager (from `.symlinks/plugins/photo_manager/ios`)
21 19
   - url_launcher (from `.symlinks/plugins/url_launcher/ios`)
22
-  - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`)
23
-  - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`)
20
+  - video_player (from `.symlinks/plugins/video_player/ios`)
24 21
 
25 22
 EXTERNAL SOURCES:
26 23
   Flutter:
27 24
     :path: Flutter
28
-  flutter_plugin_android_lifecycle:
29
-    :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios"
30 25
   image_picker:
31 26
     :path: ".symlinks/plugins/image_picker/ios"
27
+  path_provider:
28
+    :path: ".symlinks/plugins/path_provider/ios"
32 29
   photo_manager:
33 30
     :path: ".symlinks/plugins/photo_manager/ios"
34 31
   url_launcher:
35 32
     :path: ".symlinks/plugins/url_launcher/ios"
36
-  url_launcher_macos:
37
-    :path: ".symlinks/plugins/url_launcher_macos/ios"
38
-  url_launcher_web:
39
-    :path: ".symlinks/plugins/url_launcher_web/ios"
33
+  video_player:
34
+    :path: ".symlinks/plugins/video_player/ios"
40 35
 
41 36
 SPEC CHECKSUMS:
42 37
   Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
43
-  flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35
44
-  image_picker: 66aa71bc96850a90590a35d4c4a2907b0d823109
38
+  image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09
39
+  path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
45 40
   photo_manager: f7c619c2cc8c2adb8d85c63363babac477de9c67
46 41
   url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
47
-  url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313
48
-  url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c
42
+  video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e
49 43
 
50
-PODFILE CHECKSUM: 49ec7d4076524b7e225c38b98147173651ac4b9d
44
+PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d
51 45
 
52
-COCOAPODS: 1.9.1
46
+COCOAPODS: 1.9.3

+ 3
- 0
packages/zefyr/example/ios/Runner.xcodeproj/project.pbxproj View File

@@ -219,9 +219,12 @@
219 219
 			files = (
220 220
 			);
221 221
 			inputPaths = (
222
+				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
223
+				"${PODS_ROOT}/../Flutter/Flutter.framework",
222 224
 			);
223 225
 			name = "[CP] Embed Pods Frameworks";
224 226
 			outputPaths = (
227
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
225 228
 			);
226 229
 			runOnlyForDeploymentPostprocessing = 0;
227 230
 			shellPath = /bin/sh;

+ 2
- 0
packages/zefyr/example/lib/generated_plugin_registrant.dart View File

@@ -6,11 +6,13 @@
6 6
 import 'dart:ui';
7 7
 
8 8
 import 'package:url_launcher_web/url_launcher_web.dart';
9
+import 'package:video_player_web/video_player_web.dart';
9 10
 
10 11
 import 'package:flutter_web_plugins/flutter_web_plugins.dart';
11 12
 
12 13
 // ignore: public_member_api_docs
13 14
 void registerPlugins(PluginRegistry registry) {
14 15
   UrlLauncherPlugin.registerWith(registry.registrarFor(UrlLauncherPlugin));
16
+  VideoPlayerPlugin.registerWith(registry.registrarFor(VideoPlayerPlugin));
15 17
   registry.registerMessageHandler();
16 18
 }

+ 256
- 0
packages/zefyr/lib/src/extended_assets_picker/ct_asset_picker.dart View File

@@ -0,0 +1,256 @@
1
+library ct_assets_picker;
2
+
3
+import 'dart:developer';
4
+import 'dart:io';
5
+import 'dart:math' as math;
6
+import 'dart:typed_data';
7
+import 'dart:ui' as ui;
8
+
9
+import 'package:extended_image/extended_image.dart';
10
+import 'package:flutter/foundation.dart';
11
+import 'package:flutter/material.dart';
12
+import 'package:flutter/rendering.dart';
13
+import 'package:flutter/services.dart';
14
+import 'package:intl/intl.dart';
15
+import 'package:notus/notus.dart';
16
+import 'package:provider/provider.dart';
17
+import 'package:wechat_assets_picker/wechat_assets_picker.dart';
18
+import 'package:image_picker/image_picker.dart';
19
+import 'package:zefyr/src/widgets/iconfont.dart';
20
+import 'package:zefyr/src/widgets/theme.dart';
21
+import 'package:zefyr/src/widgets/toolbar.dart';
22
+
23
+export 'package:wechat_assets_picker/wechat_assets_picker.dart'
24
+    show RequestType, AssetEntity, AssetType, PhotoManager;
25
+export 'package:image_picker/image_picker.dart' show CameraDevice;
26
+
27
+part 'src/constants/constants.dart';
28
+part 'src/constants/screens.dart';
29
+part 'src/delegate/asset_picker_builder_delegate.dart';
30
+part 'src/delegate/asset_picker_quick_builder_delegate.dart';
31
+part 'src/delegate/asset_picker_viewer_builder_delegate.dart';
32
+part 'src/delegate/asset_picker_sort_delegate.dart';
33
+part 'src/delegate/asset_picker_text_delegate.dart';
34
+part 'src/widget/slide_page_transition_builder.dart';
35
+part 'src/widget/asset_picker_viewer.dart';
36
+part 'src/widget/rounded_check_box.dart';
37
+part 'src/provider/asset_picker_provider.dart';
38
+part 'src/entity/asset_picker_entity.dart';
39
+
40
+class ExtendedAssetPicker {
41
+  static Future<AssetPickerEntity> pickAssets(
42
+    BuildContext context, {
43
+    int maxAssets = 9,
44
+    int pageSize = 320,
45
+    int pathThumbSize = 200,
46
+    int gridCount = 4,
47
+    List<int> previewThumbSize,
48
+    RequestType requestType,
49
+    SpecialPickerType specialPickerType,
50
+    List<AssetEntity> selectedAssets,
51
+    Color themeColor,
52
+    ThemeData pickerTheme,
53
+    SortPathDelegate sortPathDelegate,
54
+    AssetsPickerTextDelegate textDelegate,
55
+    FilterOptionGroup filterOptions,
56
+    WidgetBuilder specialItemBuilder,
57
+    SpecialItemPosition specialItemPosition = SpecialItemPosition.none,
58
+    bool allowSpecialItemWhenEmpty = false,
59
+    Curve routeCurve = Curves.easeIn,
60
+    Duration routeDuration = const Duration(milliseconds: 300),
61
+  }) async {
62
+    if (maxAssets == null || maxAssets < 1) {
63
+      throw ArgumentError(
64
+        'maxAssets must be greater than 1.',
65
+      );
66
+    }
67
+    if (pageSize != null && pageSize % gridCount != 0) {
68
+      throw ArgumentError(
69
+        'pageSize must be a multiple of gridCount.',
70
+      );
71
+    }
72
+    if (pickerTheme != null && themeColor != null) {
73
+      throw ArgumentError(
74
+        'Theme and theme color cannot be set at the same time.',
75
+      );
76
+    }
77
+    if (specialPickerType != null && requestType != null) {
78
+      throw ArgumentError(
79
+        'specialPickerType and requestType cannot be set at the same time.',
80
+      );
81
+    } else {
82
+      if (specialPickerType == SpecialPickerType.wechatMoment) {
83
+        requestType = RequestType.common;
84
+      } else {
85
+        requestType ??= RequestType.image;
86
+      }
87
+    }
88
+    if ((specialItemBuilder == null &&
89
+            specialItemPosition != SpecialItemPosition.none) ||
90
+        (specialItemBuilder != null &&
91
+            specialItemPosition == SpecialItemPosition.none)) {
92
+      throw ArgumentError('Custom item didn\'t set properly.');
93
+    }
94
+
95
+    final theme = Theme.of(context);
96
+
97
+    try {
98
+      final isPermissionGranted = await PhotoManager.requestPermission();
99
+      if (isPermissionGranted) {
100
+        final provider = ExtendedAssetPickerProvider(
101
+          maxAssets: maxAssets,
102
+          pageSize: pageSize,
103
+          pathThumbSize: pathThumbSize,
104
+          selectedAssets: selectedAssets,
105
+          requestType: requestType,
106
+          sortPathDelegate: sortPathDelegate ?? CustomSortPathDelegate(),
107
+          filterOptions: filterOptions,
108
+          routeDuration: routeDuration,
109
+        );
110
+        final Widget picker =
111
+            ChangeNotifierProvider<DefaultAssetPickerProvider>.value(
112
+          value: provider,
113
+          child: AssetPicker<AssetEntity, AssetPathEntity>(
114
+            key: Constants.pickerKey,
115
+            builder: ExtendedAssetPickerBuilderDelegate(
116
+              provider: provider,
117
+              gridCount: gridCount,
118
+              textDelegate: textDelegate ??
119
+                  (Intl.getCurrentLocale() == 'zh_CN'
120
+                      ? DefaultAssetsPickerTextDelegate()
121
+                      : EnglishTextDelegate()),
122
+              themeColor: themeColor,
123
+              pickerTheme: theme,
124
+              previewThumbSize: previewThumbSize,
125
+              specialPickerType: specialPickerType,
126
+              specialItemPosition: specialItemPosition,
127
+              specialItemBuilder: specialItemBuilder,
128
+              allowSpecialItemWhenEmpty: allowSpecialItemWhenEmpty,
129
+            ),
130
+          ),
131
+        );
132
+        final result = await Navigator.of(
133
+          context,
134
+          rootNavigator: true,
135
+        ).push<AssetPickerEntity>(
136
+          SlidePageTransitionBuilder<AssetPickerEntity>(
137
+            builder: picker,
138
+            transitionCurve: routeCurve,
139
+            transitionDuration: routeDuration,
140
+          ),
141
+        );
142
+        return result;
143
+      } else {
144
+        return null;
145
+      }
146
+    } catch (e) {
147
+      realDebugPrint('Error when calling assets picker: $e');
148
+      return null;
149
+    }
150
+  }
151
+
152
+  static Future<AssetEntity> pickSingleAsset(
153
+    BuildContext context, {
154
+    int pageSize = 320,
155
+    int pathThumbSize = 200,
156
+    int gridCount = 4,
157
+    List<int> previewThumbSize,
158
+    RequestType requestType,
159
+    SpecialPickerType specialPickerType,
160
+    List<AssetEntity> selectedAssets,
161
+    Color themeColor,
162
+    ThemeData pickerTheme,
163
+    SortPathDelegate sortPathDelegate,
164
+    AssetsPickerTextDelegate textDelegate,
165
+    FilterOptionGroup filterOptions,
166
+    WidgetBuilder specialItemBuilder,
167
+    SpecialItemPosition specialItemPosition = SpecialItemPosition.none,
168
+    bool allowSpecialItemWhenEmpty = false,
169
+    Curve routeCurve = Curves.easeIn,
170
+    Duration routeDuration = const Duration(milliseconds: 300),
171
+  }) async {
172
+    final result = await ExtendedAssetPicker.pickAssets(
173
+      context,
174
+      maxAssets: 1,
175
+      pageSize: pageSize,
176
+      pathThumbSize: pathThumbSize,
177
+      gridCount: gridCount,
178
+      previewThumbSize: previewThumbSize,
179
+      requestType: requestType,
180
+      specialPickerType: specialPickerType,
181
+      themeColor: themeColor,
182
+      pickerTheme: pickerTheme,
183
+      sortPathDelegate: sortPathDelegate,
184
+      textDelegate: textDelegate,
185
+      filterOptions: filterOptions,
186
+      specialItemBuilder: specialItemBuilder,
187
+      specialItemPosition: specialItemPosition,
188
+      allowSpecialItemWhenEmpty: allowSpecialItemWhenEmpty,
189
+      routeCurve: routeCurve,
190
+      routeDuration: routeDuration,
191
+    );
192
+    if (result != null) {
193
+      return result.assets?.first;
194
+    }
195
+    return null;
196
+  }
197
+
198
+  static Future<File> getImageFromCamera({
199
+    ImageSource source = ImageSource.camera,
200
+    double maxWidth,
201
+    double maxHeight,
202
+    int imageQuality = 100,
203
+    CameraDevice preferredCameraDevice = CameraDevice.rear,
204
+  }) {
205
+    return ImagePicker()
206
+        .getImage(
207
+            source: source,
208
+            maxWidth: maxWidth,
209
+            maxHeight: maxHeight,
210
+            imageQuality: imageQuality,
211
+            preferredCameraDevice: preferredCameraDevice)
212
+        .then((result) {
213
+      if (result != null) {
214
+        return File(result.path);
215
+      }
216
+      return null;
217
+    });
218
+  }
219
+
220
+  static Widget buildQuickPicker(
221
+    BuildContext context, {
222
+      AssetPickerProvider provider,
223
+    int gridCount = 4,
224
+    List<int> previewThumbSize,
225
+    SpecialPickerType specialPickerType,
226
+    Color themeColor,
227
+    ThemeData pickerTheme,
228
+    AssetsPickerTextDelegate textDelegate,
229
+    WidgetBuilder specialItemBuilder,
230
+    SpecialItemPosition specialItemPosition = SpecialItemPosition.none,
231
+    bool allowSpecialItemWhenEmpty = false,
232
+  }) {
233
+    final theme = Theme.of(context);
234
+    
235
+    return ChangeNotifierProvider<DefaultAssetPickerProvider>.value(
236
+      value: provider,
237
+      child: AssetPicker<AssetEntity, AssetPathEntity>(
238
+        key: Constants.pickerKey,
239
+        builder: ExtendedAssetPickerQuickBuilderDelegate(
240
+          provider: provider,
241
+          textDelegate: textDelegate ??
242
+              (Intl.getCurrentLocale() == 'zh_CN'
243
+                  ? ExtendedChineseTextDelegate()
244
+                  : ExtendedEnglishTextDelegate()),
245
+          themeColor: themeColor,
246
+          pickerTheme: theme,
247
+          previewThumbSize: previewThumbSize,
248
+          specialPickerType: specialPickerType,
249
+          specialItemPosition: specialItemPosition,
250
+          specialItemBuilder: specialItemBuilder,
251
+          allowSpecialItemWhenEmpty: allowSpecialItemWhenEmpty,
252
+        ),
253
+      ),
254
+    );
255
+  }
256
+}

+ 20
- 0
packages/zefyr/lib/src/extended_assets_picker/src/constants/constants.dart View File

@@ -0,0 +1,20 @@
1
+part of ct_assets_picker;
2
+
3
+class Constants {
4
+  const Constants._();
5
+
6
+  static GlobalKey pickerKey = GlobalKey();
7
+
8
+  static AssetsPickerTextDelegate textDelegate = DefaultAssetsPickerTextDelegate();
9
+  static SortPathDelegate sortPathDelegate = SortPathDelegate.common;
10
+
11
+  static const List<int> defaultPreviewThumbSize = <int>[200, 200];
12
+}
13
+
14
+/// Log only in debug mode.
15
+/// 只在调试模式打印
16
+void realDebugPrint(dynamic message) {
17
+  if (!kReleaseMode) {
18
+    log('$message');
19
+  }
20
+}

+ 61
- 0
packages/zefyr/lib/src/extended_assets_picker/src/constants/screens.dart View File

@@ -0,0 +1,61 @@
1
+///
2
+/// [Author] Alex (https://github.com/AlexV525)
3
+/// [Date] 2020/8/19 10:29
4
+///
5
+part of ct_assets_picker;
6
+
7
+/// Screens utils with multiple properties access.
8
+/// 获取屏幕各项属性的工具类
9
+class Screens {
10
+  const Screens._();
11
+
12
+  /// Get [MediaQueryData] from [ui.window]
13
+  /// 通过 [ui.window] 获取 [MediaQueryData]
14
+  static MediaQueryData get mediaQuery => MediaQueryData.fromWindow(ui.window);
15
+
16
+  /// The number of device pixels for each logical pixel.
17
+  /// 设备每个逻辑像素对应的dp比例
18
+  static double get scale => mediaQuery.devicePixelRatio;
19
+
20
+  /// The horizontal extent of this size.
21
+  /// 水平范围的大小
22
+  static double get width => mediaQuery.size.width;
23
+
24
+  /// The horizontal pixels of this size.
25
+  /// 水平范围的像素值
26
+  static int get widthPixels => (width * scale).toInt();
27
+
28
+  /// The vertical extent of this size.
29
+  /// 垂直范围的大小
30
+  static double get height => mediaQuery.size.height;
31
+
32
+  /// The vertical pixels of this size.
33
+  /// 垂直范围的像素值
34
+  static int get heightPixels => (height * scale).toInt();
35
+
36
+  /// Top offset in the [ui.window], usually is the notch size.
37
+  /// 从 [ui.window] 获取的顶部偏移(间距),通常是刘海的大小。
38
+  static double get topSafeHeight => mediaQuery.padding.top;
39
+
40
+  /// Bottom offset in the [ui.window], usually is the action bar/navigation bar size.
41
+  /// 从 [ui.window] 获取的底部偏移(间距),通常是操作条/导航条的大小。
42
+  static double get bottomSafeHeight => mediaQuery.padding.bottom;
43
+
44
+  /// Height exclude top and bottom safe height.
45
+  /// 去除顶部和底部安全区域的高度
46
+  static double get safeHeight => height - topSafeHeight - bottomSafeHeight;
47
+
48
+  /// Method to update status bar's style.
49
+  /// 更新状态栏样式的方法
50
+  static void updateStatusBarStyle(SystemUiOverlayStyle style) {
51
+    SystemChrome.setSystemUIOverlayStyle(style);
52
+  }
53
+
54
+  /// Scale factor for the text.
55
+  /// 文字缩放的倍数
56
+  static double get textScaleFactor => mediaQuery.textScaleFactor;
57
+
58
+  /// Return a fixed font size according to text scale factor.
59
+  /// 根据文字缩放计算出的固定文字大小
60
+  static double fixedFontSize(double fontSize) => fontSize / textScaleFactor;
61
+}

+ 582
- 0
packages/zefyr/lib/src/extended_assets_picker/src/delegate/asset_picker_builder_delegate.dart View File

@@ -0,0 +1,582 @@
1
+part of ct_assets_picker;
2
+
3
+class ExtendedAssetPickerBuilderDelegate extends DefaultAssetPickerBuilderDelegate {
4
+  ExtendedAssetPickerBuilderDelegate({
5
+    @required ExtendedAssetPickerProvider provider,
6
+    int gridCount = 4,
7
+    Color themeColor,
8
+    AssetsPickerTextDelegate textDelegate,
9
+    ThemeData pickerTheme,
10
+    SpecialItemPosition specialItemPosition = SpecialItemPosition.none,
11
+    WidgetBuilder specialItemBuilder,
12
+    bool allowSpecialItemWhenEmpty = false,
13
+    List<int> previewThumbSize,
14
+    SpecialPickerType specialPickerType,
15
+  })  : assert(
16
+          provider != null,
17
+          'AssetPickerProvider must be provided and not null.',
18
+        ),
19
+        assert(
20
+          pickerTheme == null || themeColor == null,
21
+          'Theme and theme color cannot be set at the same time.',
22
+        ),
23
+        super(
24
+          provider: provider,
25
+          gridCount: gridCount,
26
+          themeColor: themeColor,
27
+          textDelegate: textDelegate,
28
+          pickerTheme: pickerTheme,
29
+          specialItemPosition: specialItemPosition,
30
+          specialItemBuilder: specialItemBuilder,
31
+          allowSpecialItemWhenEmpty: allowSpecialItemWhenEmpty,
32
+          previewThumbSize: previewThumbSize,
33
+          specialPickerType: specialPickerType,
34
+        );
35
+
36
+  @override
37
+  bool get isAppleOS => true;
38
+
39
+  @override
40
+  double get appleOSBlurRadius => 0;
41
+
42
+  /// Action bar widget aligned to bottom.
43
+  /// 底部操作栏部件
44
+  @override
45
+  Widget bottomActionBar(BuildContext context) {
46
+    return !isSingleAssetMode ? Container(
47
+      width: Screens.width,
48
+      height: bottomActionBarHeight + Screens.bottomSafeHeight,
49
+      padding: EdgeInsets.only(
50
+        left: 20.0,
51
+        right: 20.0,
52
+        bottom: Screens.bottomSafeHeight,
53
+      ),
54
+      color: theme.colorScheme.surface,
55
+      child: Row(children: <Widget>[
56
+        previewButton(context),
57
+        Expanded(
58
+          child: fullImageButton(context),
59
+        ),
60
+        confirmButton(context),
61
+      ]),
62
+    ) : Container(
63
+      width: Screens.width,
64
+      height: Screens.bottomSafeHeight,
65
+    );
66
+  }
67
+
68
+  Widget fullImageButton(BuildContext context) {
69
+    return Selector<DefaultAssetPickerProvider, bool>(
70
+      selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
71
+          (provider as ExtendedAssetPickerProvider).fullImage,
72
+      builder: (BuildContext _, bool fullImage, Widget __) {
73
+        return GestureDetector(
74
+          onTap: () {
75
+            (provider as ExtendedAssetPickerProvider).fullImage = !fullImage;
76
+          },
77
+          child: Row(
78
+            mainAxisSize: MainAxisSize.min,
79
+            mainAxisAlignment: MainAxisAlignment.center,
80
+            children: [
81
+              Container(
82
+                // margin: EdgeInsets.all(Screens.width / gridCount / 15.0),
83
+                width: 18,
84
+                height: 18,
85
+                alignment: Alignment.center,
86
+                child: AnimatedContainer(
87
+                  duration: switchingPathDuration,
88
+                  width: 18,
89
+                  height: 18,
90
+                  decoration: BoxDecoration(
91
+                    border: Border.all(
92
+                        color: fullImage
93
+                            ? theme.colorScheme.primary
94
+                            : theme.unselectedWidgetColor,
95
+                        width: 2.0),
96
+                    color: null,
97
+                    shape: BoxShape.circle,
98
+                  ),
99
+                  child: AnimatedSwitcher(
100
+                    duration: switchingPathDuration,
101
+                    reverseDuration: switchingPathDuration,
102
+                    child: fullImage
103
+                        ? Container(
104
+                            width: 10,
105
+                            height: 10,
106
+                            decoration: BoxDecoration(
107
+                              color: theme.colorScheme.primary,
108
+                              shape: BoxShape.circle,
109
+                            ),
110
+                          )
111
+                        : const SizedBox.shrink(),
112
+                  ),
113
+                ),
114
+              ),
115
+              SizedBox(
116
+                width: 8,
117
+              ),
118
+              Text(
119
+                Constants.textDelegate.original,
120
+                style: TextStyle(
121
+                  color: theme.textTheme.caption.color,
122
+                  fontSize: 18.0,
123
+                ),
124
+              ),
125
+            ],
126
+          ),
127
+        );
128
+      },
129
+    );
130
+  }
131
+
132
+  /// The main grid view builder for assets.
133
+  /// 主要的资源查看网格部件
134
+  @override
135
+  Widget assetsGridBuilder(BuildContext context) {
136
+    return ColoredBox(
137
+      color: theme.colorScheme.background,
138
+      child: Selector<AssetPickerProvider<AssetEntity, AssetPathEntity>,
139
+          List<AssetEntity>>(
140
+        selector: (BuildContext _,
141
+                AssetPickerProvider<AssetEntity, AssetPathEntity> provider) =>
142
+            provider.currentAssets,
143
+        builder: (
144
+          BuildContext _,
145
+          List<AssetEntity> currentAssets,
146
+          Widget __,
147
+        ) {
148
+          return GridView.builder(
149
+            padding: isAppleOS
150
+                ? EdgeInsets.only(
151
+                    top: Screens.topSafeHeight + kToolbarHeight,
152
+                    bottom: bottomActionBarHeight,
153
+                  )
154
+                : EdgeInsets.zero,
155
+            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
156
+              crossAxisCount: gridCount,
157
+              mainAxisSpacing: itemSpacing,
158
+              crossAxisSpacing: itemSpacing,
159
+            ),
160
+            itemCount: assetsGridItemCount(_, currentAssets),
161
+            itemBuilder: (BuildContext _, int index) {
162
+              return assetGridItemBuilder(_, index, currentAssets);
163
+            },
164
+          );
165
+        },
166
+      ),
167
+    );
168
+  }
169
+
170
+  /// It'll pop with [AssetPickerProvider.selectedAssets]
171
+  /// when there're any assets chosen.
172
+  /// 当有资源已选时,点击按钮将把已选资源通过路由返回。
173
+  @override
174
+  Widget confirmButton(BuildContext context) {
175
+    return Consumer<DefaultAssetPickerProvider>(
176
+      builder: (
177
+        BuildContext _,
178
+        DefaultAssetPickerProvider provider,
179
+        Widget __,
180
+      ) {
181
+        return FlatButton(
182
+          minWidth: provider.isSelectedNotEmpty ? 48.0 : 20.0,
183
+          height: appBarItemHeight,
184
+          padding: const EdgeInsets.symmetric(horizontal: 12.0),
185
+          color: provider.isSelectedNotEmpty
186
+              ? theme.colorScheme.primary
187
+              : theme.dividerColor,
188
+          shape: RoundedRectangleBorder(
189
+            borderRadius: BorderRadius.circular(3.0),
190
+          ),
191
+          child: Text(
192
+            provider.isSelectedNotEmpty && !isSingleAssetMode
193
+                ? '${Constants.textDelegate.confirm}'
194
+                    '(${provider.selectedAssets.length}/${provider.maxAssets})'
195
+                : Constants.textDelegate.confirm,
196
+            style: TextStyle(
197
+              color: provider.isSelectedNotEmpty
198
+                  ? theme.colorScheme.onPrimary
199
+                  : theme.textTheme.caption.color,
200
+              fontSize: 17.0,
201
+              fontWeight: FontWeight.normal,
202
+            ),
203
+          ),
204
+          onPressed: () {
205
+            if (provider.isSelectedNotEmpty) {
206
+              Navigator.of(context).pop(AssetPickerEntity(
207
+                  assets: provider.selectedAssets,
208
+                  isFullImage: (provider as ExtendedAssetPickerProvider).fullImage,
209
+                ));
210
+            }
211
+          },
212
+          materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
213
+        );
214
+      },
215
+    );
216
+  }
217
+
218
+  @override
219
+  Widget previewButton(BuildContext context) {
220
+    return Selector<DefaultAssetPickerProvider, bool>(
221
+      selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
222
+          provider.isSelectedNotEmpty,
223
+      builder: (BuildContext _, bool isSelectedNotEmpty, Widget __) {
224
+        return GestureDetector(
225
+          onTap: isSelectedNotEmpty
226
+              ? () async {
227
+                  final List<AssetEntity> result =
228
+                      await ExtendedAssetPickerViewer.pushToViewer(
229
+                    context,
230
+                    currentIndex: 0,
231
+                    previewAssets: provider.currentAssets,
232
+                    previewThumbSize: previewThumbSize,
233
+                    selectedAssets: provider.selectedAssets,
234
+                    selectorProvider: provider as ExtendedAssetPickerProvider,
235
+                    themeData: theme,
236
+                  );
237
+                  if (result != null) {
238
+                    Navigator.of(context).pop(AssetPickerEntity(
239
+                      assets: result,
240
+                      isFullImage: (provider as ExtendedAssetPickerProvider).fullImage,
241
+                    ));
242
+                  }
243
+                }
244
+              : null,
245
+          child: Padding(
246
+            padding: const EdgeInsets.symmetric(vertical: 12.0),
247
+            child: Selector<DefaultAssetPickerProvider, List<AssetEntity>>(
248
+              selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
249
+                  provider.selectedAssets,
250
+              builder: (
251
+                BuildContext _,
252
+                List<AssetEntity> selectedAssets,
253
+                Widget __,
254
+              ) {
255
+                return Text(
256
+                  isSelectedNotEmpty
257
+                      ? '${Constants.textDelegate.preview}'
258
+                          '(${provider.selectedAssets.length})'
259
+                      : Constants.textDelegate.preview,
260
+                  style: TextStyle(
261
+                    color: isSelectedNotEmpty
262
+                        ? null
263
+                        : theme.textTheme.caption.color,
264
+                    fontSize: 18.0,
265
+                  ),
266
+                );
267
+              },
268
+            ),
269
+          ),
270
+        );
271
+      },
272
+    );
273
+  }
274
+
275
+  @override
276
+  Widget selectIndicator(BuildContext context, AssetEntity asset) {
277
+    if (isSingleAssetMode) {
278
+      return Container();
279
+    }
280
+    return Selector<DefaultAssetPickerProvider, List<AssetEntity>>(
281
+      selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
282
+          provider.selectedAssets,
283
+      builder: (BuildContext _, List<AssetEntity> selectedAssets, Widget __) {
284
+        final bool selected = selectedAssets.contains(asset);
285
+        final double indicatorSize = Screens.width / gridCount / 3;
286
+        return Positioned(
287
+          top: 0.0,
288
+          right: 0.0,
289
+          child: GestureDetector(
290
+            behavior: HitTestBehavior.opaque,
291
+            onTap: () {
292
+              if (selected) {
293
+                provider.unSelectAsset(asset);
294
+              } else {
295
+                if (isSingleAssetMode) {
296
+                  provider.selectedAssets.clear();
297
+                }
298
+                provider.selectAsset(asset);
299
+              }
300
+            },
301
+            child: Container(
302
+              margin: EdgeInsets.all(Screens.width / gridCount / 15.0),
303
+              width: indicatorSize,
304
+              height: indicatorSize,
305
+              alignment: AlignmentDirectional.topEnd,
306
+              child: AnimatedContainer(
307
+                duration: switchingPathDuration,
308
+                width: indicatorSize / 1.5,
309
+                height: indicatorSize / 1.5,
310
+                decoration: BoxDecoration(
311
+                  border: !selected
312
+                      ? Border.all(color: Colors.white, width: 2.0)
313
+                      : null,
314
+                  color: selected ? theme.colorScheme.primary : null,
315
+                  shape: BoxShape.circle,
316
+                ),
317
+                child: AnimatedSwitcher(
318
+                  duration: switchingPathDuration,
319
+                  reverseDuration: switchingPathDuration,
320
+                  child: selected
321
+                      ? isSingleAssetMode
322
+                          ? const Icon(Icons.check, size: 18.0)
323
+                          : Text(
324
+                              '${selectedAssets.indexOf(asset) + 1}',
325
+                              style: TextStyle(
326
+                                color: selected
327
+                                    ? theme.colorScheme.onPrimary
328
+                                    : null,
329
+                                fontSize: 14.0,
330
+                                fontWeight: isAppleOS
331
+                                    ? FontWeight.w600
332
+                                    : FontWeight.bold,
333
+                              ),
334
+                            )
335
+                      : const SizedBox.shrink(),
336
+                ),
337
+              ),
338
+            ),
339
+          ),
340
+        );
341
+      },
342
+    );
343
+  }
344
+
345
+  @override
346
+  Widget selectedBackdrop(BuildContext context, int index, AssetEntity asset) {
347
+    return Selector<DefaultAssetPickerProvider, List<AssetEntity>>(
348
+      selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
349
+          provider.selectedAssets,
350
+      builder: (BuildContext _, List<AssetEntity> selectedAssets, Widget __) {
351
+        final bool selected = selectedAssets.contains(asset);
352
+        return Positioned.fill(
353
+          child: GestureDetector(
354
+            onTap: () async {
355
+              if (isSingleAssetMode) {
356
+                Navigator.of(context).pop(AssetPickerEntity(
357
+                  assets: [asset],
358
+                  isFullImage: (provider as ExtendedAssetPickerProvider).fullImage,
359
+                ));
360
+                return;
361
+              }
362
+              final List<AssetEntity> result =
363
+                  await ExtendedAssetPickerViewer.pushToViewer(
364
+                context,
365
+                currentIndex: index,
366
+                previewAssets: provider.currentAssets,
367
+                previewThumbSize: previewThumbSize,
368
+                selectedAssets: provider.selectedAssets,
369
+                selectorProvider: provider as ExtendedAssetPickerProvider,
370
+                themeData: theme,
371
+              );
372
+              if (result != null) {
373
+                Navigator.of(context).pop(AssetPickerEntity(
374
+                  assets: result,
375
+                  isFullImage: (provider as ExtendedAssetPickerProvider).fullImage,
376
+                ));
377
+              }
378
+            },
379
+            child: AnimatedContainer(
380
+              duration: switchingPathDuration,
381
+              color: selected
382
+                  ? Colors.black.withOpacity(0.45)
383
+                  : Colors.black.withOpacity(0.1),
384
+            ),
385
+          ), // 点击预览同目录下所有资源
386
+        );
387
+      },
388
+    );
389
+  }
390
+
391
+  @override
392
+  Widget pathEntityListWidget(BuildContext context) {
393
+    final double appBarHeight = kToolbarHeight + Screens.topSafeHeight;
394
+    final double maxHeight = Screens.height * 0.625;
395
+    return Selector<DefaultAssetPickerProvider, bool>(
396
+      selector: (
397
+        BuildContext _,
398
+        DefaultAssetPickerProvider provider,
399
+      ) =>
400
+          provider.isSwitchingPath,
401
+      builder: (BuildContext _, bool isSwitchingPath, Widget __) {
402
+        final pathHeight = provider.pathEntityList.length * 65.0;
403
+        final double height = pathHeight > maxHeight ? maxHeight : pathHeight;
404
+        return AnimatedPositioned(
405
+          duration: switchingPathDuration,
406
+          curve: switchingPathCurve,
407
+          top: isAppleOS
408
+              ? !isSwitchingPath
409
+                  ? -height
410
+                  : appBarHeight
411
+              : -(!isSwitchingPath ? maxHeight : 1.0),
412
+          child: AnimatedOpacity(
413
+            duration: switchingPathDuration,
414
+            curve: switchingPathCurve,
415
+            opacity: !isAppleOS || isSwitchingPath ? 1.0 : 0.0,
416
+            child: Container(
417
+              width: Screens.width,
418
+              height: height,
419
+              decoration: BoxDecoration(
420
+                borderRadius: isAppleOS
421
+                    ? const BorderRadius.only(
422
+                        bottomLeft: Radius.circular(10.0),
423
+                        bottomRight: Radius.circular(10.0),
424
+                      )
425
+                    : null,
426
+                color: theme.colorScheme.surface,
427
+              ),
428
+              child: Selector<DefaultAssetPickerProvider,
429
+                  Map<AssetPathEntity, Uint8List>>(
430
+                selector: (
431
+                  BuildContext _,
432
+                  DefaultAssetPickerProvider provider,
433
+                ) =>
434
+                    provider.pathEntityList,
435
+                builder: (
436
+                  BuildContext _,
437
+                  Map<AssetPathEntity, Uint8List> pathEntityList,
438
+                  Widget __,
439
+                ) {
440
+                  return ListView.separated(
441
+                    padding: const EdgeInsets.only(top: 1.0),
442
+                    itemCount: pathEntityList.length,
443
+                    itemBuilder: (BuildContext _, int index) {
444
+                      return pathEntityWidget(
445
+                        context,
446
+                        pathEntityList.keys.elementAt(index),
447
+                      );
448
+                    },
449
+                    separatorBuilder: (BuildContext _, int __) => Container(
450
+                      margin: const EdgeInsets.only(left: 60.0),
451
+                      height: 1.0,
452
+                      color: theme.canvasColor,
453
+                    ),
454
+                  );
455
+                },
456
+              ),
457
+            ),
458
+          ),
459
+        );
460
+      },
461
+    );
462
+  }
463
+
464
+  @override
465
+  Widget pathEntityWidget(BuildContext context, AssetPathEntity path) {
466
+    Widget builder(
467
+      BuildContext context,
468
+      Map<AssetPathEntity, Uint8List> pathEntityList,
469
+      Widget __,
470
+    ) {
471
+      if (context.watch<DefaultAssetPickerProvider>().requestType ==
472
+          RequestType.audio) {
473
+        return ColoredBox(
474
+          color: theme.colorScheme.primary.withOpacity(0.12),
475
+          child: const Center(child: Icon(Icons.audiotrack)),
476
+        );
477
+      }
478
+
479
+      /// The reason that the `thumbData` should be checked at here to see if it
480
+      /// is null is that even the image file is not exist, the `File` can still
481
+      /// returned as it exist, which will cause the thumb bytes return null.
482
+      ///
483
+      /// 此处需要检查缩略图为空的原因是:尽管文件可能已经被删除,但通过`File`读取的文件
484
+      /// 对象 仍然存在,使得返回的数据为空。
485
+      final Uint8List thumbData = pathEntityList[path];
486
+      if (thumbData != null) {
487
+        return Image.memory(
488
+          pathEntityList[path],
489
+          fit: BoxFit.cover,
490
+        );
491
+      } else {
492
+        return ColoredBox(
493
+          color: theme.colorScheme.primary.withOpacity(0.12),
494
+        );
495
+      }
496
+    }
497
+
498
+    return Material(
499
+      type: MaterialType.transparency,
500
+      child: InkWell(
501
+        splashFactory: InkSplash.splashFactory,
502
+        onTap: () => provider.switchPath(path),
503
+        child: SizedBox(
504
+          height: isAppleOS ? 64.0 : 52.0,
505
+          child: Padding(
506
+            padding: EdgeInsets.symmetric(horizontal: 16),
507
+            child: Row(
508
+              children: <Widget>[
509
+                SizedBox(
510
+                  width: 50,
511
+                  height: 50,
512
+                  child: RepaintBoundary(
513
+                    child: AspectRatio(
514
+                      aspectRatio: 1.0,
515
+                      child: Selector<DefaultAssetPickerProvider,
516
+                          Map<AssetPathEntity, Uint8List>>(
517
+                        selector: (
518
+                          BuildContext _,
519
+                          DefaultAssetPickerProvider provider,
520
+                        ) =>
521
+                            provider.pathEntityList,
522
+                        builder: builder,
523
+                      ),
524
+                    ),
525
+                  ),
526
+                ),
527
+                Expanded(
528
+                  child: Padding(
529
+                    padding: const EdgeInsets.only(left: 15.0, right: 20.0),
530
+                    child: Row(
531
+                      children: <Widget>[
532
+                        Flexible(
533
+                          child: Padding(
534
+                            padding: const EdgeInsets.only(right: 10.0),
535
+                            child: Text(
536
+                              path.name ?? '',
537
+                              style: const TextStyle(fontSize: 16.0),
538
+                              maxLines: 1,
539
+                              overflow: TextOverflow.ellipsis,
540
+                            ),
541
+                          ),
542
+                        ),
543
+                        Text(
544
+                          '(${path.assetCount})',
545
+                          style: TextStyle(
546
+                            color: theme.textTheme.caption.color,
547
+                            fontSize: 16.0,
548
+                          ),
549
+                          maxLines: 1,
550
+                          overflow: TextOverflow.ellipsis,
551
+                        ),
552
+                      ],
553
+                    ),
554
+                  ),
555
+                ),
556
+                Selector<DefaultAssetPickerProvider, AssetPathEntity>(
557
+                  selector: (
558
+                    BuildContext _,
559
+                    DefaultAssetPickerProvider provider,
560
+                  ) =>
561
+                      provider.currentPathEntity,
562
+                  builder: (
563
+                    BuildContext _,
564
+                    AssetPathEntity currentPathEntity,
565
+                    Widget __,
566
+                  ) {
567
+                    if (currentPathEntity == path) {
568
+                      return Icon(ZefyrFont.multiselect_selected,
569
+                          color: theme.colorScheme.primary, size: 20.0);
570
+                    } else {
571
+                      return const SizedBox.shrink();
572
+                    }
573
+                  },
574
+                ),
575
+              ],
576
+            ),
577
+          ),
578
+        ),
579
+      ),
580
+    );
581
+  }
582
+}

+ 534
- 0
packages/zefyr/lib/src/extended_assets_picker/src/delegate/asset_picker_quick_builder_delegate.dart View File

@@ -0,0 +1,534 @@
1
+part of ct_assets_picker;
2
+
3
+class ExtendedAssetPickerQuickBuilderDelegate
4
+    extends DefaultAssetPickerBuilderDelegate {
5
+  ExtendedAssetPickerQuickBuilderDelegate({
6
+    @required ExtendedAssetPickerProvider provider,
7
+    int gridCount = 4,
8
+    Color themeColor,
9
+    AssetsPickerTextDelegate textDelegate,
10
+    ThemeData pickerTheme,
11
+    SpecialItemPosition specialItemPosition = SpecialItemPosition.none,
12
+    WidgetBuilder specialItemBuilder,
13
+    bool allowSpecialItemWhenEmpty = false,
14
+    List<int> previewThumbSize,
15
+    SpecialPickerType specialPickerType,
16
+  })  : assert(
17
+          provider != null,
18
+          'AssetPickerProvider must be provided and not null.',
19
+        ),
20
+        assert(
21
+          pickerTheme == null || themeColor == null,
22
+          'Theme and theme color cannot be set at the same time.',
23
+        ),
24
+        super(
25
+          provider: provider,
26
+          gridCount: gridCount,
27
+          themeColor: themeColor,
28
+          textDelegate: textDelegate,
29
+          pickerTheme: pickerTheme,
30
+          specialItemPosition: specialItemPosition,
31
+          specialItemBuilder: specialItemBuilder,
32
+          allowSpecialItemWhenEmpty: allowSpecialItemWhenEmpty,
33
+          previewThumbSize: previewThumbSize,
34
+          specialPickerType: specialPickerType,
35
+        ) {
36
+    Constants.textDelegate = textDelegate ?? DefaultAssetsPickerTextDelegate();
37
+  }
38
+
39
+  @override
40
+  bool get isAppleOS => true;
41
+
42
+  @override
43
+  double get appleOSBlurRadius => 0;
44
+
45
+  /// The main grid view builder for assets.
46
+  /// 主要的资源查看列表部件
47
+  Widget assetsListBuilder(BuildContext context) {
48
+    return ColoredBox(
49
+      color: theme.canvasColor,
50
+      child: Selector<AssetPickerProvider<AssetEntity, AssetPathEntity>,
51
+          List<AssetEntity>>(
52
+        selector: (BuildContext _,
53
+                AssetPickerProvider<AssetEntity, AssetPathEntity> provider) =>
54
+            provider.currentAssets,
55
+        builder: (
56
+          BuildContext _,
57
+          List<AssetEntity> currentAssets,
58
+          Widget __,
59
+        ) {
60
+          return ListView.builder(
61
+            scrollDirection: Axis.horizontal,
62
+            physics: ClampingScrollPhysics(),
63
+            padding: EdgeInsets.zero,
64
+            itemCount: assetsGridItemCount(_, currentAssets),
65
+            itemBuilder: (BuildContext _, int index) {
66
+              return assetGridItemBuilder(_, index, currentAssets);
67
+            },
68
+          );
69
+        },
70
+      ),
71
+    );
72
+  }
73
+
74
+  @override
75
+  Widget assetGridItemBuilder(
76
+      BuildContext context, int index, List<AssetEntity> assets) {
77
+    return Padding(
78
+      padding: EdgeInsets.only(left: index != 0 ? 4 : 0),
79
+      child: AspectRatio(
80
+        aspectRatio: 0.5,
81
+        child: super.assetGridItemBuilder(context, index, assets),
82
+      ),
83
+    );
84
+  }
85
+
86
+  @override
87
+  int assetsGridItemCount(
88
+    BuildContext context,
89
+    List<AssetEntity> currentAssets,
90
+  ) {
91
+    final AssetPathEntity currentPathEntity =
92
+        Provider.of<DefaultAssetPickerProvider>(
93
+      context,
94
+      listen: false,
95
+    ).currentPathEntity;
96
+
97
+    if (currentPathEntity == null &&
98
+        specialItemPosition != SpecialItemPosition.none) {
99
+      return 1;
100
+    }
101
+
102
+    /// Return actual length if current path is all.
103
+    /// 如果当前目录是全部内容,则返回实际的内容数量。
104
+    if (!(currentPathEntity?.isAll ?? false)) {
105
+      return currentAssets?.length ?? 0;
106
+    }
107
+    int length;
108
+    switch (specialItemPosition) {
109
+      case SpecialItemPosition.none:
110
+        length = currentAssets.length;
111
+        break;
112
+      case SpecialItemPosition.prepend:
113
+      case SpecialItemPosition.append:
114
+        length = currentAssets.length + 1;
115
+        break;
116
+    }
117
+    return length;
118
+  }
119
+
120
+  /// It'll pop with [AssetPickerProvider.selectedAssets]
121
+  /// when there're any assets chosen.
122
+  /// 当有资源已选时,点击按钮将把已选资源通过路由返回。
123
+  @override
124
+  Widget confirmButton(BuildContext context) {
125
+    final zefyrTheme = ZefyrTheme.of(context).toolbarTheme;
126
+    final toolbar = ZefyrToolbar.of(context);
127
+    return Consumer<DefaultAssetPickerProvider>(
128
+      builder: (
129
+        BuildContext _,
130
+        DefaultAssetPickerProvider provider,
131
+        Widget __,
132
+      ) {
133
+        return FlatButton(
134
+          minWidth: provider.isSelectedNotEmpty ? 48.0 : 20.0,
135
+          height: appBarItemHeight,
136
+          shape: StadiumBorder(),
137
+          padding: const EdgeInsets.symmetric(horizontal: 12.0),
138
+          color: provider.isSelectedNotEmpty
139
+              ? zefyrTheme.toggleColor
140
+              : zefyrTheme.disabledIconColor,
141
+          child: Text(
142
+            provider.isSelectedNotEmpty && !isSingleAssetMode
143
+                ? '${Constants.textDelegate.confirm}'
144
+                    '(${provider.selectedAssets.length}/${provider.maxAssets})'
145
+                : Constants.textDelegate.confirm,
146
+            style: TextStyle(
147
+              color: Colors.white,
148
+              fontSize: 16.0,
149
+              fontWeight: FontWeight.normal,
150
+            ),
151
+          ),
152
+          onPressed: () async {
153
+            if (provider.isSelectedNotEmpty) {
154
+              toolbar.editor.closeKeyboard(true);
155
+              for (var asset in provider.selectedAssets) {
156
+                var file = await asset.originFile;
157
+                final image = await toolbar.editor.imageDelegate.picked(
158
+                    file, (provider as ExtendedAssetPickerProvider).fullImage);
159
+                if (image != null) {
160
+                  toolbar.editor
161
+                      .formatSelection(NotusAttribute.embed.image(image));
162
+                  toolbar.editor.closeKeyboard();
163
+                }
164
+              }
165
+            }
166
+          },
167
+          materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
168
+        );
169
+      },
170
+    );
171
+  }
172
+
173
+  Widget fullImageButton(BuildContext context) {
174
+    final zefyrTheme = ZefyrTheme.of(context).toolbarTheme;
175
+    return Selector<DefaultAssetPickerProvider, bool>(
176
+      selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
177
+          (provider as ExtendedAssetPickerProvider).fullImage,
178
+      builder: (BuildContext _, bool fullImage, Widget __) {
179
+        return GestureDetector(
180
+          onTap: () {
181
+            (provider as ExtendedAssetPickerProvider).fullImage = !fullImage;
182
+          },
183
+          child: Row(
184
+            mainAxisSize: MainAxisSize.min,
185
+            mainAxisAlignment: MainAxisAlignment.center,
186
+            children: [
187
+              Container(
188
+                // margin: EdgeInsets.all(Screens.width / gridCount / 15.0),
189
+                width: 18,
190
+                height: 18,
191
+                alignment: Alignment.center,
192
+                child: AnimatedContainer(
193
+                  duration: switchingPathDuration,
194
+                  width: 18,
195
+                  height: 18,
196
+                  decoration: BoxDecoration(
197
+                    border: Border.all(
198
+                        color: fullImage
199
+                            ? zefyrTheme.toggleColor
200
+                            : zefyrTheme.disabledIconColor,
201
+                        width: 2.0),
202
+                    color: null,
203
+                    shape: BoxShape.circle,
204
+                  ),
205
+                  child: AnimatedSwitcher(
206
+                    duration: switchingPathDuration,
207
+                    reverseDuration: switchingPathDuration,
208
+                    child: fullImage
209
+                        ? Container(
210
+                            width: 10,
211
+                            height: 10,
212
+                            decoration: BoxDecoration(
213
+                              color: zefyrTheme.toggleColor,
214
+                              shape: BoxShape.circle,
215
+                            ),
216
+                          )
217
+                        : const SizedBox.shrink(),
218
+                  ),
219
+                ),
220
+              ),
221
+              SizedBox(
222
+                width: 8,
223
+              ),
224
+              Text(
225
+                Constants.textDelegate.original,
226
+                style: TextStyle(
227
+                  color: zefyrTheme.iconColor,
228
+                  fontSize: 16.0,
229
+                ),
230
+              ),
231
+            ],
232
+          ),
233
+        );
234
+      },
235
+    );
236
+  }
237
+
238
+  /// Action bar widget aligned to bottom.
239
+  /// 底部操作栏部件
240
+  @override
241
+  Widget bottomActionBar(BuildContext context) {
242
+    final zefyrTheme = ZefyrTheme.of(context).toolbarTheme;
243
+    return Container(
244
+      height: kToolbarHeight + Screens.bottomSafeHeight,
245
+      color: zefyrTheme.color,
246
+      padding: EdgeInsets.symmetric(horizontal: 20).copyWith(
247
+        bottom: Screens.bottomSafeHeight,
248
+      ),
249
+      child: Row(
250
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
251
+        children: [
252
+          fullImageButton(context),
253
+          confirmButton(context),
254
+        ],
255
+      ),
256
+    );
257
+  }
258
+  
259
+  @override
260
+  Widget selectIndicator(BuildContext context, AssetEntity asset) {
261
+    final zefyrTheme = ZefyrTheme.of(context).toolbarTheme;
262
+    if (isSingleAssetMode) {
263
+      return Container();
264
+    }
265
+    return Selector<DefaultAssetPickerProvider, List<AssetEntity>>(
266
+      selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
267
+          provider.selectedAssets,
268
+      builder: (BuildContext _, List<AssetEntity> selectedAssets, Widget __) {
269
+        final bool selected = selectedAssets.contains(asset);
270
+        final double indicatorSize = Screens.width / gridCount / 3;
271
+        return Positioned(
272
+          top: 0.0,
273
+          right: 0.0,
274
+          child: GestureDetector(
275
+            behavior: HitTestBehavior.opaque,
276
+            onTap: () {
277
+              if (selected) {
278
+                provider.unSelectAsset(asset);
279
+              } else {
280
+                if (isSingleAssetMode) {
281
+                  provider.selectedAssets.clear();
282
+                }
283
+                provider.selectAsset(asset);
284
+              }
285
+            },
286
+            child: Container(
287
+              margin: EdgeInsets.all(Screens.width / gridCount / 15.0),
288
+              width: indicatorSize,
289
+              height: indicatorSize,
290
+              alignment: AlignmentDirectional.topEnd,
291
+              child: AnimatedContainer(
292
+                duration: switchingPathDuration,
293
+                width: indicatorSize / 1.5,
294
+                height: indicatorSize / 1.5,
295
+                decoration: BoxDecoration(
296
+                  border: !selected
297
+                      ? Border.all(color: Colors.white, width: 2.0)
298
+                      : null,
299
+                  color: selected ? zefyrTheme.toggleColor : null,
300
+                  shape: BoxShape.circle,
301
+                ),
302
+                child: AnimatedSwitcher(
303
+                  duration: switchingPathDuration,
304
+                  reverseDuration: switchingPathDuration,
305
+                  child: selected
306
+                      ? isSingleAssetMode
307
+                          ? const Icon(Icons.check, size: 18.0)
308
+                          : Text(
309
+                              '${selectedAssets.indexOf(asset) + 1}',
310
+                              style: TextStyle(
311
+                                color: selected
312
+                                    ? Colors.white
313
+                                    : null,
314
+                                fontSize: 14.0,
315
+                                fontWeight: isAppleOS
316
+                                    ? FontWeight.w600
317
+                                    : FontWeight.bold,
318
+                              ),
319
+                            )
320
+                      : const SizedBox.shrink(),
321
+                ),
322
+              ),
323
+            ),
324
+          ),
325
+        );
326
+      },
327
+    );
328
+  }
329
+
330
+  @override
331
+  Widget selectedBackdrop(BuildContext context, int index, AssetEntity asset) {
332
+    return Selector<DefaultAssetPickerProvider, List<AssetEntity>>(
333
+      selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
334
+          provider.selectedAssets,
335
+      builder: (BuildContext _, List<AssetEntity> selectedAssets, Widget __) {
336
+        final bool selected = selectedAssets.contains(asset);
337
+        return Positioned.fill(
338
+          child: GestureDetector(
339
+            onTap: () async {
340
+              if (isSingleAssetMode) {
341
+                Navigator.of(context).pop(AssetPickerEntity(
342
+                  assets: [asset],
343
+                  isFullImage: (provider as ExtendedAssetPickerProvider).fullImage,
344
+                ));
345
+                return;
346
+              }
347
+              final List<AssetEntity> result =
348
+                  await ExtendedAssetPickerViewer.pushToViewer(
349
+                context,
350
+                currentIndex: index,
351
+                previewAssets: provider.currentAssets,
352
+                previewThumbSize: previewThumbSize,
353
+                selectedAssets: provider.selectedAssets,
354
+                selectorProvider: provider as ExtendedAssetPickerProvider,
355
+                themeData: theme,
356
+              );
357
+              if (result != null) {
358
+                Navigator.of(context).pop(AssetPickerEntity(
359
+                  assets: result,
360
+                  isFullImage: (provider as ExtendedAssetPickerProvider).fullImage,
361
+                ));
362
+              }
363
+            },
364
+            child: AnimatedContainer(
365
+              duration: switchingPathDuration,
366
+              color: selected
367
+                  ? Colors.black.withOpacity(0.45)
368
+                  : Colors.black.withOpacity(0.1),
369
+            ),
370
+          ), // 点击预览同目录下所有资源
371
+        );
372
+      },
373
+    );
374
+  }
375
+
376
+  /// Yes, the build method.
377
+  /// 没错,是它是它就是它,我们亲爱的 build 方法~
378
+  @override
379
+  Widget build(BuildContext context) {
380
+    final zefyrTheme = ZefyrTheme.of(context).toolbarTheme;
381
+    final toolbar = ZefyrToolbar.of(context);
382
+    return AnnotatedRegion<SystemUiOverlayStyle>(
383
+      value: overlayStyle,
384
+      child: Theme(
385
+        data: theme,
386
+        child: ChangeNotifierProvider<
387
+            AssetPickerProvider<AssetEntity, AssetPathEntity>>.value(
388
+          value: provider,
389
+          child: Material(
390
+            color: theme.canvasColor,
391
+            child: Container(
392
+              child: Column(
393
+                children: <Widget>[
394
+                  Expanded(
395
+                    child: Container(
396
+                      decoration: BoxDecoration(
397
+                        border: Border(
398
+                          top: BorderSide(color: theme.dividerColor, width: 1),
399
+                          bottom:
400
+                              BorderSide(color: theme.dividerColor, width: 1),
401
+                        ),
402
+                      ),
403
+                      child: Row(
404
+                        children: [
405
+                          Container(
406
+                            width: 58,
407
+                            child: Column(
408
+                              children: [
409
+                                Expanded(
410
+                                  child: FlatButton(
411
+                                      shape: RoundedRectangleBorder(),
412
+                                      color: zefyrTheme.captionColor,
413
+                                      padding: EdgeInsets.zero,
414
+                                      onPressed: () {
415
+                                        ExtendedAssetPicker.getImageFromCamera()
416
+                                            .then((result) {
417
+                                          if (result != null) {
418
+                                            toolbar.closeOverlay();
419
+                                            toolbar.editor.imageDelegate.picked(result, true);
420
+                                          }
421
+                                        });
422
+                                      },
423
+                                      child: Container(
424
+                                        child: Column(
425
+                                          mainAxisAlignment:
426
+                                              MainAxisAlignment.center,
427
+                                          children: [
428
+                                            Icon(
429
+                                              kDefaultButtonIcons[
430
+                                                  ZefyrToolbarAction
431
+                                                      .cameraImage],
432
+                                              size: 24,
433
+                                              color: Color(0xFFBFBFBF),
434
+                                            ),
435
+                                            Padding(
436
+                                              padding: EdgeInsets.only(top: 6),
437
+                                              child: Text(
438
+                                                (Constants.textDelegate
439
+                                                        as ExtendedAssetsPickerTextDelegate)
440
+                                                    .camera,
441
+                                                style: TextStyle(
442
+                                                  fontSize: 12,
443
+                                                  color: Color(0xFF8C8C8C),
444
+                                                ),
445
+                                              ),
446
+                                            )
447
+                                          ],
448
+                                        ),
449
+                                      )),
450
+                                ),
451
+                                Expanded(
452
+                                  child: FlatButton(
453
+                                      shape: RoundedRectangleBorder(),
454
+                                      color: zefyrTheme.captionColor,
455
+                                      padding: EdgeInsets.zero,
456
+                                      onPressed: () {
457
+                                        final editor =
458
+                                            ZefyrToolbar.of(context).editor;
459
+                                        // var _selection = toolbar.editor.selection;
460
+                                        editor.closeKeyboard(true);
461
+                                        ExtendedAssetPicker.pickAssets(context,
462
+                                                requestType: RequestType.image)
463
+                                            .then((result) async {
464
+                                          if (result != null) {
465
+                                            for (var asset in result.assets) {
466
+                                              var file = await asset.originFile;
467
+                                              final image = await editor
468
+                                                  .imageDelegate
469
+                                                  .picked(
470
+                                                      file, result.isFullImage);
471
+                                              if (image != null) {
472
+                                                editor.formatSelection(
473
+                                                    NotusAttribute.embed
474
+                                                        .image(image));
475
+                                                editor.closeKeyboard();
476
+                                              }
477
+                                            }
478
+                                          }
479
+                                        });
480
+                                      },
481
+                                      child: Container(
482
+                                        child: Column(
483
+                                          mainAxisAlignment:
484
+                                              MainAxisAlignment.center,
485
+                                          children: [
486
+                                            Icon(
487
+                                              kDefaultButtonIcons[
488
+                                                  ZefyrToolbarAction
489
+                                                      .galleryImage],
490
+                                              size: 24,
491
+                                              color: Color(0xFFBFBFBF),
492
+                                            ),
493
+                                            Padding(
494
+                                              padding: EdgeInsets.only(top: 6),
495
+                                              child: Text(
496
+                                                (Constants.textDelegate
497
+                                                        as ExtendedAssetsPickerTextDelegate)
498
+                                                    .album,
499
+                                                style: TextStyle(
500
+                                                  fontSize: 12,
501
+                                                  color: Color(0xFF8C8C8C),
502
+                                                ),
503
+                                              ),
504
+                                            )
505
+                                          ],
506
+                                        ),
507
+                                      )),
508
+                                ),
509
+                              ],
510
+                            ),
511
+                          ),
512
+                          Expanded(
513
+                            child: Listener(
514
+                              behavior: HitTestBehavior.translucent,
515
+                              onPointerUp: (PointerUpEvent event) {
516
+                                toolbar.editor.keepOverlay = true;
517
+                              },
518
+                              child: assetsListBuilder(context),
519
+                            ),
520
+                          ),
521
+                        ],
522
+                      ),
523
+                    ),
524
+                  ),
525
+                  bottomActionBar(context),
526
+                ],
527
+              ),
528
+            ),
529
+          ),
530
+        ),
531
+      ),
532
+    );
533
+  }
534
+}

+ 65
- 0
packages/zefyr/lib/src/extended_assets_picker/src/delegate/asset_picker_sort_delegate.dart View File

@@ -0,0 +1,65 @@
1
+part of ct_assets_picker;
2
+
3
+// TODO(lucky1213): 此处需修改翻译
4
+class CustomSortPathDelegate extends SortPathDelegate {
5
+  const CustomSortPathDelegate();
6
+
7
+  @override
8
+  void sort(List<AssetPathEntity> list) {
9
+    for (final AssetPathEntity entity in list) {
10
+      if (entity.isAll) {
11
+        entity.name = '图片和视频';
12
+      } else if (_isVideo(entity)) {
13
+        entity.name = '全部视频';
14
+      } else if (_isCamera(entity)) {
15
+        entity.name = '相机';
16
+      } else if (_isScreenShot(entity)) {
17
+        entity.name = '截图';
18
+      }
19
+    }
20
+    list.sort((AssetPathEntity path1, AssetPathEntity path2) {
21
+      if (path1.isAll) {
22
+        return -1;
23
+      }
24
+      if (path2.isAll) {
25
+        return 1;
26
+      }
27
+      if (_isCamera(path1)) {
28
+        return -1;
29
+      }
30
+      if (_isCamera(path2)) {
31
+        return 1;
32
+      }
33
+      if (_isVideo(path1)) {
34
+        return -1;
35
+      }
36
+      if (_isVideo(path2)) {
37
+        return 1;
38
+      }
39
+      if (_isScreenShot(path1)) {
40
+        return -1;
41
+      }
42
+      if (_isScreenShot(path2)) {
43
+        return 1;
44
+      }
45
+      return otherSort(path1, path2);
46
+    });
47
+  }
48
+
49
+  int otherSort(AssetPathEntity path1, AssetPathEntity path2) {
50
+    return path1.name.compareTo(path2.name);
51
+  }
52
+
53
+  bool _isCamera(AssetPathEntity entity) {
54
+    return entity.name.toUpperCase() == 'camera'.toUpperCase();
55
+  }
56
+
57
+  bool _isScreenShot(AssetPathEntity entity) {
58
+    return entity.name.toUpperCase() == 'screenshots'.toUpperCase() ||
59
+        entity.name.toUpperCase() == 'screenshot'.toUpperCase();
60
+  }
61
+
62
+  bool _isVideo(AssetPathEntity entity) {
63
+    return entity.name.toUpperCase() == 'Videos'.toUpperCase();
64
+  }
65
+}

+ 132
- 0
packages/zefyr/lib/src/extended_assets_picker/src/delegate/asset_picker_text_delegate.dart View File

@@ -0,0 +1,132 @@
1
+part of ct_assets_picker;
2
+
3
+/// Default text delegate implements with Chinese.
4
+/// 中文文字实现
5
+abstract class ExtendedAssetsPickerTextDelegate
6
+    extends AssetsPickerTextDelegate {
7
+  String camera;
8
+
9
+  String album;
10
+}
11
+
12
+/// Default text delegate implements with Chinese.
13
+/// 中文文字实现
14
+class ExtendedChineseTextDelegate implements ExtendedAssetsPickerTextDelegate {
15
+  factory ExtendedChineseTextDelegate() => _instance;
16
+
17
+  ExtendedChineseTextDelegate._internal();
18
+
19
+  static final ExtendedChineseTextDelegate _instance =
20
+      ExtendedChineseTextDelegate._internal();
21
+
22
+  @override
23
+  String camera = '拍照';
24
+
25
+  @override
26
+  String album = '相册';
27
+
28
+  @override
29
+  String confirm = '上传';
30
+
31
+  @override
32
+  String cancel = '取消';
33
+
34
+  @override
35
+  String edit = '编辑';
36
+
37
+  @override
38
+  String emptyPlaceHolder = '这里空空如也';
39
+
40
+  @override
41
+  String gifIndicator = 'GIF';
42
+
43
+  @override
44
+  String heicNotSupported = '尚未支持HEIC类型资源';
45
+
46
+  @override
47
+  String loadFailed = '加载失败';
48
+
49
+  @override
50
+  String original = '原图';
51
+
52
+  @override
53
+  String preview = '预览';
54
+
55
+  @override
56
+  String select = '选择';
57
+
58
+  @override
59
+  String unSupportedAssetType = '尚未支持的资源类型';
60
+
61
+  @override
62
+  String durationIndicatorBuilder(Duration duration) {
63
+    const String separator = ':';
64
+    final String minute = duration.inMinutes.toString().padLeft(2, '0');
65
+    final String second =
66
+        ((duration - Duration(minutes: duration.inMinutes)).inSeconds)
67
+            .toString()
68
+            .padLeft(2, '0');
69
+    return '$minute$separator$second';
70
+  }
71
+}
72
+
73
+/// [AssetsPickerTextDelegate] implements with English.
74
+/// 英文文字实现
75
+class ExtendedEnglishTextDelegate implements ExtendedAssetsPickerTextDelegate {
76
+  factory ExtendedEnglishTextDelegate() => _instance;
77
+
78
+  ExtendedEnglishTextDelegate._internal();
79
+
80
+  static final ExtendedEnglishTextDelegate _instance =
81
+      ExtendedEnglishTextDelegate._internal();
82
+
83
+  @override
84
+  String camera = 'Camera';
85
+
86
+  @override
87
+  String album = 'Album';
88
+
89
+  @override
90
+  String confirm = 'Upload';
91
+
92
+  @override
93
+  String cancel = 'Cancel';
94
+
95
+  @override
96
+  String edit = 'Edit';
97
+
98
+  @override
99
+  String emptyPlaceHolder = 'Nothing here...';
100
+
101
+  @override
102
+  String gifIndicator = 'GIF';
103
+
104
+  @override
105
+  String heicNotSupported = 'Unsupported HEIC asset type.';
106
+
107
+  @override
108
+  String loadFailed = 'Load failed';
109
+
110
+  @override
111
+  String original = 'Full Image';
112
+
113
+  @override
114
+  String preview = 'Preview';
115
+
116
+  @override
117
+  String select = 'Select';
118
+
119
+  @override
120
+  String unSupportedAssetType = 'Unsupported HEIC asset type.';
121
+
122
+  @override
123
+  String durationIndicatorBuilder(Duration duration) {
124
+    const String separator = ':';
125
+    final String minute = duration.inMinutes.toString().padLeft(2, '0');
126
+    final String second =
127
+        ((duration - Duration(minutes: duration.inMinutes)).inSeconds)
128
+            .toString()
129
+            .padLeft(2, '0');
130
+    return '$minute$separator$second';
131
+  }
132
+}

+ 520
- 0
packages/zefyr/lib/src/extended_assets_picker/src/delegate/asset_picker_viewer_builder_delegate.dart View File

@@ -0,0 +1,520 @@
1
+part of ct_assets_picker;
2
+
3
+class ExtendedAssetPickerViewerBuilderDelegate
4
+    extends DefaultAssetPickerViewerBuilderDelegate {
5
+  ExtendedAssetPickerViewerBuilderDelegate({
6
+    @required int currentIndex,
7
+    @required List<AssetEntity> previewAssets,
8
+    @required AssetPickerViewerProvider<AssetEntity> provider,
9
+    @required ThemeData themeData,
10
+    List<AssetEntity> selectedAssets,
11
+    AssetPickerProvider<AssetEntity, AssetPathEntity> selectorProvider,
12
+    List<int> previewThumbSize,
13
+    SpecialPickerType specialPickerType,
14
+  }) : super(
15
+          currentIndex: currentIndex,
16
+          previewAssets: previewAssets,
17
+          provider: provider,
18
+          themeData: themeData,
19
+          selectedAssets: selectedAssets,
20
+          selectorProvider: selectorProvider,
21
+          previewThumbSize: previewThumbSize,
22
+          specialPickerType: specialPickerType,
23
+        );
24
+
25
+  /// Whether the current platform is Apple OS.
26
+  /// 当前平台是否为苹果系列系统
27
+  @override
28
+  bool get isAppleOS => false;
29
+
30
+  /// [Duration] when triggering path switching.
31
+  /// 切换路径时的动画时长
32
+  Duration get switchingPathDuration => kThemeAnimationDuration * 1.5;
33
+
34
+  /// [Curve] when triggering path switching.
35
+  /// 切换路径时的动画曲线
36
+  Curve get switchingPathCurve => Curves.easeInOut;
37
+
38
+  /// AppBar widget.
39
+  /// 顶栏部件
40
+  @override
41
+  Widget appBar(BuildContext context) {
42
+    return AnimatedPositioned(
43
+      duration: kThemeAnimationDuration,
44
+      curve: Curves.easeInOut,
45
+      top: isDisplayingDetail ? 0.0 : -(Screens.topSafeHeight + kToolbarHeight),
46
+      left: 0.0,
47
+      right: 0.0,
48
+      height: Screens.topSafeHeight + kToolbarHeight,
49
+      child: Container(
50
+        padding: EdgeInsets.only(top: Screens.topSafeHeight, right: 12.0),
51
+        color: themeData.canvasColor.withOpacity(0.85),
52
+        child: Row(
53
+          children: <Widget>[
54
+            const BackButton(),
55
+            if (!isAppleOS && specialPickerType == null)
56
+              StreamBuilder<int>(
57
+                initialData: currentIndex,
58
+                stream: pageStreamController.stream,
59
+                builder: (BuildContext _, AsyncSnapshot<int> snapshot) {
60
+                  if (previewAssets.isEmpty) {
61
+                    return Container();
62
+                  }
63
+                  return Text(
64
+                    '${snapshot.data + 1}/${previewAssets.length}',
65
+                    style: const TextStyle(
66
+                      fontSize: 18.0,
67
+                      fontWeight: FontWeight.bold,
68
+                    ),
69
+                  );
70
+                },
71
+              ),
72
+            const Spacer(),
73
+            if (isAppleOS && provider != null) selectButton(context),
74
+            if (!isAppleOS && provider != null ||
75
+                specialPickerType == SpecialPickerType.wechatMoment)
76
+              confirmButton(context),
77
+          ],
78
+        ),
79
+      ),
80
+    );
81
+  }
82
+
83
+  /// Select button for apple OS.
84
+  /// 苹果系列系统的选择按钮
85
+  Widget _appleOSSelectButton(bool isSelected, AssetEntity asset) {
86
+    return Padding(
87
+      padding: const EdgeInsets.only(right: 10.0),
88
+      child: GestureDetector(
89
+        behavior: HitTestBehavior.opaque,
90
+        onTap: () {
91
+          if (isSelected) {
92
+            provider.unSelectAssetEntity(asset);
93
+          } else {
94
+            provider.selectAssetEntity(asset);
95
+          }
96
+        },
97
+        child: AnimatedContainer(
98
+          duration: kThemeAnimationDuration,
99
+          width: 28.0,
100
+          decoration: BoxDecoration(
101
+            border: !isSelected
102
+                ? Border.all(
103
+                    color: themeData.iconTheme.color,
104
+                  )
105
+                : null,
106
+            color: isSelected ? themeData.buttonColor : null,
107
+            shape: BoxShape.circle,
108
+          ),
109
+          child: Center(
110
+            child: isSelected
111
+                ? Text(
112
+                    (currentIndex + 1).toString(),
113
+                    style: const TextStyle(
114
+                      fontSize: 16.0,
115
+                      fontWeight: FontWeight.bold,
116
+                    ),
117
+                  )
118
+                : const Icon(Icons.check, size: 20.0),
119
+          ),
120
+        ),
121
+      ),
122
+    );
123
+  }
124
+
125
+  /// Select button for Android.
126
+  /// 安卓系统的选择按钮
127
+  Widget _androidSelectButton(bool isSelected, AssetEntity asset) {
128
+    return RoundedCheckbox(
129
+      value: isSelected,
130
+      onChanged: (bool value) {
131
+        if (isSelected) {
132
+          provider.unSelectAssetEntity(asset);
133
+        } else {
134
+          provider.selectAssetEntity(asset);
135
+        }
136
+      },
137
+      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
138
+    );
139
+  }
140
+
141
+  @override
142
+  Widget selectButton(BuildContext context) {
143
+    return Row(
144
+      children: <Widget>[
145
+        StreamBuilder<int>(
146
+          initialData: currentIndex,
147
+          stream: pageStreamController.stream,
148
+          builder: (BuildContext _, AsyncSnapshot<int> snapshot) {
149
+            return ChangeNotifierProvider<
150
+                AssetPickerViewerProvider<AssetEntity>>.value(
151
+              value: provider,
152
+              child: Selector<AssetPickerViewerProvider<AssetEntity>,
153
+                  List<AssetEntity>>(
154
+                selector: (
155
+                  BuildContext _,
156
+                  AssetPickerViewerProvider<AssetEntity> provider,
157
+                ) =>
158
+                    provider.currentlySelectedAssets,
159
+                builder: (
160
+                  BuildContext _,
161
+                  List<AssetEntity> currentlySelectedAssets,
162
+                  Widget __,
163
+                ) {
164
+                  final AssetEntity asset =
165
+                      previewAssets.elementAt(snapshot.data);
166
+                  final bool isSelected =
167
+                      currentlySelectedAssets.contains(asset);
168
+                  if (isAppleOS) {
169
+                    return _appleOSSelectButton(isSelected, asset);
170
+                  } else {
171
+                    return _androidSelectButton(isSelected, asset);
172
+                  }
173
+                },
174
+              ),
175
+            );
176
+          },
177
+        ),
178
+        if (!isAppleOS)
179
+          Text(
180
+            Constants.textDelegate.select,
181
+            style: const TextStyle(fontSize: 18.0),
182
+          ),
183
+      ],
184
+    );
185
+  }
186
+
187
+  /// It'll pop with [AssetPickerProvider.selectedAssets] when there're any
188
+  /// assets chosen. The [PhotoSelector] will recognize and pop too.
189
+  /// 当有资源已选时,点击按钮将把已选资源通过路由返回。
190
+  /// 资源选择器将识别并一同返回。
191
+  @override
192
+  Widget confirmButton(BuildContext context) {
193
+    return ChangeNotifierProvider<AssetPickerViewerProvider<AssetEntity>>.value(
194
+      value: provider,
195
+      child: Consumer<AssetPickerViewerProvider<AssetEntity>>(
196
+        builder: (
197
+          BuildContext _,
198
+          AssetPickerViewerProvider<AssetEntity> provider,
199
+          Widget __,
200
+        ) {
201
+          return FlatButton(
202
+            minWidth: () {
203
+              if (specialPickerType == SpecialPickerType.wechatMoment) {
204
+                return 48.0;
205
+              }
206
+              return provider.isSelectedNotEmpty ? 48.0 : 20.0;
207
+            }(),
208
+            height: 32.0,
209
+            padding: const EdgeInsets.symmetric(horizontal: 12.0),
210
+            color: () {
211
+              if (specialPickerType == SpecialPickerType.wechatMoment) {
212
+                return themeData.colorScheme.secondary;
213
+              }
214
+              return provider.isSelectedNotEmpty
215
+                  ? themeData.colorScheme.primary
216
+                  : themeData.dividerColor;
217
+            }(),
218
+            shape: RoundedRectangleBorder(
219
+              borderRadius: BorderRadius.circular(3.0),
220
+            ),
221
+            child: Text(
222
+              () {
223
+                if (specialPickerType == SpecialPickerType.wechatMoment) {
224
+                  return Constants.textDelegate.confirm;
225
+                }
226
+                if (provider.isSelectedNotEmpty) {
227
+                  return '${Constants.textDelegate.confirm}'
228
+                      '(${provider.currentlySelectedAssets.length}'
229
+                      '/'
230
+                      '${selectorProvider.maxAssets})';
231
+                }
232
+                return Constants.textDelegate.confirm;
233
+              }(),
234
+              style: TextStyle(
235
+                color: () {
236
+                  if (specialPickerType == SpecialPickerType.wechatMoment) {
237
+                    return themeData.textTheme.bodyText1.color;
238
+                  }
239
+                  return provider.isSelectedNotEmpty
240
+                      ? themeData.colorScheme.onPrimary
241
+                      : themeData.textTheme.caption.color;
242
+                }(),
243
+                fontSize: 17.0,
244
+                fontWeight: FontWeight.normal,
245
+              ),
246
+            ),
247
+            onPressed: () {
248
+              if (specialPickerType == SpecialPickerType.wechatMoment) {
249
+                Navigator.of(context).pop(<AssetEntity>[currentAsset]);
250
+                return;
251
+              }
252
+              if (provider.isSelectedNotEmpty) {
253
+                Navigator.of(context).pop(provider.currentlySelectedAssets);
254
+              }
255
+            },
256
+            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
257
+          );
258
+        },
259
+      ),
260
+    );
261
+  }
262
+
263
+  /// Preview item widgets for audios.
264
+  /// 音频的底部预览部件
265
+  Widget _audioPreviewItem(AssetEntity asset) {
266
+    return ColoredBox(
267
+      color: themeData?.dividerColor,
268
+      child: const Center(child: Icon(Icons.audiotrack)),
269
+    );
270
+  }
271
+
272
+  /// Preview item widgets for images.
273
+  /// 音频的底部预览部件
274
+  Widget _imagePreviewItem(AssetEntity asset) {
275
+    return Positioned.fill(
276
+      child: RepaintBoundary(
277
+        child: ExtendedImage(
278
+          image: AssetEntityImageProvider(
279
+            asset,
280
+            isOriginal: false,
281
+          ),
282
+          fit: BoxFit.cover,
283
+        ),
284
+      ),
285
+    );
286
+  }
287
+
288
+  /// Preview item widgets for video.
289
+  /// 音频的底部预览部件
290
+  Widget _videoPreviewItem(AssetEntity asset) {
291
+    return Positioned.fill(
292
+      child: Stack(
293
+        children: <Widget>[
294
+          _imagePreviewItem(asset),
295
+          Center(
296
+            child: Icon(
297
+              Icons.video_library,
298
+              color: themeData.colorScheme.surface.withOpacity(0.54),
299
+            ),
300
+          ),
301
+        ],
302
+      ),
303
+    );
304
+  }
305
+
306
+  Widget fullImageButton(BuildContext context) {
307
+    return Selector<DefaultAssetPickerProvider, bool>(
308
+      selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
309
+          (provider as ExtendedAssetPickerProvider).fullImage,
310
+      builder: (BuildContext _, bool fullImage, Widget __) {
311
+        return GestureDetector(
312
+          onTap: () {
313
+            (selectorProvider as ExtendedAssetPickerProvider).fullImage = !fullImage;
314
+          },
315
+          child: Row(
316
+            mainAxisSize: MainAxisSize.min,
317
+            mainAxisAlignment: MainAxisAlignment.center,
318
+            children: [
319
+              Container(
320
+                // margin: EdgeInsets.all(Screens.width / gridCount / 15.0),
321
+                width: 18,
322
+                height: 18,
323
+                alignment: Alignment.center,
324
+                child: AnimatedContainer(
325
+                  duration: switchingPathDuration,
326
+                  width: 18,
327
+                  height: 18,
328
+                  decoration: BoxDecoration(
329
+                    border: Border.all(
330
+                        color: fullImage
331
+                            ? themeData.colorScheme.primary
332
+                            : themeData.unselectedWidgetColor,
333
+                        width: 2.0),
334
+                    color: null,
335
+                    shape: BoxShape.circle,
336
+                  ),
337
+                  child: AnimatedSwitcher(
338
+                    duration: switchingPathDuration,
339
+                    reverseDuration: switchingPathDuration,
340
+                    child: fullImage
341
+                        ? Container(
342
+                            width: 10,
343
+                            height: 10,
344
+                            decoration: BoxDecoration(
345
+                              color: themeData.colorScheme.primary,
346
+                              shape: BoxShape.circle,
347
+                            ),
348
+                          )
349
+                        : const SizedBox.shrink(),
350
+                  ),
351
+                ),
352
+              ),
353
+              SizedBox(
354
+                width: 8,
355
+              ),
356
+              Text(
357
+                Constants.textDelegate.original,
358
+                style: TextStyle(
359
+                  color: themeData.textTheme.caption.color,
360
+                  fontSize: 18.0,
361
+                ),
362
+              ),
363
+            ],
364
+          ),
365
+        );
366
+      },
367
+    );
368
+  }
369
+
370
+  @override
371
+  Widget bottomDetailBuilder(BuildContext context) {
372
+    return AnimatedPositioned(
373
+      duration: kThemeAnimationDuration,
374
+      curve: Curves.easeInOut,
375
+      bottom: isDisplayingDetail
376
+          ? 0.0
377
+          : -(Screens.bottomSafeHeight + bottomDetailHeight),
378
+      left: 0.0,
379
+      right: 0.0,
380
+      height: Screens.bottomSafeHeight + bottomDetailHeight,
381
+      child: Container(
382
+        padding: EdgeInsets.only(bottom: Screens.bottomSafeHeight),
383
+        color: themeData.canvasColor.withOpacity(0.85),
384
+        child: Column(
385
+          children: <Widget>[
386
+            ChangeNotifierProvider<
387
+                AssetPickerViewerProvider<AssetEntity>>.value(
388
+              value: provider,
389
+              child: SizedBox(
390
+                height: 90.0,
391
+                child: Selector<AssetPickerViewerProvider<AssetEntity>,
392
+                  List<AssetEntity>>(
393
+                selector: (
394
+                  BuildContext _,
395
+                  AssetPickerViewerProvider<AssetEntity> provider,
396
+                ) =>
397
+                    provider.currentlySelectedAssets,
398
+                builder: (
399
+                  BuildContext _,
400
+                  List<AssetEntity> currentlySelectedAssets,
401
+                  Widget __,
402
+                ) {
403
+                    return ListView.builder(
404
+                      scrollDirection: Axis.horizontal,
405
+                      padding: const EdgeInsets.symmetric(horizontal: 5.0),
406
+                      itemCount: currentlySelectedAssets.length,
407
+                      itemBuilder: bottomDetailItemBuilder,
408
+                    );
409
+                  },
410
+                ),
411
+              ),
412
+            ),
413
+            Container(
414
+              height: 1.0,
415
+              color: themeData.dividerColor,
416
+            ),
417
+            Expanded(
418
+              child: Padding(
419
+                padding: const EdgeInsets.symmetric(horizontal: 20.0),
420
+                child: Row(
421
+                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
422
+                  children: <Widget>[
423
+                    // const Spacer(),
424
+                    fullImageButton(context),
425
+                    if (isAppleOS && provider != null)
426
+                      ChangeNotifierProvider<
427
+                          AssetPickerViewerProvider<AssetEntity>>.value(
428
+                        value: provider,
429
+                        child: confirmButton(context),
430
+                      )
431
+                    else
432
+                      selectButton(context),
433
+                  ],
434
+                ),
435
+              ),
436
+            ),
437
+          ],
438
+        ),
439
+      ),
440
+    );
441
+  }
442
+
443
+  @override
444
+  Widget bottomDetailItemBuilder(BuildContext context, int index) {
445
+    return Padding(
446
+      padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
447
+      child: AspectRatio(
448
+        aspectRatio: 1.0,
449
+        child: StreamBuilder<int>(
450
+          initialData: currentIndex,
451
+          stream: pageStreamController.stream,
452
+          builder: (BuildContext _, AsyncSnapshot<int> snapshot) {
453
+            final AssetEntity asset = provider.currentlySelectedAssets.elementAt(index);
454
+            final bool isViewing = asset == currentAsset;
455
+            return GestureDetector(
456
+              onTap: () {
457
+                if (!isViewing) {
458
+                  pageController.jumpToPage(previewAssets.indexWhere((e) => e.id == asset.id));
459
+                }
460
+              },
461
+              child: Selector<AssetPickerViewerProvider<AssetEntity>,
462
+                  List<AssetEntity>>(
463
+                selector: (
464
+                  BuildContext _,
465
+                  AssetPickerViewerProvider<AssetEntity> provider,
466
+                ) =>
467
+                    provider.currentlySelectedAssets,
468
+                builder: (
469
+                  BuildContext _,
470
+                  List<AssetEntity> currentlySelectedAssets,
471
+                  Widget __,
472
+                ) {
473
+                  final bool isSelected =
474
+                      currentlySelectedAssets.contains(asset);
475
+                  return Stack(
476
+                    children: <Widget>[
477
+                      () {
478
+                        Widget item;
479
+                        switch (asset.type) {
480
+                          case AssetType.other:
481
+                            item = const SizedBox.shrink();
482
+                            break;
483
+                          case AssetType.image:
484
+                            item = _imagePreviewItem(asset);
485
+                            break;
486
+                          case AssetType.video:
487
+                            item = _videoPreviewItem(asset);
488
+                            break;
489
+                          case AssetType.audio:
490
+                            item = _audioPreviewItem(asset);
491
+                            break;
492
+                        }
493
+                        return item;
494
+                      }(),
495
+                      AnimatedContainer(
496
+                        duration: kThemeAnimationDuration,
497
+                        curve: Curves.easeInOut,
498
+                        decoration: BoxDecoration(
499
+                          border: isViewing
500
+                              ? Border.all(
501
+                                  color: themeData.colorScheme.primary,
502
+                                  width: 2.0,
503
+                                )
504
+                              : null,
505
+                          color: isSelected
506
+                              ? null
507
+                              : themeData.colorScheme.surface.withOpacity(0.54),
508
+                        ),
509
+                      ),
510
+                    ],
511
+                  );
512
+                },
513
+              ),
514
+            );
515
+          },
516
+        ),
517
+      ),
518
+    );
519
+  }
520
+}

+ 10
- 0
packages/zefyr/lib/src/extended_assets_picker/src/entity/asset_picker_entity.dart View File

@@ -0,0 +1,10 @@
1
+part of ct_assets_picker;
2
+
3
+class AssetPickerEntity {
4
+  const AssetPickerEntity({
5
+    @required this.assets,
6
+    @required this.isFullImage,
7
+  });
8
+  final List<AssetEntity> assets;
9
+  final bool isFullImage;
10
+}

+ 37
- 0
packages/zefyr/lib/src/extended_assets_picker/src/provider/asset_picker_provider.dart View File

@@ -0,0 +1,37 @@
1
+part of ct_assets_picker;
2
+
3
+class ExtendedAssetPickerProvider extends DefaultAssetPickerProvider {
4
+  ExtendedAssetPickerProvider({
5
+    RequestType requestType = RequestType.image,
6
+    SortPathDelegate sortPathDelegate = SortPathDelegate.common,
7
+    FilterOptionGroup filterOptions,
8
+    int maxAssets = 9,
9
+    int pageSize = 320,
10
+    int pathThumbSize = 80,
11
+    List<AssetEntity> selectedAssets,
12
+    Duration routeDuration,
13
+  }) : _fullImage = maxAssets == 1, super(
14
+    requestType: requestType,
15
+    sortPathDelegate: sortPathDelegate,
16
+    filterOptions: filterOptions,
17
+    maxAssets: maxAssets,
18
+    pageSize: pageSize,
19
+    pathThumbSize: pathThumbSize,
20
+    selectedAssets: selectedAssets,
21
+    routeDuration: routeDuration,
22
+  );
23
+
24
+  /// 原图
25
+  bool _fullImage;
26
+
27
+  bool get fullImage => _fullImage;
28
+
29
+  set fullImage(bool value) {
30
+    assert(value != null);
31
+    if (value == _fullImage) {
32
+      return;
33
+    }
34
+    _fullImage = value;
35
+    notifyListeners();
36
+  }
37
+}

+ 64
- 0
packages/zefyr/lib/src/extended_assets_picker/src/widget/asset_picker_viewer.dart View File

@@ -0,0 +1,64 @@
1
+part of ct_assets_picker;
2
+
3
+class ExtendedAssetPickerViewer<A, P> extends AssetPickerViewer<A, P> {
4
+  const ExtendedAssetPickerViewer({
5
+    Key key,
6
+    @required AssetPickerViewerBuilderDelegate<A, P> builder,
7
+  }) : super(key: key, builder: builder);
8
+
9
+  static Future<List<AssetEntity>> pushToViewer(
10
+    BuildContext context, {
11
+    int currentIndex = 0,
12
+    @required List<AssetEntity> previewAssets,
13
+    @required ThemeData themeData,
14
+    List<int> previewThumbSize,
15
+    List<AssetEntity> selectedAssets,
16
+    DefaultAssetPickerProvider selectorProvider,
17
+    SpecialPickerType specialPickerType,
18
+  }) async {
19
+    try {
20
+      final Widget viewer =
21
+          ChangeNotifierProvider<DefaultAssetPickerProvider>.value(
22
+        value: selectorProvider,
23
+        child: AssetPickerViewer<AssetEntity, AssetPathEntity>(
24
+          builder: ExtendedAssetPickerViewerBuilderDelegate(
25
+            currentIndex: currentIndex,
26
+            previewAssets: previewAssets,
27
+            provider: selectedAssets != null
28
+                ? AssetPickerViewerProvider<AssetEntity>(selectedAssets)
29
+                : null,
30
+            themeData: themeData,
31
+            previewThumbSize: previewThumbSize,
32
+            specialPickerType: specialPickerType,
33
+            selectedAssets: selectedAssets,
34
+            selectorProvider: selectorProvider,
35
+          ),
36
+        ),
37
+      );
38
+      final PageRouteBuilder<List<AssetEntity>> pageRoute =
39
+          PageRouteBuilder<List<AssetEntity>>(
40
+        pageBuilder: (
41
+          BuildContext context,
42
+          Animation<double> animation,
43
+          Animation<double> secondaryAnimation,
44
+        ) {
45
+          return viewer;
46
+        },
47
+        transitionsBuilder: (
48
+          BuildContext context,
49
+          Animation<double> animation,
50
+          Animation<double> secondaryAnimation,
51
+          Widget child,
52
+        ) {
53
+          return FadeTransition(opacity: animation, child: child);
54
+        },
55
+      );
56
+      final List<AssetEntity> result =
57
+          await Navigator.of(context).push<List<AssetEntity>>(pageRoute);
58
+      return result;
59
+    } catch (e) {
60
+      realDebugPrint('Error when calling assets picker viewer: $e');
61
+      return null;
62
+    }
63
+  }
64
+}

+ 283
- 0
packages/zefyr/lib/src/extended_assets_picker/src/widget/rounded_check_box.dart View File

@@ -0,0 +1,283 @@
1
+part of ct_assets_picker;
2
+
3
+class RoundedCheckbox extends StatefulWidget {
4
+  const RoundedCheckbox({
5
+    Key key,
6
+    @required this.value,
7
+    this.tristate = false,
8
+    @required this.onChanged,
9
+    this.activeColor,
10
+    this.inactiveColor,
11
+    this.checkColor,
12
+    this.materialTapTargetSize,
13
+  })  : assert(tristate != null),
14
+        assert(tristate || value != null),
15
+        super(key: key);
16
+
17
+  final bool value;
18
+
19
+  final ValueChanged<bool> onChanged;
20
+
21
+  final Color activeColor;
22
+
23
+  final Color inactiveColor;
24
+
25
+  final Color checkColor;
26
+
27
+  final bool tristate;
28
+
29
+  final MaterialTapTargetSize materialTapTargetSize;
30
+
31
+  static const double width = 18.0;
32
+
33
+  @override
34
+  _RoundedCheckboxState createState() => _RoundedCheckboxState();
35
+}
36
+
37
+class _RoundedCheckboxState extends State<RoundedCheckbox>
38
+    with TickerProviderStateMixin {
39
+  @override
40
+  Widget build(BuildContext context) {
41
+    assert(debugCheckHasMaterial(context));
42
+    final ThemeData themeData = Theme.of(context);
43
+    Size size;
44
+    switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) {
45
+      case MaterialTapTargetSize.padded:
46
+        size = const Size(
47
+            2 * kRadialReactionRadius + 8.0, 2 * kRadialReactionRadius + 8.0);
48
+        break;
49
+      case MaterialTapTargetSize.shrinkWrap:
50
+        size = const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius);
51
+        break;
52
+    }
53
+    final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
54
+    return _CheckboxRenderObjectWidget(
55
+      value: widget.value,
56
+      tristate: widget.tristate,
57
+      activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
58
+      checkColor: widget.checkColor ?? const Color(0xFFFFFFFF),
59
+      inactiveColor: widget.inactiveColor ??
60
+          (widget.onChanged != null
61
+              ? themeData.unselectedWidgetColor
62
+              : themeData.disabledColor),
63
+      onChanged: widget.onChanged,
64
+      additionalConstraints: additionalConstraints,
65
+      vsync: this,
66
+    );
67
+  }
68
+}
69
+
70
+class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
71
+  const _CheckboxRenderObjectWidget({
72
+    Key key,
73
+    @required this.value,
74
+    @required this.tristate,
75
+    @required this.activeColor,
76
+    @required this.checkColor,
77
+    @required this.inactiveColor,
78
+    @required this.onChanged,
79
+    @required this.vsync,
80
+    @required this.additionalConstraints,
81
+  })  : assert(tristate != null),
82
+        assert(tristate || value != null),
83
+        assert(activeColor != null),
84
+        assert(inactiveColor != null),
85
+        assert(vsync != null),
86
+        super(key: key);
87
+
88
+  final bool value;
89
+  final bool tristate;
90
+  final Color activeColor;
91
+  final Color checkColor;
92
+  final Color inactiveColor;
93
+  final ValueChanged<bool> onChanged;
94
+  final TickerProvider vsync;
95
+  final BoxConstraints additionalConstraints;
96
+
97
+  @override
98
+  _RenderCheckbox createRenderObject(BuildContext context) => _RenderCheckbox(
99
+        value: value,
100
+        tristate: tristate,
101
+        activeColor: activeColor,
102
+        checkColor: checkColor,
103
+        inactiveColor: inactiveColor,
104
+        onChanged: onChanged,
105
+        vsync: vsync,
106
+        additionalConstraints: additionalConstraints,
107
+      );
108
+
109
+  @override
110
+  void updateRenderObject(BuildContext context, _RenderCheckbox renderObject) {
111
+    renderObject
112
+      ..value = value
113
+      ..tristate = tristate
114
+      ..activeColor = activeColor
115
+      ..checkColor = checkColor
116
+      ..inactiveColor = inactiveColor
117
+      ..onChanged = onChanged
118
+      ..additionalConstraints = additionalConstraints
119
+      ..vsync = vsync;
120
+  }
121
+}
122
+
123
+const double _kEdgeSize = RoundedCheckbox.width;
124
+const Radius _kEdgeRadius = Radius.circular(123546);
125
+const double _kStrokeWidth = 2.0;
126
+
127
+class _RenderCheckbox extends RenderToggleable {
128
+  _RenderCheckbox({
129
+    bool value,
130
+    bool tristate,
131
+    Color activeColor,
132
+    this.checkColor,
133
+    Color inactiveColor,
134
+    BoxConstraints additionalConstraints,
135
+    ValueChanged<bool> onChanged,
136
+    @required TickerProvider vsync,
137
+  })  : _oldValue = value,
138
+        super(
139
+          value: value,
140
+          tristate: tristate,
141
+          activeColor: activeColor,
142
+          inactiveColor: inactiveColor,
143
+          onChanged: onChanged,
144
+          additionalConstraints: additionalConstraints,
145
+          vsync: vsync,
146
+        );
147
+
148
+  bool _oldValue;
149
+  Color checkColor;
150
+
151
+  @override
152
+  set value(bool newValue) {
153
+    if (newValue == value) {
154
+      return;
155
+    }
156
+    _oldValue = value;
157
+    super.value = newValue;
158
+  }
159
+
160
+  @override
161
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
162
+    super.describeSemanticsConfiguration(config);
163
+    config.isChecked = value == true;
164
+  }
165
+
166
+  RRect _outerRectAt(Offset origin, double t) {
167
+    final double inset = 1.0 - (t - 0.5).abs() * 2.0;
168
+    final double size = _kEdgeSize - inset * _kStrokeWidth;
169
+    final Rect rect =
170
+        Rect.fromLTWH(origin.dx + inset, origin.dy + inset, size, size);
171
+    return RRect.fromRectAndRadius(rect, _kEdgeRadius);
172
+  }
173
+
174
+  Color _colorAt(double t) {
175
+    return onChanged == null
176
+        ? inactiveColor
177
+        : (t >= 0.25
178
+            ? activeColor
179
+            : Color.lerp(inactiveColor, activeColor, t * 4.0));
180
+  }
181
+
182
+  void _initStrokePaint(Paint paint) {
183
+    paint
184
+      ..color = checkColor
185
+      ..style = PaintingStyle.stroke
186
+      ..strokeWidth = _kStrokeWidth;
187
+  }
188
+
189
+  void _drawBorder(Canvas canvas, RRect outer, double t, Paint paint) {
190
+    assert(t >= 0.0 && t <= 0.5);
191
+    final double size = outer.width;
192
+    final RRect inner =
193
+        outer.deflate(math.min(size / 2.0, _kStrokeWidth + size * t));
194
+    canvas.drawDRRect(outer, inner, paint);
195
+  }
196
+
197
+  void _drawCheck(Canvas canvas, Offset origin, double t, Paint paint) {
198
+    assert(t >= 0.0 && t <= 1.0);
199
+    final Path path = Path();
200
+    const double size = _kEdgeSize - 2;
201
+    const Offset start = Offset(size * 0.2, size * 0.55);
202
+    const Offset mid = Offset(size * 0.45, size * 0.8);
203
+    const Offset end = Offset(size * 0.9, size * 0.35);
204
+    if (t < 0.5) {
205
+      final double strokeT = t * 2.0;
206
+      final Offset drawMid = Offset.lerp(start, mid, strokeT);
207
+      path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
208
+      path.lineTo(origin.dx + drawMid.dx, origin.dy + drawMid.dy);
209
+    } else {
210
+      final double strokeT = (t - 0.5) * 2.0;
211
+      final Offset drawEnd = Offset.lerp(mid, end, strokeT);
212
+      path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
213
+      path.lineTo(origin.dx + mid.dx, origin.dy + mid.dy);
214
+      path.lineTo(origin.dx + drawEnd.dx, origin.dy + drawEnd.dy);
215
+    }
216
+    canvas.drawPath(path, paint);
217
+  }
218
+
219
+  void _drawDash(Canvas canvas, Offset origin, double t, Paint paint) {
220
+    assert(t >= 0.0 && t <= 1.0);
221
+    const Offset start = Offset(_kEdgeSize * 0.2, _kEdgeSize * 0.5);
222
+    const Offset mid = Offset(_kEdgeSize * 0.5, _kEdgeSize * 0.5);
223
+    const Offset end = Offset(_kEdgeSize * 0.8, _kEdgeSize * 0.5);
224
+    final Offset drawStart = Offset.lerp(start, mid, 1.0 - t);
225
+    final Offset drawEnd = Offset.lerp(mid, end, t);
226
+    canvas.drawLine(origin + drawStart, origin + drawEnd, paint);
227
+  }
228
+
229
+  @override
230
+  void paint(PaintingContext context, Offset offset) {
231
+    final Canvas canvas = context.canvas;
232
+    paintRadialReaction(canvas, offset, size.center(Offset.zero));
233
+
234
+    final Offset origin =
235
+        offset + (size / 2.0 - const Size.square(_kEdgeSize) / 2.0 as Offset);
236
+    final AnimationStatus status = position.status;
237
+    final double tNormalized =
238
+        status == AnimationStatus.forward || status == AnimationStatus.completed
239
+            ? position.value
240
+            : 1.0 - position.value;
241
+
242
+    if (_oldValue == false || value == false) {
243
+      final double t = value == false ? 1.0 - tNormalized : tNormalized;
244
+      final RRect outer = _outerRectAt(origin, t);
245
+      final Paint paint = Paint()..color = _colorAt(t);
246
+
247
+      if (t <= 0.5) {
248
+        _drawBorder(canvas, outer, t, paint);
249
+      } else {
250
+        canvas.drawRRect(outer, paint);
251
+
252
+        _initStrokePaint(paint);
253
+        final double tShrink = (t - 0.5) * 2.0;
254
+        if (_oldValue == null || value == null) {
255
+          _drawDash(canvas, origin, tShrink, paint);
256
+        } else {
257
+          _drawCheck(canvas, origin, tShrink, paint);
258
+        }
259
+      }
260
+    } else {
261
+      final RRect outer = _outerRectAt(origin, 1.0);
262
+      final Paint paint = Paint()..color = _colorAt(1.0);
263
+      canvas.drawRRect(outer, paint);
264
+
265
+      _initStrokePaint(paint);
266
+      if (tNormalized <= 0.5) {
267
+        final double tShrink = 1.0 - tNormalized * 2.0;
268
+        if (_oldValue == true) {
269
+          _drawCheck(canvas, origin, tShrink, paint);
270
+        } else {
271
+          _drawDash(canvas, origin, tShrink, paint);
272
+        }
273
+      } else {
274
+        final double tExpand = (tNormalized - 0.5) * 2.0;
275
+        if (value == true) {
276
+          _drawCheck(canvas, origin, tExpand, paint);
277
+        } else {
278
+          _drawDash(canvas, origin, tExpand, paint);
279
+        }
280
+      }
281
+    }
282
+  }
283
+}

+ 65
- 0
packages/zefyr/lib/src/extended_assets_picker/src/widget/slide_page_transition_builder.dart View File

@@ -0,0 +1,65 @@
1
+part of ct_assets_picker;
2
+///
3
+/// [Author] Alex (https://github.com/Alex525)
4
+/// [Date] 2020/4/13 18:04
5
+///
6
+
7
+/// Built a slide page transition for the picker.
8
+/// 为选择器构造一个上下进出的页面过渡动画
9
+class SlidePageTransitionBuilder<T> extends PageRoute<T> {
10
+  SlidePageTransitionBuilder({
11
+    @required this.builder,
12
+    this.transitionCurve = Curves.easeIn,
13
+    this.transitionDuration = const Duration(milliseconds: 500),
14
+  });
15
+
16
+  final Widget builder;
17
+
18
+  final Curve transitionCurve;
19
+
20
+  @override
21
+  final Duration transitionDuration;
22
+
23
+  @override
24
+  final bool opaque = true;
25
+
26
+  @override
27
+  final bool barrierDismissible = false;
28
+
29
+  @override
30
+  final bool maintainState = true;
31
+
32
+  @override
33
+  Color get barrierColor => null;
34
+
35
+  @override
36
+  String get barrierLabel => null;
37
+
38
+  @override
39
+  Widget buildPage(
40
+    BuildContext context,
41
+    Animation<double> animation,
42
+    Animation<double> secondaryAnimation,
43
+  ) {
44
+    return builder;
45
+  }
46
+
47
+  @override
48
+  Widget buildTransitions(
49
+    BuildContext context,
50
+    Animation<double> animation,
51
+    Animation<double> secondaryAnimation,
52
+    Widget child,
53
+  ) {
54
+    return SlideTransition(
55
+      position: Tween<Offset>(
56
+        begin: const Offset(0, 1),
57
+        end: Offset.zero,
58
+      ).animate(CurvedAnimation(
59
+        curve: transitionCurve,
60
+        parent: animation,
61
+      )),
62
+      child: child,
63
+    );
64
+  }
65
+}

+ 18
- 289
packages/zefyr/lib/src/widgets/buttons.dart View File

@@ -4,12 +4,13 @@
4 4
 import 'dart:async';
5 5
 import 'dart:io';
6 6
 
7
+import 'package:flutter/foundation.dart';
7 8
 import 'package:flutter/material.dart';
8 9
 import 'package:flutter/services.dart';
9 10
 import 'package:notus/notus.dart';
10
-import 'package:photo/photo.dart';
11
-import 'package:photo_manager/photo_manager.dart';
11
+import 'package:image_picker/image_picker.dart';
12 12
 import 'package:url_launcher/url_launcher.dart';
13
+import 'package:zefyr/src/extended_assets_picker/ct_asset_picker.dart';
13 14
 
14 15
 import 'link.dart';
15 16
 import 'scope.dart';
@@ -533,7 +534,16 @@ class ImageButton extends StatefulWidget {
533 534
 }
534 535
 
535 536
 class _ImageButtonState extends State<ImageButton> {
536
-  List<AssetEntity> pickedAssetList = [];
537
+
538
+  final provider = ExtendedAssetPickerProvider(
539
+    maxAssets: 9,
540
+    pageSize: 320,
541
+    pathThumbSize: 200,
542
+    selectedAssets: [],
543
+    requestType: RequestType.image,
544
+    sortPathDelegate: CustomSortPathDelegate(),
545
+    routeDuration: const Duration(milliseconds: 300),
546
+  );
537 547
 
538 548
   @override
539 549
   Widget build(BuildContext context) {
@@ -548,8 +558,8 @@ class _ImageButtonState extends State<ImageButton> {
548 558
   Future<void> showOverlay() async {
549 559
     final toolbar = ZefyrToolbar.of(context);
550 560
     if (Platform.isIOS || Platform.isAndroid) {
551
-      var result = await PhotoManager.requestPermission();
552
-      if (result) {
561
+      var isPermissionGranted = await PhotoManager.requestPermission();
562
+      if (isPermissionGranted) {
553 563
         return toolbar.showOverlay(buildOverlay, ZefyrToolbarAction.image);
554 564
       } else {
555 565
         PhotoManager.openSetting();
@@ -559,294 +569,13 @@ class _ImageButtonState extends State<ImageButton> {
559 569
     }
560 570
   }
561 571
 
562
-  String get uploadText => Localizations.localeOf(context) == Locale('zh', 'CN') ? '上传' : 'Upload';
563
-  String get fullImageText => Localizations.localeOf(context) == Locale('zh', 'CN') ? '原图' : 'Full image';
564
-  String get camera => Localizations.localeOf(context) == Locale('zh', 'CN') ? '拍照' : 'Camera';
565
-  String get album => Localizations.localeOf(context) == Locale('zh', 'CN') ? '相册' : 'Album';
566
-  I18nProvider get locale => Localizations.localeOf(context) == Locale('zh', 'CN') ? I18nProvider.chinese : I18nProvider.english;
567
-
568 572
 
569 573
   Widget buildOverlay(BuildContext context) {
570
-    final theme = ZefyrTheme.of(context).toolbarTheme;
571
-    final toolbar = ZefyrToolbar.of(context);
572
-    final pickedChangeController = StreamController<PickedEntity>.broadcast();
573
-    return Material(
574
-      color: theme.color,
575
-      child: Container(
576
-        child: Column(
577
-          children: <Widget>[
578
-            Expanded(
579
-              child: Container(
580
-                decoration: BoxDecoration(
581
-                  border: Border(
582
-                    top: BorderSide(color: theme.dividerColor, width: 1),
583
-                    bottom: BorderSide(color: theme.dividerColor, width: 1),
584
-                  ),
585
-                ),
586
-                child: Row(
587
-                  children: [
588
-                    Container(
589
-                      width: 58,
590
-                      child: Column(
591
-                        children: [
592
-                          Expanded(
593
-                            child: FlatButton(
594
-                                shape: RoundedRectangleBorder(),
595
-                                color: theme.captionColor,
596
-                                padding: EdgeInsets.zero,
597
-                                onPressed: () async {
598
-                                  // toolbar.editor.tapHandle =
599
-                                  //     TapHandle.takePhoto;
600
-                                  
601
-                                  var file = await PhotoPicker.pickCamera();
602
-                                  if (file != null) {
603
-                                    toolbar.closeOverlay();
604
-                                    await toolbar
605
-                                        .editor.imageDelegate
606
-                                        .picked(File(file.path), true);
607
-                                  }
608
-                                },
609
-                                child: Container(
610
-                                  child: Column(
611
-                                    mainAxisAlignment: MainAxisAlignment.center,
612
-                                    children: [
613
-                                      Icon(
614
-                                        kDefaultButtonIcons[
615
-                                            ZefyrToolbarAction.cameraImage],
616
-                                        size: 24,
617
-                                        color: Color(0xFFBFBFBF),
618
-                                      ),
619
-                                      Padding(
620
-                                        padding: EdgeInsets.only(top: 6),
621
-                                        child: Text(
622
-                                          camera,
623
-                                          style: TextStyle(
624
-                                            fontSize: 12,
625
-                                            color: Color(0xFF8C8C8C),
626
-                                          ),
627
-                                        ),
628
-                                      )
629
-                                    ],
630
-                                  ),
631
-                                )),
632
-                          ),
633
-                          Expanded(
634
-                            child: FlatButton(
635
-                                shape: RoundedRectangleBorder(),
636
-                                color: theme.captionColor,
637
-                                padding: EdgeInsets.zero,
638
-                                onPressed: () {
639
-                                  final editor =
640
-                                      ZefyrToolbar.of(context).editor;
641
-                                  // var _selection = toolbar.editor.selection;
642
-                                  editor.closeKeyboard(true);
643
-                                  PhotoPicker.pickAsset(
644
-                                    context: context,
645
-                                    rowCount: 4,
646
-                                    itemRadio: 1,
647
-                                    padding: 4,
648
-                                    provider: locale,
649
-                                    sortDelegate: SortDelegate.common,
650
-                                    pickType: PickType.onlyImage,
651
-                                    photoPathList: null,
652
-                                  ).then((PickedEntity entity) async {
653
-                                    if (entity.asset.isNotEmpty) {
654
-                                      for (var asset in entity.asset) {
655
-                                        var file = await asset.originFile;
656
-                                        final image = await editor.imageDelegate
657
-                                            .picked(file, entity.isFullImage);
658
-                                        if (image != null) {
659
-                                          editor.formatSelection(NotusAttribute
660
-                                              .embed
661
-                                              .image(image));
662
-                                          editor.closeKeyboard();
663
-                                        }
664
-                                      }
665
-                                    }
666
-                                  });
667
-                                },
668
-                                child: Container(
669
-                                  child: Column(
670
-                                    mainAxisAlignment: MainAxisAlignment.center,
671
-                                    children: [
672
-                                      Icon(
673
-                                        kDefaultButtonIcons[
674
-                                            ZefyrToolbarAction.galleryImage],
675
-                                        size: 24,
676
-                                        color: Color(0xFFBFBFBF),
677
-                                      ),
678
-                                      Padding(
679
-                                        padding: EdgeInsets.only(top: 6),
680
-                                        child: Text(
681
-                                          album,
682
-                                          style: TextStyle(
683
-                                            fontSize: 12,
684
-                                            color: Color(0xFF8C8C8C),
685
-                                          ),
686
-                                        ),
687
-                                      )
688
-                                    ],
689
-                                  ),
690
-                                )),
691
-                          ),
692
-                        ],
693
-                      ),
694
-                    ),
695
-                    Expanded(
696
-                      child: Listener(
697
-                        behavior: HitTestBehavior.translucent,
698
-                        onPointerUp: (PointerUpEvent event) {
699
-                          toolbar.editor.keepOverlay = true;
700
-                        },
701
-                        child: StreamBuilder<PickedEntity>(
702
-                          stream: pickedChangeController.stream,
703
-                          initialData: PickedEntity(),
704
-                          builder: (context, snapshot) {
705
-                            return PhotoPicker.buildGallery(
706
-                              context: context,
707
-                              padding: 4,
708
-                              thumbSize: 300,
709
-                              itemRadio: 0.5,
710
-                              provider: locale,
711
-                              sortDelegate: SortDelegate.common,
712
-                              pickType: PickType.onlyImage,
713
-                              photoPathList: null,
714
-                              isFullImage: snapshot.data.isFullImage,
715
-                              onSelected: (PickedEntity entity) {
716
-                                pickedChangeController.add(entity);
717
-                              },
718
-                            );
719
-                          },
720
-                        ),
721
-                      ),
722
-                    ),
723
-                  ],
724
-                ),
725
-              ),
726
-            ),
727
-            Container(
728
-              height: 50,
729
-              color: theme.color,
730
-              padding: EdgeInsets.symmetric(horizontal: 20),
731
-              child: StreamBuilder<PickedEntity>(
732
-                builder: (context, snapshot) {
733
-                  return Row(
734
-                    children: [
735
-                      Expanded(
736
-                        child: Stack(
737
-                          children: <Widget>[
738
-                            IgnorePointer(
739
-                              child: Row(
740
-                                children: [
741
-                                  SizedBox(
742
-                                    width: 16,
743
-                                    height: 16,
744
-                                    child: Radio<bool>(
745
-                                      value: true,
746
-                                      groupValue: snapshot.data.isFullImage,
747
-                                      onChanged: (bool result) {},
748
-                                    ),
749
-                                  ),
750
-                                  Padding(
751
-                                    padding:
752
-                                        EdgeInsets.symmetric(horizontal: 8),
753
-                                    child: Text(
754
-                                      fullImageText,
755
-                                      style: TextStyle(
756
-                                          color: theme.iconColor, fontSize: 16),
757
-                                    ),
758
-                                  ),
759
-                                ],
760
-                              ),
761
-                            ),
762
-                            Positioned(
763
-                              top: 0.0,
764
-                              bottom: 0.0,
765
-                              left: 0.0,
766
-                              right: 0.0,
767
-                              child: GestureDetector(
768
-                                onTap: () {
769
-                                  pickedChangeController.add(PickedEntity(
770
-                                    asset: snapshot.data.asset,
771
-                                    isFullImage: !snapshot.data.isFullImage,
772
-                                  ));
773
-                                },
774
-                                behavior: HitTestBehavior.translucent,
775
-                                child: Container(),
776
-                              ),
777
-                            ),
778
-                          ],
779
-                        ),
780
-                      ),
781
-                      FlatButton(
782
-                        padding: EdgeInsets.zero,
783
-                        color: theme.toggleColor,
784
-                        disabledColor: theme.disabledIconColor,
785
-                        shape: StadiumBorder(),
786
-                        onPressed: snapshot.data.asset.isNotEmpty
787
-                            ? () async {
788
-                                pickedChangeController.add(PickedEntity());
789
-                                // toolbar.closeOverlay();
790
-                                toolbar.editor.closeKeyboard(true);
791
-                                for (var asset in snapshot.data.asset) {
792
-                                  var file = await asset.originFile;
793
-                                  final image = await toolbar
794
-                                      .editor.imageDelegate
795
-                                      .picked(file, snapshot.data.isFullImage);
796
-                                  if (image != null) {
797
-                                    // toolbar.editor.toolbarAction = null;
798
-                                    // toolbar.editor.keepOverlay = true;
799
-                                    toolbar.editor.formatSelection(
800
-                                        NotusAttribute.embed.image(image));
801
-                                    toolbar.editor.closeKeyboard();
802
-                                  }
803
-                                }
804
-                              }
805
-                            : null,
806
-                        child: Container(
807
-                          height: 30,
808
-                          alignment: Alignment.center,
809
-                          padding: EdgeInsets.symmetric(horizontal: 20),
810
-                          child: Text(
811
-                            '$uploadText ${snapshot.data.asset.isNotEmpty ? "(${snapshot.data.asset.length})" : ''}',
812
-                            style: TextStyle(
813
-                              color: Colors.white,
814
-                              fontSize: 14,
815
-                            ),
816
-                          ),
817
-                        ),
818
-                      ),
819
-                    ],
820
-                  );
821
-                },
822
-                initialData: PickedEntity(),
823
-                stream: pickedChangeController.stream,
824
-              ),
825
-            ),
826
-          ],
827
-        ),
828
-      ),
574
+    return ExtendedAssetPicker.buildQuickPicker(
575
+      context,
576
+      provider: provider,
829 577
     );
830 578
   }
831
-
832
-  // void _pickFromCamera() async {
833
-  //   final editor = ZefyrToolbar.of(context).editor;
834
-  //   final image =
835
-  //       await editor.imageDelegate.pickImage(editor.imageDelegate.cameraSource);
836
-  //   if (image != null) {
837
-  //     editor.formatSelection(NotusAttribute.embed.image(image));
838
-  //   }
839
-  // }
840
-
841
-  // void _pickFromGallery() async {
842
-  //   final editor = ZefyrToolbar.of(context).editor;
843
-  //   for (var asset in pickedAssetList) {
844
-  //     final image = await editor.imageDelegate.picked(asset);
845
-  //     if (image != null) {
846
-  //       editor.formatSelection(NotusAttribute.embed.image(image));
847
-  //     }
848
-  //   }
849
-  // }
850 579
 }
851 580
 
852 581
 class LinkButton extends StatefulWidget {

+ 7
- 4
packages/zefyr/pubspec.yaml View File

@@ -1,6 +1,6 @@
1 1
 name: zefyr
2 2
 description: Clean, minimalistic and collaboration-ready rich text editor for Flutter.
3
-version: 0.10.3
3
+version: 0.10.4
4 4
 author: Anatoly Pulyaevskiy <anatoly.pulyaevskiy@gmail.com>
5 5
 homepage: https://github.com/memspace/zefyr
6 6
 
@@ -19,9 +19,12 @@ dependencies:
19 19
       path: packages/notus
20 20
   meta: ^1.1.0
21 21
   quiver_hashcode: ^2.0.0
22
-  photo: 
23
-    git: 
24
-      url: https://git.links123.net/cloudteam/image_picker.git
22
+  
23
+  wechat_assets_picker: ^5.0.0-dev.3
24
+      
25
+  provider: ^4.3.2+2
26
+
27
+  image_picker: ^0.6.7+17
25 28
 
26 29
 dev_dependencies:
27 30
   flutter_test: