소스 검색

add theme

lucky1213 4 년 전
부모
커밋
052417f88c

+ 1
- 1
example/ios/Podfile 파일 보기

@@ -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'

+ 1
- 13
example/ios/Runner.xcodeproj/project.pbxproj 파일 보기

@@ -9,10 +9,6 @@
9 9
 /* Begin PBXBuildFile section */
10 10
 		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 11
 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12
-		3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
13
-		3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
14
-		9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
15
-		9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
16 12
 		9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
17 13
 		978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
18 14
 		97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
@@ -29,8 +25,6 @@
29 25
 			dstPath = "";
30 26
 			dstSubfolderSpec = 10;
31 27
 			files = (
32
-				3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
33
-				9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
34 28
 			);
35 29
 			name = "Embed Frameworks";
36 30
 			runOnlyForDeploymentPostprocessing = 0;
@@ -43,13 +37,11 @@
43 37
 		223FFDA7243467D600A654DC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Main.strings"; sourceTree = "<group>"; };
44 38
 		223FFDA8243467D600A654DC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = "<group>"; };
45 39
 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
46
-		3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
47 40
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
48 41
 		7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
49 42
 		7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
50 43
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
51 44
 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
52
-		9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
53 45
 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
54 46
 		97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
55 47
 		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
@@ -66,8 +58,6 @@
66 58
 			isa = PBXFrameworksBuildPhase;
67 59
 			buildActionMask = 2147483647;
68 60
 			files = (
69
-				9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
70
-				3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
71 61
 				F1F41F4DCF67837579E0EB42 /* libPods-Runner.a in Frameworks */,
72 62
 			);
73 63
 			runOnlyForDeploymentPostprocessing = 0;
@@ -87,9 +77,7 @@
87 77
 		9740EEB11CF90186004384FC /* Flutter */ = {
88 78
 			isa = PBXGroup;
89 79
 			children = (
90
-				3B80C3931E831B6300D905FE /* App.framework */,
91 80
 				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
92
-				9740EEBA1CF902C7004384FC /* Flutter.framework */,
93 81
 				9740EEB21CF90195004384FC /* Debug.xcconfig */,
94 82
 				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
95 83
 				9740EEB31CF90195004384FC /* Generated.xcconfig */,
@@ -251,7 +239,7 @@
251 239
 			);
252 240
 			runOnlyForDeploymentPostprocessing = 0;
253 241
 			shellPath = /bin/sh;
254
-			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
242
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
255 243
 		};
256 244
 		9740EEB61CF901F6004384FC /* Run Script */ = {
257 245
 			isa = PBXShellScriptBuildPhase;

+ 0
- 8
example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings 파일 보기

@@ -1,8 +0,0 @@
1
-<?xml version="1.0" encoding="UTF-8"?>
2
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
-<plist version="1.0">
4
-<dict>
5
-	<key>BuildSystemType</key>
6
-	<string>Original</string>
7
-</dict>
8
-</plist>

+ 44
- 42
example/lib/main.dart 파일 보기

@@ -18,7 +18,26 @@ class MyApp extends StatelessWidget {
18 18
       child: MaterialApp(
19 19
         title: 'Pick Image Demo',
20 20
         theme: ThemeData(
21
-          primarySwatch: Colors.lime,
21
+          primaryColor: Color(0xFF01AAFF),
22
+          // colorScheme: ColorScheme.light().copyWith(surface: Colors.white, onPrimary: Color(0xFFFFFFFF)),
23
+          // appBarTheme: AppBarTheme(
24
+          //   color: Color(0xFFFFFFFF),
25
+          //   elevation: 0,
26
+          //   iconTheme: IconThemeData(
27
+          //     color: Color(0xFF595959),
28
+          //   ),
29
+          //   actionsIconTheme: IconThemeData(
30
+          //     color: Color(0xFF01AAFF),
31
+          //   ),
32
+          //   textTheme: TextTheme(
33
+          //     headline6: TextStyle(
34
+          //       fontSize: 18,
35
+          //       fontWeight: FontWeight.w500,
36
+          //       color: Color(0xFF595959),
37
+          //     ),
38
+          //   ),
39
+          // ),
40
+          // dividerColor: Color(0xFFF5F5F5),
22 41
         ),
23 42
         home: MyHomePage(title: 'Pick Image Demo'),
24 43
       ),
@@ -101,6 +120,23 @@ class _MyHomePageState extends State<MyHomePage> with LoadingDelegate {
101 120
                 text: "Picked asset example.",
102 121
                 onTap: () => routePage(PickedExample()),
103 122
               ),
123
+              Container(
124
+                height: 210,
125
+                child: PhotoPicker.buildGallery(
126
+                  context: context,
127
+                  padding: 4.0,
128
+                  itemHeight: 210,
129
+                  itemWidth: 180,
130
+                  provider: I18nProvider.chinese,
131
+                  sortDelegate: SortDelegate.common,
132
+                  loadingDelegate: this,
133
+                  pickType: PickType.onlyImage,
134
+                  photoPathList: null,
135
+                  onSelected: (List<AssetEntity> value) {
136
+                    print(value);
137
+                  }
138
+                ),
139
+              )
104 140
             ],
105 141
           ),
106 142
         ),
@@ -123,53 +159,19 @@ class _MyHomePageState extends State<MyHomePage> with LoadingDelegate {
123 159
     /// context is required, other params is optional.
124 160
     /// context is required, other params is optional.
125 161
     /// context is required, other params is optional.
126
-
162
+    List<AssetPathEntity> pathList2 = await PhotoManager.getAssetPathList(
163
+        hasAll: true, type: RequestType.image,);
127 164
     List<AssetEntity> imgList = await PhotoPicker.pickAsset(
128
-      // BuildContext required
129 165
       context: context,
130
-
131
-      /// The following are optional parameters.
132
-      themeColor: Colors.green,
133
-      // the title color and bottom color
134
-
135
-      textColor: Colors.white,
136
-      // text color
137
-      padding: 1.0,
138
-      // item padding
139
-      dividerColor: Colors.grey,
140
-      // divider color
141
-      disableColor: Colors.grey.shade300,
142
-      // the check box disable color
143
-      itemRadio: 0.88,
144
-      // the content item radio
166
+      padding: 4.0,
167
+      itemRadio: 1,
145 168
       maxSelected: 8,
146
-      // max picker image count
147
-      // provider: I18nProvider.english,
148 169
       provider: I18nProvider.chinese,
149
-      // i18n provider ,default is chinese. , you can custom I18nProvider or use ENProvider()
150
-      rowCount: 3,
151
-      // item row count
152
-
170
+      rowCount: 4,
153 171
       thumbSize: 150,
154
-      // preview thumb size , default is 64
155 172
       sortDelegate: SortDelegate.common,
156
-      // default is common ,or you make custom delegate to sort your gallery
157
-      checkBoxBuilderDelegate: DefaultCheckBoxBuilderDelegate(
158
-        activeColor: Colors.white,
159
-        unselectedColor: Colors.white,
160
-        checkColor: Colors.green,
161
-      ),
162
-      // default is DefaultCheckBoxBuilderDelegate ,or you make custom delegate to create checkbox
163
-
164
-      loadingDelegate: this,
165
-      // if you want to build custom loading widget,extends LoadingDelegate, [see example/lib/main.dart]
166
-
167
-      badgeDelegate: const DurationBadgeDelegate(),
168
-      // badgeDelegate to show badge widget
169
-
170
-      pickType: type,
171
-
172
-      photoPathList: pathList,
173
+      pickType: PickType.onlyVideo,
174
+      photoPathList: pathList2,
173 175
     );
174 176
 
175 177
     if (imgList == null || imgList.isEmpty) {

+ 69
- 28
lib/photo.dart 파일 보기

@@ -3,6 +3,8 @@ library photo;
3 3
 import 'dart:async';
4 4
 
5 5
 import 'package:flutter/material.dart';
6
+import 'package:photo/src/entity/_theme.dart';
7
+import 'package:photo/src/ui/page/photo_list_page.dart';
6 8
 
7 9
 import 'package:photo_manager/photo_manager.dart';
8 10
 
@@ -14,6 +16,8 @@ import 'package:photo/src/entity/options.dart';
14 16
 import 'package:photo/src/provider/i18n_provider.dart';
15 17
 import 'package:photo/src/ui/dialog/not_permission_dialog.dart';
16 18
 import 'package:photo/src/ui/photo_app.dart';
19
+
20
+import 'src/provider/config_provider.dart';
17 21
 export 'package:photo/src/delegate/checkbox_builder_delegate.dart';
18 22
 export 'package:photo/src/delegate/loading_delegate.dart';
19 23
 export 'package:photo/src/delegate/sort_delegate.dart';
@@ -68,19 +72,13 @@ class PhotoPicker {
68 72
     @required BuildContext context,
69 73
     int rowCount = 4,
70 74
     int maxSelected = 9,
71
-    double padding = 0.5,
75
+    double padding = 1,
72 76
     double itemRadio = 1.0,
73
-    Color themeColor,
74
-    Color dividerColor,
75
-    Color textColor,
76
-    Color disableColor,
77 77
     int thumbSize = 64,
78 78
     I18nProvider provider = I18nProvider.chinese,
79 79
     SortDelegate sortDelegate,
80
-    CheckBoxBuilderDelegate checkBoxBuilderDelegate,
81 80
     LoadingDelegate loadingDelegate,
82 81
     PickType pickType = PickType.all,
83
-    BadgeDelegate badgeDelegate = const DefaultBadgeDelegate(),
84 82
     List<AssetPathEntity> photoPathList,
85 83
     List<AssetEntity> pickedAssetList,
86 84
   }) {
@@ -88,30 +86,21 @@ class PhotoPicker {
88 86
     assert(context != null, "context must be not null");
89 87
     assert(pickType != null, "pickType must be not null");
90 88
 
91
-    themeColor ??= Theme.of(context)?.primaryColor ?? Colors.black;
92
-    dividerColor ??= Theme.of(context)?.dividerColor ?? Colors.grey;
93
-    disableColor ??= Theme.of(context)?.disabledColor ?? Colors.grey;
94
-    textColor ??= Colors.white;
95
-
96 89
     sortDelegate ??= SortDelegate.common;
97
-    checkBoxBuilderDelegate ??= DefaultCheckBoxBuilderDelegate();
98 90
 
99 91
     loadingDelegate ??= DefaultLoadingDelegate();
100 92
 
101 93
     var options = Options(
102 94
       rowCount: rowCount,
103
-      dividerColor: dividerColor,
104 95
       maxSelected: maxSelected,
105 96
       itemRadio: itemRadio,
106 97
       padding: padding,
107
-      disableColor: disableColor,
108
-      textColor: textColor,
109
-      themeColor: themeColor,
98
+      theme: PhotoTheme.fallback(context),
110 99
       thumbSize: thumbSize,
111 100
       sortDelegate: sortDelegate,
112
-      checkBoxBuilderDelegate: checkBoxBuilderDelegate,
101
+      checkBoxBuilderDelegate: DefaultCheckBoxBuilderDelegate(),
113 102
       loadingDelegate: loadingDelegate,
114
-      badgeDelegate: badgeDelegate,
103
+      badgeDelegate: DefaultBadgeDelegate(),
115 104
       pickType: pickType,
116 105
     );
117 106
 
@@ -131,12 +120,72 @@ class PhotoPicker {
131 120
     List<AssetPathEntity> photoList,
132 121
     List<AssetEntity> pickedAssetList,
133 122
   ) async {
123
+    await requestPermission(context, provider: provider);
124
+
125
+    return _openGalleryContentPage(
126
+      context,
127
+      options,
128
+      provider,
129
+      photoList,
130
+      pickedAssetList,
131
+    );
132
+  }
133
+
134
+  static Widget buildGallery({
135
+    @required BuildContext context,
136
+    double padding = 1,
137
+    @required double itemHeight,
138
+    @required double itemWidth,
139
+    int thumbSize = 64,
140
+    I18nProvider provider = I18nProvider.chinese,
141
+    SortDelegate sortDelegate,
142
+    LoadingDelegate loadingDelegate,
143
+    PickType pickType = PickType.all,
144
+    List<AssetPathEntity> photoPathList,
145
+    List<AssetEntity> pickedAssetList,
146
+    void Function(List<AssetEntity> value) onSelected,
147
+  }) {
148
+    sortDelegate ??= SortDelegate.common;
149
+
150
+    loadingDelegate ??= DefaultLoadingDelegate();
151
+
152
+    var options = ListOptions(
153
+      padding: padding,
154
+      itemHeight: itemHeight,
155
+      itemWidth: itemWidth,
156
+      theme: PhotoTheme.fallback(context),
157
+      thumbSize: thumbSize,
158
+      sortDelegate: sortDelegate,
159
+      checkBoxBuilderDelegate: DefaultCheckBoxBuilderDelegate(),
160
+      loadingDelegate: loadingDelegate,
161
+      badgeDelegate: DefaultBadgeDelegate(),
162
+      pickType: pickType,
163
+    );
164
+    assert(provider != null, "provider must be not null");
165
+    assert(context != null, "context must be not null");
166
+    assert(pickType != null, "pickType must be not null");
167
+
168
+    final pickerProvider = PhotoPickerProvider(
169
+      provider: provider,
170
+      options: options,
171
+      pickedAssetList: pickedAssetList,
172
+      child: PhotoListPage(
173
+        onSelected: onSelected,
174
+        options: options,
175
+        photoList: photoPathList,
176
+      ),
177
+    );
178
+
179
+    return pickerProvider;
180
+  }
181
+
182
+  Future<void> requestPermission(BuildContext context, {@required I18nProvider provider}) async {
134 183
     var requestPermission = await PhotoManager.requestPermission();
135 184
     if (requestPermission != true) {
136 185
       var result = await showDialog(
137 186
         context: context,
138 187
         builder: (ctx) => NotPermissionDialog(
139
-          provider.getNotPermissionText(options),
188
+          provider.getNotPermissionText(),
140 189
         ),
141 190
       );
142 191
       if (result == true) {
@@ -144,14 +193,6 @@ class PhotoPicker {
144 193
       }
145 194
       return null;
146 195
     }
147
-
148
-    return _openGalleryContentPage(
149
-      context,
150
-      options,
151
-      provider,
152
-      photoList,
153
-      pickedAssetList,
154
-    );
155 196
   }
156 197
 
157 198
   Future<List<AssetEntity>> _openGalleryContentPage(

+ 57
- 30
lib/src/delegate/checkbox_builder_delegate.dart 파일 보기

@@ -15,13 +15,13 @@ abstract class CheckBoxBuilderDelegate {
15 15
 
16 16
 class DefaultCheckBoxBuilderDelegate extends CheckBoxBuilderDelegate {
17 17
   Color activeColor;
18
-  Color unselectedColor;
19 18
   Color checkColor;
19
+  Color unselectedColor;
20 20
 
21 21
   DefaultCheckBoxBuilderDelegate({
22
-    this.activeColor = Colors.white,
23
-    this.unselectedColor = Colors.white,
24
-    this.checkColor = Colors.black,
22
+    this.activeColor,
23
+    this.checkColor,
24
+    this.unselectedColor,
25 25
   });
26 26
 
27 27
   @override
@@ -33,52 +33,79 @@ class DefaultCheckBoxBuilderDelegate extends CheckBoxBuilderDelegate {
33 33
     I18nProvider i18nProvider,
34 34
   ) {
35 35
     return Theme(
36
-      data: Theme.of(context).copyWith(unselectedWidgetColor: unselectedColor),
37
-      child: CheckboxListTile(
38
-        value: checked,
39
-        onChanged: (bool check) {},
40
-        activeColor: activeColor,
41
-        checkColor: checkColor,
42
-        title: Text(
43
-          i18nProvider.getSelectedOptionsText(options),
44
-          textAlign: TextAlign.end,
45
-          style: TextStyle(color: options.textColor),
36
+      data: Theme.of(context).copyWith(
37
+          unselectedWidgetColor:
38
+              unselectedColor ?? options.theme.unselectedColor),
39
+      child: Container(
40
+        child: Row(
41
+          children: [
42
+            SizedBox(
43
+              width: 20,
44
+              height: 20,
45
+              child: Checkbox(
46
+                value: checked,
47
+                onChanged: (bool check) {},
48
+                activeColor: activeColor ?? options.theme.primaryColor,
49
+                checkColor: checkColor ?? options.theme.onPrimaryColor,
50
+              ),
51
+            ),
52
+            SizedBox(
53
+              width: 6,
54
+            ),
55
+            Text(
56
+              i18nProvider.getSelectedOptionsText(options),
57
+              textAlign: TextAlign.start,
58
+              style: options.theme.textStyle,
59
+            ),
60
+          ],
46 61
         ),
47 62
       ),
48 63
     );
49 64
   }
50 65
 }
51 66
 
52
-class RadioCheckBoxBuilderDelegate extends CheckBoxBuilderDelegate {
67
+class RadioCheckBoxBuilderDelegate {
53 68
   Color activeColor;
54 69
   Color unselectedColor;
55 70
 
56 71
   RadioCheckBoxBuilderDelegate({
57
-    this.activeColor = Colors.white,
58
-    this.unselectedColor = Colors.white,
72
+    this.activeColor,
73
+    this.unselectedColor,
59 74
   });
60 75
 
61
-  @override
62 76
   Widget buildCheckBox(
63 77
     BuildContext context,
64 78
     bool checked,
65
-    int index,
66 79
     Options options,
67 80
     I18nProvider i18nProvider,
68 81
   ) {
69 82
     return Theme(
70
-      data: Theme.of(context).copyWith(unselectedWidgetColor: unselectedColor),
71
-      child: RadioListTile<bool>(
72
-        value: true,
73
-        onChanged: (bool check) {},
74
-        activeColor: activeColor,
75
-        title: Text(
76
-          i18nProvider.getSelectedOptionsText(options),
77
-          textAlign: TextAlign.end,
78
-          style: TextStyle(color: options.textColor, fontSize: 14.0),
83
+      data: Theme.of(context).copyWith(
84
+          unselectedWidgetColor:
85
+              unselectedColor ?? options.theme.unselectedColor),
86
+      child: Container(
87
+        child: Row(
88
+          children: [
89
+            SizedBox(
90
+              width: 20,
91
+              height: 20,
92
+              child: Radio(
93
+                value: true,
94
+                groupValue: checked,
95
+                onChanged: (bool check) {},
96
+                activeColor: activeColor ?? options.theme.primaryColor,
97
+              ),
98
+            ),
99
+            SizedBox(
100
+              width: 6,
101
+            ),
102
+            Text(
103
+              i18nProvider.getFullImageText(options),
104
+              textAlign: TextAlign.start,
105
+              style: options.theme.textStyle,
106
+            ),
107
+          ],
79 108
         ),
80
-        groupValue: checked,
81
-        controlAffinity: ListTileControlAffinity.trailing,
82 109
       ),
83 110
     );
84 111
   }

+ 73
- 0
lib/src/entity/_theme.dart 파일 보기

@@ -0,0 +1,73 @@
1
+import 'package:flutter/material.dart';
2
+
3
+class PhotoTheme {
4
+  final Color primaryColor;
5
+  final Color onPrimaryColor;
6
+  final Color surfaceColor;
7
+  final Color onSurfaceColor;
8
+  final Color backgroundColor;
9
+  final Color onBackgroundColor;
10
+  final Color dividerColor;
11
+  final Color disableColor;
12
+  final Color unselectedColor;
13
+  final TextStyle textStyle;
14
+
15
+  factory PhotoTheme.fallback(BuildContext context) {
16
+    final theme = Theme.of(context);
17
+    return PhotoTheme._(
18
+      primaryColor: theme.colorScheme?.primary ?? Color(0xFF01AAFF),
19
+      onPrimaryColor: theme.colorScheme?.onPrimary ?? Color(0xFFFFFFFF),
20
+      surfaceColor: theme.colorScheme?.surface ?? (theme.brightness == Brightness.light ? Color(0xFFFFFFFF) : Color(0xFF282828)),
21
+      onSurfaceColor: theme.colorScheme?.onSurface ?? (theme.brightness == Brightness.light ? Color(0xFF8C8C8C) : Color(0xFF696969)),
22
+      backgroundColor: theme.colorScheme?.background ?? (theme.brightness == Brightness.light ? Color(0xFFF8F8F8) : Color(0xFF1F1F1F)),
23
+      onBackgroundColor: theme.colorScheme?.onBackground ?? Color(0xFF8C8C8C),
24
+      dividerColor: theme.dividerColor ?? (theme.brightness == Brightness.light ? Color(0xFFF2F2F2) : Color(0xFF3B3B3B)),
25
+      disableColor: theme.disabledColor,
26
+      unselectedColor: theme.unselectedWidgetColor ?? (theme.brightness == Brightness.light ? Color(0xFFBFBFBF) : Color(0xFF696969)),
27
+      textStyle: TextStyle(
28
+        color: theme.brightness == Brightness.light ? Color(0xFF595959) : Color(0xFFA8A8A8),
29
+        fontSize: 16,
30
+        fontWeight: FontWeight.w400,
31
+      ), //theme.textTheme?.subtitle2 ?? 
32
+    );
33
+  }
34
+
35
+  PhotoTheme._({
36
+    @required this.primaryColor,
37
+    @required this.onPrimaryColor,
38
+    @required this.surfaceColor,
39
+    @required this.onSurfaceColor,
40
+    @required this.backgroundColor,
41
+    @required this.onBackgroundColor,
42
+    @required this.dividerColor,
43
+    @required this.disableColor,
44
+    @required this.unselectedColor,
45
+    @required this.textStyle,
46
+  });
47
+
48
+  PhotoTheme copyWith({
49
+    Color primaryColor,
50
+    Color onPrimaryColor,
51
+    Color surfaceColor,
52
+    Color onSurfaceColor,
53
+    Color backgroundColor,
54
+    Color onBackgroundColor,
55
+    Color dividerColor,
56
+    Color disableColor,
57
+    Color unselectedColor,
58
+    TextStyle textStyle,
59
+  }) {
60
+    return PhotoTheme._(
61
+      primaryColor: primaryColor ?? this.primaryColor,
62
+      onPrimaryColor: onPrimaryColor ?? this.onPrimaryColor,
63
+      surfaceColor: surfaceColor ?? this.surfaceColor,
64
+      onSurfaceColor: onSurfaceColor ?? this.onSurfaceColor,
65
+      backgroundColor: backgroundColor ?? this.backgroundColor,
66
+      onBackgroundColor: onBackgroundColor ?? this.onBackgroundColor,
67
+      dividerColor: dividerColor ?? this.dividerColor,
68
+      disableColor: disableColor ?? this.disableColor,
69
+      unselectedColor: unselectedColor ?? this.unselectedColor,
70
+      textStyle: textStyle ?? this.textStyle,
71
+    );
72
+  }
73
+}

+ 37
- 11
lib/src/entity/options.dart 파일 보기

@@ -3,6 +3,7 @@ import 'package:photo/src/delegate/badge_delegate.dart';
3 3
 import 'package:photo/src/delegate/checkbox_builder_delegate.dart';
4 4
 import 'package:photo/src/delegate/loading_delegate.dart';
5 5
 import 'package:photo/src/delegate/sort_delegate.dart';
6
+import 'package:photo/src/entity/_theme.dart';
6 7
 
7 8
 class Options {
8 9
   final int rowCount;
@@ -13,13 +14,7 @@ class Options {
13 14
 
14 15
   final double itemRadio;
15 16
 
16
-  final Color themeColor;
17
-
18
-  final Color dividerColor;
19
-
20
-  final Color textColor;
21
-
22
-  final Color disableColor;
17
+  final PhotoTheme theme;
23 18
 
24 19
   final int thumbSize;
25 20
 
@@ -38,10 +33,7 @@ class Options {
38 33
     this.maxSelected,
39 34
     this.padding,
40 35
     this.itemRadio,
41
-    this.themeColor,
42
-    this.dividerColor,
43
-    this.textColor,
44
-    this.disableColor,
36
+    this.theme,
45 37
     this.thumbSize,
46 38
     this.sortDelegate,
47 39
     this.checkBoxBuilderDelegate,
@@ -51,6 +43,40 @@ class Options {
51 43
   });
52 44
 }
53 45
 
46
+class ListOptions extends Options {
47
+
48
+  final double itemWidth;
49
+
50
+  final double itemHeight;
51
+
52
+  final Axis scrollDirection;
53
+
54
+  const ListOptions({
55
+    this.itemHeight,
56
+    this.itemWidth,
57
+    this.scrollDirection = Axis.horizontal,
58
+    int maxSelected,
59
+    double padding,
60
+    PhotoTheme theme,
61
+    int thumbSize,
62
+    SortDelegate sortDelegate,
63
+    CheckBoxBuilderDelegate checkBoxBuilderDelegate,
64
+    LoadingDelegate loadingDelegate,
65
+    BadgeDelegate badgeDelegate,
66
+    PickType pickType,
67
+  }) : super(
68
+    maxSelected: maxSelected,
69
+    padding: padding,
70
+    theme: theme,
71
+    thumbSize: thumbSize,
72
+    sortDelegate: sortDelegate,
73
+    checkBoxBuilderDelegate: checkBoxBuilderDelegate,
74
+    loadingDelegate: loadingDelegate,
75
+    badgeDelegate: badgeDelegate,
76
+    pickType: pickType,
77
+  );
78
+}
79
+
54 80
 enum PickType {
55 81
   all,
56 82
   onlyImage,

+ 16
- 4
lib/src/provider/i18n_provider.dart 파일 보기

@@ -12,6 +12,8 @@ abstract class I18nProvider {
12 12
 
13 13
   String getSelectedOptionsText(Options options);
14 14
 
15
+  String getFullImageText(Options options);
16
+
15 17
   String getMaxTipText(Options options);
16 18
 
17 19
   String getAllGalleryText(Options options);
@@ -20,7 +22,7 @@ abstract class I18nProvider {
20 22
     return "Loading...";
21 23
   }
22 24
 
23
-  I18NPermissionProvider getNotPermissionText(Options options);
25
+  I18NPermissionProvider getNotPermissionText([Options options]);
24 26
 
25 27
   static const I18nProvider chinese = CNProvider();
26 28
 
@@ -50,6 +52,11 @@ class CNProvider extends I18nProvider {
50 52
     return "选择";
51 53
   }
52 54
 
55
+  @override
56
+  String getFullImageText(Options options) {
57
+    return "原图";
58
+  }
59
+
53 60
   @override
54 61
   String getMaxTipText(Options options) {
55 62
     return "您已经选择了${options.maxSelected}张图片";
@@ -66,7 +73,7 @@ class CNProvider extends I18nProvider {
66 73
   }
67 74
 
68 75
   @override
69
-  I18NPermissionProvider getNotPermissionText(Options options) {
76
+  I18NPermissionProvider getNotPermissionText([Options options]) {
70 77
     return I18NPermissionProvider(
71 78
         cancelText: "取消", sureText: "去开启", titleText: "没有访问相册的权限");
72 79
   }
@@ -95,6 +102,11 @@ class ENProvider extends I18nProvider {
95 102
     return "Selected";
96 103
   }
97 104
 
105
+  @override
106
+  String getFullImageText(Options options) {
107
+    return "Full Image";
108
+  }
109
+
98 110
   @override
99 111
   String getMaxTipText(Options options) {
100 112
     return "Select ${options.maxSelected} pictures at most";
@@ -106,7 +118,7 @@ class ENProvider extends I18nProvider {
106 118
   }
107 119
 
108 120
   @override
109
-  I18NPermissionProvider getNotPermissionText(Options options) {
121
+  I18NPermissionProvider getNotPermissionText([Options options]) {
110 122
     return I18NPermissionProvider(
111 123
         cancelText: "Cancel",
112 124
         sureText: "Allow",
@@ -146,7 +158,7 @@ abstract class I18NCustomProvider implements I18nProvider {
146 158
   }
147 159
 
148 160
   @override
149
-  I18NPermissionProvider getNotPermissionText(Options options) {
161
+  I18NPermissionProvider getNotPermissionText([Options options]) {
150 162
     return notPermissionText;
151 163
   }
152 164
 }

+ 1
- 0
lib/src/provider/selected_provider.dart 파일 보기

@@ -3,6 +3,7 @@ import 'dart:async';
3 3
 import 'package:photo_manager/photo_manager.dart';
4 4
 
5 5
 abstract class SelectedProvider {
6
+  bool isFullImage = false;
6 7
   List<AssetEntity> selectedList = [];
7 8
 
8 9
   int get selectedCount => selectedList.length;

+ 72
- 6
lib/src/ui/dialog/change_gallery_dialog.dart 파일 보기

@@ -4,15 +4,20 @@ import 'package:photo/src/provider/i18n_provider.dart';
4 4
 import 'package:photo_manager/photo_manager.dart';
5 5
 
6 6
 class ChangeGalleryDialog extends StatefulWidget {
7
+
8
+  final ValueChanged<AssetPathEntity> onGalleryChange;
7 9
   final List<AssetPathEntity> galleryList;
8 10
   final I18nProvider i18n;
9 11
   final Options options;
12
+  final AssetPathEntity current;
10 13
 
11 14
   const ChangeGalleryDialog({
12 15
     Key key,
16
+    this.onGalleryChange,
13 17
     this.galleryList,
14 18
     this.i18n,
15 19
     this.options,
20
+    this.current,
16 21
   }) : super(key: key);
17 22
 
18 23
   @override
@@ -20,12 +25,19 @@ class ChangeGalleryDialog extends StatefulWidget {
20 25
 }
21 26
 
22 27
 class _ChangeGalleryDialogState extends State<ChangeGalleryDialog> {
28
+      
23 29
   @override
24 30
   Widget build(BuildContext context) {
25 31
     return Container(
26
-      child: ListView.builder(
27
-        itemBuilder: _buildItem,
28
-        itemCount: widget.galleryList.length,
32
+      padding: EdgeInsets.symmetric(horizontal: 16),
33
+      child: MediaQuery.removePadding(
34
+        context: context,
35
+        removeTop: true,
36
+        removeLeft: true,
37
+        child: ListView.builder(
38
+          itemBuilder: _buildItem,
39
+          itemCount: widget.galleryList.length,
40
+        ),
29 41
       ),
30 42
     );
31 43
   }
@@ -41,11 +53,65 @@ class _ChangeGalleryDialogState extends State<ChangeGalleryDialog> {
41 53
     text = text ?? entity.name;
42 54
 
43 55
     return FlatButton(
44
-      child: ListTile(
45
-        title: Text("$text (${entity.assetCount})"),
56
+      padding: EdgeInsets.zero,
57
+      child: Container(
58
+        padding: EdgeInsets.symmetric(vertical: 12),
59
+        decoration: BoxDecoration(
60
+          border: Border(
61
+            top: index == 0 ? BorderSide.none : BorderSide(color: widget.options.theme.dividerColor, width: 1),
62
+          ),
63
+        ),
64
+        child: Row(
65
+          children: [
66
+            // ImageItem(),
67
+            Container(
68
+              width: 40,
69
+              height: 40,
70
+              color: Colors.black,
71
+            ),
72
+            SizedBox(width: 16,),
73
+            Expanded(
74
+              child: Row(
75
+                children: [
76
+                  Expanded(
77
+                    child: Row(
78
+                      children: <Widget>[
79
+                        Text('$text ', style: widget.options.theme.textStyle,),
80
+                        Text('(${entity.assetCount})', style: widget.options.theme.textStyle.copyWith(fontSize: 14),),
81
+                      ],
82
+                    ),
83
+                  ),
84
+                  if (widget.current.id == entity.id)
85
+                  Icon(
86
+                    Icons.check_circle,
87
+                    size: 20,
88
+                    color: widget.options.theme.primaryColor,
89
+                  ),
90
+                  // RichText(
91
+                  //   text: TextSpan(
92
+                  //     text: '$text ',
93
+                  //     style: widget.options.theme.textStyle.copyWith(
94
+                  //       height: 1.4,
95
+                  //     ),
96
+                  //     children: [
97
+                  //       TextSpan(
98
+                  //         text: '(${entity.assetCount})',
99
+                  //         style: widget.options.theme.textStyle.copyWith(
100
+                  //           fontSize: 14,
101
+                  //           height: 1.6,
102
+                  //         ),
103
+                  //       )
104
+                  //     ]
105
+                  //   ),
106
+                  // )
107
+                ],
108
+              )
109
+            ),
110
+          ],
111
+        ),
46 112
       ),
47 113
       onPressed: () {
48
-        Navigator.pop(context, entity);
114
+        widget.onGalleryChange?.call(entity);
49 115
       },
50 116
     );
51 117
   }

+ 85
- 58
lib/src/ui/page/main/bottom_widget.dart 파일 보기

@@ -1,7 +1,6 @@
1 1
 part of '../photo_main_page.dart';
2 2
 
3 3
 class _BottomWidget extends StatefulWidget {
4
-  final ValueChanged<AssetPathEntity> onGalleryChange;
5 4
 
6 5
   final Options options;
7 6
 
@@ -9,20 +8,18 @@ class _BottomWidget extends StatefulWidget {
9 8
 
10 9
   final SelectedProvider selectedProvider;
11 10
 
12
-  final String galleryName;
13
-
14 11
   final GalleryListProvider galleryListProvider;
15 12
   final VoidCallback onTapPreview;
13
+  final VoidCallback onTapSure;
16 14
 
17 15
   const _BottomWidget({
18 16
     Key key,
19
-    this.onGalleryChange,
20 17
     this.options,
21 18
     this.provider,
22 19
     this.selectedProvider,
23
-    this.galleryName = "",
24 20
     this.galleryListProvider,
25 21
     this.onTapPreview,
22
+    this.onTapSure,
26 23
   }) : super(key: key);
27 24
 
28 25
   @override
@@ -34,68 +31,98 @@ class __BottomWidgetState extends State<_BottomWidget> {
34 31
 
35 32
   I18nProvider get i18nProvider => widget.provider;
36 33
 
34
+  SelectedProvider get selectedProvider => widget.selectedProvider;
35
+
36
+  bool get isFullImage => selectedProvider.isFullImage;
37
+
37 38
   @override
38 39
   Widget build(BuildContext context) {
39
-    var textStyle = TextStyle(fontSize: 14.0);
40
-    const textPadding = const EdgeInsets.symmetric(horizontal: 16.0);
41
-    return Container(
42
-      color: options.themeColor,
43
-      child: SafeArea(
44
-        bottom: true,
45
-        top: false,
46
-        child: Container(
47
-          height: 52.0,
48
-          child: Row(
49
-            children: <Widget>[
50
-              FlatButton(
51
-                onPressed: _showGallerySelectDialog,
52
-                splashColor: Colors.transparent,
53
-                child: Container(
54
-                  alignment: Alignment.center,
55
-                  height: 44.0,
56
-                  padding: textPadding,
57
-                  child: Text(
58
-                    widget.galleryName,
59
-                    style: textStyle.copyWith(color: options.textColor),
40
+    return DefaultTextStyle(
41
+      style: options.theme.textStyle,
42
+      child: Container(
43
+        color: options.theme.surfaceColor,
44
+        padding: EdgeInsets.symmetric(horizontal: 24),
45
+        child: SafeArea(
46
+          bottom: true,
47
+          top: false,
48
+          child: Container(
49
+            height: 50.0,
50
+            child: Row(
51
+              children: <Widget>[
52
+                GestureDetector(
53
+                  onTap: widget.onTapPreview,
54
+                  child: Container(
55
+                    alignment: Alignment.center,
56
+                    height: 30.0,
57
+                    child: Text(
58
+                      i18nProvider.getPreviewText(
59
+                          options, widget.selectedProvider),
60
+                      style: options.theme.textStyle.copyWith(
61
+                        color: widget.onTapPreview == null ? options.theme.disableColor : options.theme.textStyle.color,
62
+                      ),
63
+                    ),
64
+                  ),
65
+                ),
66
+                SizedBox(width: 24,),
67
+                Expanded(
68
+                  child: Container(
69
+                    child: Stack(
70
+                      children: <Widget>[
71
+                        IgnorePointer(
72
+                          child: RadioCheckBoxBuilderDelegate().buildCheckBox(
73
+                            context,
74
+                            isFullImage,
75
+                            options,
76
+                            i18nProvider,
77
+                          ),
78
+                        ),
79
+                        Positioned(
80
+                          top: 0.0,
81
+                          bottom: 0.0,
82
+                          left: 0.0,
83
+                          right: 0.0,
84
+                          child: GestureDetector(
85
+                            onTap: () {
86
+                              selectedProvider.isFullImage = !isFullImage;
87
+                              setState(() {});
88
+                            },
89
+                            behavior: HitTestBehavior.translucent,
90
+                            child: Container(),
91
+                          ),
92
+                        ),
93
+                      ],
94
+                    ),
60 95
                   ),
61 96
                 ),
62
-              ),
63
-              Expanded(
64
-                child: Container(),
65
-              ),
66
-              FlatButton(
67
-                onPressed: widget.onTapPreview,
68
-                textColor: options.textColor,
69
-                splashColor: Colors.transparent,
70
-                disabledTextColor: options.disableColor,
71
-                child: Container(
72
-                  height: 44.0,
73
-                  alignment: Alignment.center,
74
-                  child: Text(
75
-                    i18nProvider.getPreviewText(
76
-                        options, widget.selectedProvider),
77
-                    style: textStyle,
97
+                SizedBox(
98
+                  width: 110,
99
+                  height: 36.0,
100
+                  child: FlatButton(
101
+                    onPressed: widget.onTapSure,
102
+                    splashColor: Colors.transparent,
103
+                    color: options.theme.primaryColor,
104
+                    textColor: options.theme.onPrimaryColor,
105
+                    disabledColor: options.theme.disableColor,
106
+                    disabledTextColor: options.theme.disableColor,
107
+                    shape: StadiumBorder(),
108
+                    padding: EdgeInsets.zero,
109
+                    child: Container(
110
+                      alignment: Alignment.center,
111
+                      child: Text(
112
+                        i18nProvider.getSureText(options, selectedProvider.selectedList.length),
113
+                        style: TextStyle(
114
+                          fontSize: options.theme.textStyle.fontSize - 2,
115
+                          fontWeight: options.theme.textStyle.fontWeight,
116
+                        ),
117
+                      ),
118
+                    ),
78 119
                   ),
79
-                  padding: textPadding,
80 120
                 ),
81
-              ),
82
-            ],
121
+              ],
122
+            ),
83 123
           ),
84 124
         ),
85 125
       ),
86 126
     );
87 127
   }
88
-
89
-  void _showGallerySelectDialog() async {
90
-    var result = await showModalBottomSheet(
91
-      context: context,
92
-      builder: (ctx) => ChangeGalleryDialog(
93
-            galleryList: widget.galleryListProvider.galleryPathList,
94
-            i18n: i18nProvider,
95
-            options: options,
96
-          ),
97
-    );
98
-
99
-    if (result != null) widget.onGalleryChange?.call(result);
100
-  }
101 128
 }

+ 20
- 0
lib/src/ui/page/main/empty_widget.dart 파일 보기

@@ -0,0 +1,20 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:photo/src/entity/options.dart';
3
+
4
+abstract class EmptyDelegate {
5
+  const EmptyDelegate();
6
+
7
+  Widget buildEmpty(BuildContext context, Options options);
8
+}
9
+
10
+class DefaultEmptyDelegate extends EmptyDelegate {
11
+  @override
12
+  Widget buildEmpty(BuildContext context, Options options) {
13
+    return Material(
14
+      type: MaterialType.transparency,
15
+      child: Center(
16
+        child: Text('暂无数据'),
17
+      ),
18
+    );
19
+  }
20
+}

+ 344
- 0
lib/src/ui/page/photo_list_page.dart 파일 보기

@@ -0,0 +1,344 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:photo/src/entity/options.dart';
3
+import 'package:photo/src/provider/i18n_provider.dart';
4
+import 'package:photo_manager/photo_manager.dart';
5
+import 'package:photo/src/provider/gallery_list_provider.dart';
6
+import 'package:photo/src/provider/selected_provider.dart';
7
+import 'package:photo/src/provider/asset_provider.dart';
8
+import 'package:photo/src/provider/config_provider.dart';
9
+
10
+import 'photo_main_page.dart';
11
+import 'photo_preview_page.dart';
12
+
13
+
14
+class PhotoListPage extends StatefulWidget {
15
+  final ValueChanged<List<AssetEntity>> onSelected;
16
+  final ListOptions options;
17
+  final List<AssetPathEntity> photoList;
18
+
19
+  const PhotoListPage({
20
+    Key key,
21
+    this.onSelected,
22
+    this.options,
23
+    this.photoList,
24
+  }) : super(key: key);
25
+
26
+  @override
27
+  _PhotoListPageState createState() => _PhotoListPageState();
28
+}
29
+
30
+class _PhotoListPageState extends State<PhotoListPage>
31
+    with SelectedProvider, GalleryListProvider, WidgetsBindingObserver {
32
+  ListOptions get options => widget.options;
33
+  I18nProvider get i18nProvider => PhotoPickerProvider.of(context).provider;
34
+  AssetProvider get assetProvider => PhotoPickerProvider.of(context).assetProvider;
35
+  List<AssetEntity> get list => assetProvider.data;
36
+
37
+  bool _isInit = false;
38
+
39
+  GlobalKey scaffoldKey;
40
+  ScrollController scrollController;
41
+
42
+  bool isPushed = false;
43
+
44
+  // Throttle _changeThrottle;
45
+  
46
+  @override
47
+  void initState() {
48
+    super.initState();
49
+    scaffoldKey = GlobalKey();
50
+    scrollController = ScrollController();
51
+    // WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
52
+    //   var requestPermission = await PhotoManager.requestPermission();
53
+    //   if (requestPermission != true) {
54
+    //     var result = await showDialog(
55
+    //       context: context,
56
+    //       builder: (ctx) => NotPermissionDialog(
57
+    //         provider.getNotPermissionText(),
58
+    //       ),
59
+    //     );
60
+    //     if (result == true) {
61
+    //       PhotoManager.openSetting();
62
+    //     }
63
+    //     return null;
64
+    //   }
65
+    // });
66
+  }
67
+
68
+  @override
69
+  void didChangeDependencies() {
70
+    super.didChangeDependencies();
71
+    if (!_isInit) {
72
+      final pickedList = PhotoPickerProvider.of(context).pickedAssetList ?? [];
73
+      addPickedAsset(pickedList.toList());
74
+      _refreshList();
75
+    }
76
+  }
77
+
78
+  @override
79
+  void dispose() {
80
+    scaffoldKey = null;
81
+    super.dispose();
82
+  }
83
+
84
+  void _refreshList() async {
85
+    await Future.delayed(Duration.zero);
86
+
87
+    _refreshListFromGallery();
88
+  }
89
+
90
+  Future<void> _refreshListFromGallery() async {
91
+    List<AssetPathEntity> pathList;
92
+    switch (options.pickType) {
93
+      case PickType.onlyImage:
94
+        pathList = await PhotoManager.getAssetPathList(type: RequestType.image, onlyAll: true);
95
+        break;
96
+      case PickType.onlyVideo:
97
+        pathList = await PhotoManager.getAssetPathList(type: RequestType.video, onlyAll: true);
98
+        break;
99
+      default:
100
+        pathList = await PhotoManager.getAssetPathList(type: RequestType.image | RequestType.video, onlyAll: true);
101
+    }
102
+
103
+    return _onRefreshAssetPathList(pathList);
104
+  }
105
+
106
+  Future<void> _onRefreshAssetPathList(List<AssetPathEntity> pathList) async {
107
+    if (pathList == null) {
108
+      return;
109
+    }
110
+
111
+    options.sortDelegate.sort(pathList);
112
+
113
+    galleryPathList.clear();
114
+    galleryPathList.addAll(pathList);
115
+
116
+    if (pathList.isNotEmpty) {
117
+      assetProvider.current = pathList[0];
118
+      await assetProvider.loadMore();
119
+    }
120
+
121
+    for (var path in pathList) {
122
+      if (path.isAll) {
123
+        path.name = i18nProvider.getAllGalleryText(options);
124
+      }
125
+    }
126
+
127
+    setState(() {
128
+      _isInit = true;
129
+    });
130
+  }
131
+
132
+  @override
133
+  bool isUpperLimit() {
134
+    var result = selectedCount == options.maxSelected;
135
+    if (result) _showTip(i18nProvider.getMaxTipText(options));
136
+    return result;
137
+  }
138
+
139
+  @override
140
+  void sure() {
141
+    widget.onSelected?.call(selectedList);
142
+  }
143
+
144
+  void _showTip(String msg) {
145
+    if (isPushed) {
146
+      return;
147
+    }
148
+    Scaffold.of(scaffoldKey.currentContext).showSnackBar(
149
+      SnackBar(
150
+        content: Text(
151
+          msg,
152
+          style: options.theme.textStyle.copyWith(color: options.theme.onPrimaryColor),
153
+        ),
154
+        duration: Duration(milliseconds: 1500),
155
+        backgroundColor: options.theme.primaryColor.withOpacity(0.7),
156
+      ),
157
+    );
158
+  }
159
+
160
+  @override
161
+  Widget build(BuildContext context) {
162
+    if (!_isInit) {
163
+      return _buildLoading();
164
+    }
165
+    final noMore = assetProvider.noMore;
166
+
167
+    final count = assetProvider.count + (noMore ? 0 : 1);
168
+
169
+    return ListView.builder(
170
+      scrollDirection: options.scrollDirection,
171
+      physics: ClampingScrollPhysics(),
172
+      itemCount: count,
173
+      itemExtent: options.itemWidth,
174
+      itemBuilder: _buildItem,
175
+    );
176
+  }
177
+
178
+  _loadMore() async {
179
+    await assetProvider.loadMore();
180
+    setState(() {});
181
+  }
182
+
183
+  Widget _buildItem(BuildContext context, int index) {
184
+    final noMore = assetProvider.noMore;
185
+    if (!noMore && index == assetProvider.count) {
186
+      _loadMore();
187
+      return _buildLoading();
188
+    }
189
+
190
+    var data = list[index];
191
+    return RepaintBoundary(
192
+      child: GestureDetector(
193
+        onTap: () => _onItemClick(data, index),
194
+        child: Stack(
195
+          children: <Widget>[
196
+            ImageItem(
197
+              entity: data,
198
+              themeColor: options.theme.primaryColor,
199
+              size: options.thumbSize,
200
+              loadingDelegate: options.loadingDelegate,
201
+              badgeDelegate: options.badgeDelegate,
202
+            ),
203
+            _buildMask(containsEntity(data)),
204
+            _buildSelected(data),
205
+          ],
206
+        ),
207
+      ),
208
+    );
209
+  }
210
+
211
+  _buildMask(bool showMask) {
212
+    return IgnorePointer(
213
+      child: AnimatedContainer(
214
+        color: showMask ? Colors.black.withOpacity(0.5) : Colors.transparent,
215
+        duration: Duration(milliseconds: 300),
216
+      ),
217
+    );
218
+  }
219
+
220
+  Widget _buildSelected(AssetEntity entity) {
221
+    var currentSelected = containsEntity(entity);
222
+    return Positioned(
223
+      right: 0.0,
224
+      width: 36.0,
225
+      height: 36.0,
226
+      child: GestureDetector(
227
+        onTap: () {
228
+          changeCheck(!currentSelected, entity);
229
+        },
230
+        behavior: HitTestBehavior.translucent,
231
+        child: _buildText(entity),
232
+      ),
233
+    );
234
+  }
235
+
236
+  void changeCheck(bool value, AssetEntity entity) {
237
+    if (value) {
238
+      addSelectEntity(entity);
239
+    } else {
240
+      removeSelectEntity(entity);
241
+    }
242
+    sure();
243
+    setState(() {});
244
+  }
245
+
246
+  Widget _buildText(AssetEntity entity) {
247
+    var isSelected = containsEntity(entity);
248
+    Widget child;
249
+    BoxDecoration decoration = BoxDecoration(borderRadius: BorderRadius.circular(1.0),);
250
+    if (isSelected) {
251
+      child = Text(
252
+        (indexOfSelected(entity) + 1).toString(),
253
+        textAlign: TextAlign.center,
254
+        style: options.theme.textStyle.apply(
255
+          fontSizeDelta: -4,
256
+          color: options.theme.onPrimaryColor,
257
+        ),
258
+      );
259
+      decoration = decoration.copyWith(color: options.theme.primaryColor);
260
+    } else {
261
+      decoration = decoration.copyWith(border: Border.all(
262
+        color: options.theme.onPrimaryColor.withAlpha(200),
263
+      ),);
264
+    }
265
+    return Padding(
266
+      padding: const EdgeInsets.all(8.0),
267
+      child: AnimatedContainer(
268
+        duration: Duration(milliseconds: 300),
269
+        decoration: decoration,
270
+        alignment: Alignment.center,
271
+        child: child,
272
+      ),
273
+    );
274
+  }
275
+
276
+  void _onItemClick(AssetEntity data, int index) {
277
+    var result = PhotoPreviewResult();
278
+    isPushed = true;
279
+    Navigator.of(context).push(
280
+      MaterialPageRoute(
281
+        builder: (ctx) {
282
+          return PhotoPickerProvider(
283
+            provider: PhotoPickerProvider.of(context).provider,
284
+            options: options,
285
+            child: PhotoPreviewPage(
286
+              selectedProvider: this,
287
+              list: List.of(list),
288
+              initIndex: index,
289
+              changeProviderOnCheckChange: true,
290
+              result: result,
291
+              isPreview: false,
292
+              assetProvider: assetProvider,
293
+            ),
294
+          );
295
+        },
296
+      ),
297
+    ).then((v) {
298
+      if (handlePreviewResult(v)) {
299
+        Navigator.pop(context, v);
300
+        return;
301
+      }
302
+      isPushed = false;
303
+      setState(() {});
304
+    });
305
+  }
306
+
307
+  bool handlePreviewResult(List<AssetEntity> v) {
308
+    if (v == null) {
309
+      return false;
310
+    }
311
+    if (v is List<AssetEntity>) {
312
+      return true;
313
+    }
314
+    return false;
315
+  }
316
+
317
+  Widget _buildLoading() {
318
+    return Center(
319
+      child: Column(
320
+        children: <Widget>[
321
+          Container(
322
+            width: 40.0,
323
+            height: 40.0,
324
+            padding: const EdgeInsets.all(5.0),
325
+            child: CircularProgressIndicator(
326
+              valueColor: AlwaysStoppedAnimation(Colors.black),
327
+            ),
328
+          ),
329
+          Padding(
330
+            padding: const EdgeInsets.all(8.0),
331
+            child: Text(
332
+              i18nProvider.loadingText(),
333
+              style: const TextStyle(
334
+                fontSize: 12.0,
335
+              ),
336
+            ),
337
+          ),
338
+        ],
339
+        crossAxisAlignment: CrossAxisAlignment.center,
340
+        mainAxisAlignment: MainAxisAlignment.center,
341
+      ),
342
+    );
343
+  }
344
+}

+ 115
- 93
lib/src/ui/page/photo_main_page.dart 파일 보기

@@ -3,6 +3,7 @@ import 'dart:typed_data';
3 3
 
4 4
 import 'package:flutter/material.dart';
5 5
 import 'package:photo/src/delegate/badge_delegate.dart';
6
+import 'package:photo/src/delegate/checkbox_builder_delegate.dart';
6 7
 import 'package:photo/src/delegate/loading_delegate.dart';
7 8
 import 'package:photo/src/engine/lru_cache.dart';
8 9
 import 'package:photo/src/engine/throttle.dart';
@@ -13,6 +14,7 @@ import 'package:photo/src/provider/gallery_list_provider.dart';
13 14
 import 'package:photo/src/provider/i18n_provider.dart';
14 15
 import 'package:photo/src/provider/selected_provider.dart';
15 16
 import 'package:photo/src/ui/dialog/change_gallery_dialog.dart';
17
+import 'package:photo/src/ui/page/main/empty_widget.dart';
16 18
 import 'package:photo/src/ui/page/photo_preview_page.dart';
17 19
 import 'package:photo_manager/photo_manager.dart';
18 20
 
@@ -45,28 +47,13 @@ class _PhotoMainPageState extends State<PhotoMainPage>
45 47
 
46 48
   List<AssetEntity> get list => assetProvider.data;
47 49
 
48
-  Color get themeColor => options.themeColor;
49
-
50
-  AssetPathEntity _currentPath;
51
-
52 50
   bool _isInit = false;
53 51
 
54
-  AssetPathEntity get currentPath {
55
-    if (_currentPath == null) {
56
-      return null;
57
-    }
58
-    return _currentPath;
59
-  }
60
-
61
-  set currentPath(AssetPathEntity value) {
62
-    _currentPath = value;
63
-  }
64
-
65 52
   String get currentGalleryName {
66
-    if (currentPath?.isAll == true) {
53
+    if (assetProvider.current?.isAll == true) {
67 54
       return i18nProvider.getAllGalleryText(options);
68 55
     }
69
-    return currentPath?.name ?? "Select Folder";
56
+    return assetProvider.current?.name ?? "Select Folder";
70 57
   }
71 58
 
72 59
   GlobalKey scaffoldKey;
@@ -78,6 +65,8 @@ class _PhotoMainPageState extends State<PhotoMainPage>
78 65
 
79 66
   Throttle _changeThrottle;
80 67
 
68
+  OverlayEntry overlayEntry;
69
+
81 70
   @override
82 71
   void initState() {
83 72
     super.initState();
@@ -107,61 +96,106 @@ class _PhotoMainPageState extends State<PhotoMainPage>
107 96
     super.dispose();
108 97
   }
109 98
 
110
-  @override
111
-  Widget build(BuildContext context) {
112
-    var textStyle = TextStyle(
113
-      color: options.textColor,
114
-      fontSize: 14.0,
115
-    );
116
-    return Theme(
117
-      data: Theme.of(context).copyWith(primaryColor: options.themeColor),
118
-      child: DefaultTextStyle(
119
-        style: textStyle,
120
-        child: Scaffold(
121
-          appBar: AppBar(
122
-            leading: IconButton(
123
-              icon: Icon(
124
-                Icons.close,
125
-                color: options.textColor,
126
-              ),
127
-              onPressed: _cancel,
128
-            ),
129
-            title: Text(
130
-              i18nProvider.getTitleText(options),
131
-              style: TextStyle(
132
-                color: options.textColor,
99
+  void _closeGallerySelectDialog() {
100
+    overlayEntry.remove();
101
+    overlayEntry = null;
102
+  }
103
+
104
+  void _showGallerySelectDialog() {
105
+    if (overlayEntry != null) {
106
+      _closeGallerySelectDialog();
107
+      return;
108
+    }
109
+    overlayEntry = OverlayEntry(builder: (context) {
110
+      return Positioned(
111
+        top: kToolbarHeight + MediaQuery.of(context).padding.top,
112
+        right: 0,
113
+        left: 0,
114
+        bottom: 0,
115
+        child: WillPopScope(
116
+          onWillPop: () async {
117
+            _closeGallerySelectDialog();
118
+            return false;
119
+          },
120
+          child: Stack(
121
+            children: <Widget>[
122
+              GestureDetector(
123
+                onTap: _closeGallerySelectDialog,
124
+                child: Container(
125
+                  height: double.infinity,
126
+                  color: Color(0x66000000),
127
+                ),
133 128
               ),
134
-            ),
135
-            actions: <Widget>[
136
-              FlatButton(
137
-                splashColor: Colors.transparent,
138
-                child: Text(
139
-                  i18nProvider.getSureText(options, selectedCount),
140
-                  style: selectedCount == 0
141
-                      ? textStyle.copyWith(color: options.disableColor)
142
-                      : textStyle,
129
+              Material(
130
+                color: options.theme.surfaceColor,
131
+                child: Container(
132
+                  height: MediaQuery.of(context).size.height * 0.5 - MediaQuery.of(context).padding.top,
133
+                  child: ChangeGalleryDialog(
134
+                    galleryList: galleryPathList,
135
+                    i18n: i18nProvider,
136
+                    options: options,
137
+                    current: assetProvider.current,
138
+                    onGalleryChange: (AssetPathEntity result) {
139
+                      _onGalleryChange?.call(result);
140
+                    }
141
+                  ),
143 142
                 ),
144
-                onPressed: selectedCount == 0 ? null : sure,
145 143
               ),
146 144
             ],
147 145
           ),
148
-          body: _buildBody(),
149
-          bottomNavigationBar: _BottomWidget(
150
-            key: scaffoldKey,
151
-            provider: i18nProvider,
152
-            options: options,
153
-            galleryName: currentGalleryName,
154
-            onGalleryChange: _onGalleryChange,
155
-            onTapPreview: selectedList.isEmpty ? null : _onTapPreview,
156
-            selectedProvider: this,
157
-            galleryListProvider: this,
146
+        ),
147
+      );
148
+    });
149
+    Overlay.of(context).insert(overlayEntry);
150
+  }
151
+
152
+  @override
153
+  Widget build(BuildContext context) {
154
+    return DefaultTextStyle(
155
+      style: options.theme.textStyle,
156
+      child: Scaffold(
157
+        appBar: AppBar(
158
+          backgroundColor: options.theme.surfaceColor,
159
+          leading: IconButton(
160
+            icon: Text('取消', style: options.theme.textStyle),
161
+            onPressed: _cancel,
162
+          ),
163
+          centerTitle: true,
164
+          title: FlatButton(
165
+            padding: EdgeInsets.zero,
166
+            onPressed: _showGallerySelectDialog,
167
+            splashColor: Colors.transparent,
168
+            highlightColor: Colors.transparent,
169
+            child: Container(
170
+              alignment: Alignment.center,
171
+              height: 44.0,
172
+              padding: EdgeInsets.zero.copyWith(right: kToolbarHeight),
173
+              child: Text(currentGalleryName, style: options.theme.textStyle.apply(
174
+                fontSizeDelta: 2.0,
175
+                fontWeightDelta: 1,
176
+              ),),
177
+            ),
158 178
           ),
159 179
         ),
180
+        body: _buildBody(),
181
+        bottomNavigationBar: _BottomWidget(
182
+          key: scaffoldKey,
183
+          provider: i18nProvider,
184
+          options: options,
185
+          onTapPreview: selectedList.isEmpty ? null : _onTapPreview,
186
+          onTapSure: selectedList.isEmpty ? null : sure,
187
+          selectedProvider: this,
188
+          galleryListProvider: this,
189
+        ),
160 190
       ),
161 191
     );
162 192
   }
163 193
 
164 194
   void _cancel() {
195
+    if (overlayEntry != null) {
196
+      _closeGallerySelectDialog();
197
+      return;
198
+    }
165 199
     selectedList.clear();
166 200
     widget.onClose(selectedList);
167 201
   }
@@ -185,19 +219,17 @@ class _PhotoMainPageState extends State<PhotoMainPage>
185 219
       SnackBar(
186 220
         content: Text(
187 221
           msg,
188
-          style: TextStyle(
189
-            color: options.textColor,
190
-            fontSize: 14.0,
191
-          ),
222
+          style: options.theme.textStyle.copyWith(color: options.theme.onPrimaryColor),
192 223
         ),
193 224
         duration: Duration(milliseconds: 1500),
194
-        backgroundColor: themeColor.withOpacity(0.7),
225
+        backgroundColor: options.theme.primaryColor.withOpacity(0.7),
195 226
       ),
196 227
     );
197 228
   }
198 229
 
199 230
   void _refreshList() async {
200 231
     await Future.delayed(Duration.zero);
232
+    print(useAlbum);
201 233
     if (!useAlbum) {
202 234
       _refreshListFromWidget();
203 235
       return;
@@ -217,7 +249,7 @@ class _PhotoMainPageState extends State<PhotoMainPage>
217 249
         pathList = await PhotoManager.getAssetPathList(type: RequestType.image);
218 250
         break;
219 251
       case PickType.onlyVideo:
220
-        pathList = await PhotoManager.getAssetPathList(type: RequestType.image);
252
+        pathList = await PhotoManager.getAssetPathList(type: RequestType.video);
221 253
         break;
222 254
       default:
223 255
         pathList = await PhotoManager.getAssetPathList(
@@ -263,8 +295,9 @@ class _PhotoMainPageState extends State<PhotoMainPage>
263 295
     final count = assetProvider.count + (noMore ? 0 : 1);
264 296
 
265 297
     return Container(
266
-      color: options.dividerColor,
267
-      child: GridView.builder(
298
+      color: options.theme.backgroundColor,
299
+      padding: EdgeInsets.all(5),
300
+      child: assetProvider.count == 0 ? DefaultEmptyDelegate().buildEmpty(context, options) : GridView.builder(
268 301
         controller: scrollController,
269 302
         gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
270 303
           crossAxisCount: options.rowCount,
@@ -293,7 +326,7 @@ class _PhotoMainPageState extends State<PhotoMainPage>
293 326
           children: <Widget>[
294 327
             ImageItem(
295 328
               entity: data,
296
-              themeColor: themeColor,
329
+              themeColor: options.theme.primaryColor,
297 330
               size: options.thumbSize,
298 331
               loadingDelegate: options.loadingDelegate,
299 332
               badgeDelegate: options.badgeDelegate,
@@ -339,24 +372,21 @@ class _PhotoMainPageState extends State<PhotoMainPage>
339 372
   Widget _buildText(AssetEntity entity) {
340 373
     var isSelected = containsEntity(entity);
341 374
     Widget child;
342
-    BoxDecoration decoration;
375
+    BoxDecoration decoration = BoxDecoration(borderRadius: BorderRadius.circular(1.0),);
343 376
     if (isSelected) {
344 377
       child = Text(
345 378
         (indexOfSelected(entity) + 1).toString(),
346 379
         textAlign: TextAlign.center,
347
-        style: TextStyle(
348
-          fontSize: 12.0,
349
-          color: options.textColor,
380
+        style: options.theme.textStyle.apply(
381
+          fontSizeDelta: -4,
382
+          color: options.theme.onPrimaryColor,
350 383
         ),
351 384
       );
352
-      decoration = BoxDecoration(color: themeColor);
385
+      decoration = decoration.copyWith(color: options.theme.primaryColor);
353 386
     } else {
354
-      decoration = BoxDecoration(
355
-        borderRadius: BorderRadius.circular(1.0),
356
-        border: Border.all(
357
-          color: themeColor,
358
-        ),
359
-      );
387
+      decoration = decoration.copyWith(border: Border.all(
388
+        color: options.theme.onPrimaryColor.withAlpha(200),
389
+      ),);
360 390
     }
361 391
     return Padding(
362 392
       padding: const EdgeInsets.all(8.0),
@@ -379,16 +409,7 @@ class _PhotoMainPageState extends State<PhotoMainPage>
379 409
   }
380 410
 
381 411
   void _onGalleryChange(AssetPathEntity assetPathEntity) async {
382
-    // _currentPath = assetPathEntity;
383
-
384
-    // _currentPath.assetList.then((v) async {
385
-    //   _sortAssetList(v);
386
-    //   list.clear();
387
-    //   list.addAll(v);
388
-    //   scrollController.jumpTo(0.0);
389
-    //   await checkPickImageEntity();
390
-    //   setState(() {});
391
-    // });
412
+    _closeGallerySelectDialog();
392 413
     if (assetPathEntity != assetProvider.current) {
393 414
       assetProvider.current = assetPathEntity;
394 415
       await assetProvider.loadMore();
@@ -453,6 +474,7 @@ class _PhotoMainPageState extends State<PhotoMainPage>
453 474
     }
454 475
     isPushed = false;
455 476
     compareAndRemoveEntities(result.previewSelectedList);
477
+    setState(() {});
456 478
   }
457 479
 
458 480
   bool handlePreviewResult(List<AssetEntity> v) {
@@ -474,7 +496,7 @@ class _PhotoMainPageState extends State<PhotoMainPage>
474 496
             height: 40.0,
475 497
             padding: const EdgeInsets.all(5.0),
476 498
             child: CircularProgressIndicator(
477
-              valueColor: AlwaysStoppedAnimation(themeColor),
499
+              valueColor: AlwaysStoppedAnimation(options.theme.primaryColor),
478 500
             ),
479 501
           ),
480 502
           Padding(
@@ -519,7 +541,7 @@ class _PhotoMainPageState extends State<PhotoMainPage>
519 541
     this.galleryPathList.clear();
520 542
     this.galleryPathList.addAll(pathList);
521 543
 
522
-    if (!this.galleryPathList.contains(this.currentPath)) {
544
+    if (!this.galleryPathList.contains(assetProvider.current)) {
523 545
       // current path is deleted , 当前的相册被删除, 应该提示刷新
524 546
       if (this.galleryPathList.length > 0) {
525 547
         _onGalleryChange(this.galleryPathList[0]);
@@ -527,6 +549,6 @@ class _PhotoMainPageState extends State<PhotoMainPage>
527 549
       return;
528 550
     }
529 551
     // Not deleted
530
-    _onGalleryChange(this.currentPath);
552
+    _onGalleryChange(assetProvider.current);
531 553
   }
532 554
 }

+ 94
- 59
lib/src/ui/page/photo_preview_page.dart 파일 보기

@@ -2,6 +2,7 @@ import 'dart:async';
2 2
 import 'dart:typed_data';
3 3
 
4 4
 import 'package:flutter/material.dart';
5
+import 'package:photo/src/delegate/checkbox_builder_delegate.dart';
5 6
 import 'package:photo/src/entity/options.dart';
6 7
 import 'package:photo/src/provider/asset_provider.dart';
7 8
 import 'package:photo/src/provider/config_provider.dart';
@@ -48,10 +49,6 @@ class _PhotoPreviewPageState extends State<PhotoPreviewPage> {
48 49
 
49 50
   Options get options => config.options;
50 51
 
51
-  Color get themeColor => options.themeColor;
52
-
53
-  Color get textColor => options.textColor;
54
-
55 52
   SelectedProvider get selectedProvider => widget.selectedProvider;
56 53
 
57 54
   List<AssetEntity> get list {
@@ -63,8 +60,12 @@ class _PhotoPreviewPageState extends State<PhotoPreviewPage> {
63 60
 
64 61
   StreamController<int> pageChangeController = StreamController.broadcast();
65 62
 
63
+  StreamController<bool> isFullChangeController = StreamController.broadcast();
64
+
66 65
   Stream<int> get pageStream => pageChangeController.stream;
67 66
 
67
+  Stream<bool> get isFullStream => isFullChangeController.stream;
68
+
68 69
   bool get changeProviderOnCheckChange => widget.changeProviderOnCheckChange;
69 70
 
70 71
   PhotoPreviewResult get result => widget.result;
@@ -116,72 +117,68 @@ class _PhotoPreviewPageState extends State<PhotoPreviewPage> {
116 117
     } else {
117 118
       totalCount = list.length;
118 119
     }
119
-
120
-    var data = Theme.of(context);
121
-    var textStyle = TextStyle(
122
-      color: options.textColor,
123
-      fontSize: 14.0,
124
-    );
125
-    return Theme(
126
-      data: data.copyWith(
127
-        primaryColor: options.themeColor,
128
-      ),
129
-      child: Scaffold(
130
-        appBar: AppBar(
131
-          backgroundColor: config.options.themeColor,
132
-          leading: BackButton(
133
-            color: options.textColor,
134
-          ),
135
-          title: StreamBuilder(
120
+    return Scaffold(
121
+      backgroundColor: options.theme.backgroundColor,
122
+      appBar: AppBar(
123
+        backgroundColor: options.theme.surfaceColor,
124
+        leading: BackButton(
125
+          color: options.theme.textStyle.color,
126
+        ),
127
+        title: StreamBuilder(
128
+          stream: pageStream,
129
+          initialData: widget.initIndex,
130
+          builder: (ctx, snap) {
131
+            return Text(
132
+              "${snap.data + 1}/$totalCount",
133
+              style: options.theme.textStyle.apply(
134
+                fontSizeDelta: 2,
135
+                fontWeightDelta: 1,
136
+              ),
137
+            );
138
+          },
139
+        ),
140
+        actions: <Widget>[
141
+          StreamBuilder(
136 142
             stream: pageStream,
137
-            initialData: widget.initIndex,
138
-            builder: (ctx, snap) {
139
-              return Text(
140
-                "${snap.data + 1}/$totalCount",
143
+            builder: (ctx, s) => FlatButton(
144
+              splashColor: Colors.transparent,
145
+              onPressed: selectedList.length == 0 ? null : sure,
146
+              disabledTextColor: options.theme.disableColor,
147
+              textColor: options.theme.textStyle.color,
148
+              child: Text(
149
+                config.provider.getSureText(options, selectedList.length),
141 150
                 style: TextStyle(
142
-                  color: options.textColor,
143
-                ),
144
-              );
145
-            },
146
-          ),
147
-          actions: <Widget>[
148
-            StreamBuilder(
149
-              stream: pageStream,
150
-              builder: (ctx, s) => FlatButton(
151
-                splashColor: Colors.transparent,
152
-                onPressed: selectedList.length == 0 ? null : sure,
153
-                child: Text(
154
-                  config.provider.getSureText(options, selectedList.length),
155
-                  style: selectedList.length == 0
156
-                      ? textStyle.copyWith(color: options.disableColor)
157
-                      : textStyle,
151
+                  fontSize: options.theme.textStyle.fontSize,
152
+                  fontWeight: options.theme.textStyle.fontWeight,
158 153
                 ),
159 154
               ),
160 155
             ),
161
-          ],
162
-        ),
163
-        body: PageView.builder(
164
-          controller: pageController,
165
-          itemBuilder: _buildItem,
166
-          itemCount: totalCount,
167
-          onPageChanged: _onPageChanged,
168
-        ),
169
-        bottomSheet: _buildThumb(),
170
-        bottomNavigationBar: _buildBottom(),
156
+          ),
157
+        ],
171 158
       ),
159
+      body: PageView.builder(
160
+        controller: pageController,
161
+        itemBuilder: _buildItem,
162
+        itemCount: totalCount,
163
+        onPageChanged: _onPageChanged,
164
+      ),
165
+      bottomSheet: _buildThumb(),
166
+      bottomNavigationBar: _buildBottom(),
172 167
     );
173 168
   }
174 169
 
175 170
   Widget _buildBottom() {
176 171
     return Container(
177
-      color: themeColor,
172
+      color: options.theme.surfaceColor,
178 173
       child: SafeArea(
179 174
         child: Container(
180
-          height: 52.0,
175
+          height: 50.0,
176
+          padding: EdgeInsets.symmetric(horizontal: 24),
181 177
           child: Row(
178
+            crossAxisAlignment: CrossAxisAlignment.center,
182 179
             children: <Widget>[
183 180
               Expanded(
184
-                child: Container(),
181
+                child: _buildOriginalRadio(),
185 182
               ),
186 183
               _buildCheckbox(),
187 184
             ],
@@ -193,9 +190,6 @@ class _PhotoPreviewPageState extends State<PhotoPreviewPage> {
193 190
 
194 191
   Container _buildCheckbox() {
195 192
     return Container(
196
-      constraints: BoxConstraints(
197
-        maxWidth: 150.0,
198
-      ),
199 193
       child: StreamBuilder<int>(
200 194
         builder: (ctx, snapshot) {
201 195
           var index = snapshot.data;
@@ -226,6 +220,47 @@ class _PhotoPreviewPageState extends State<PhotoPreviewPage> {
226 220
     );
227 221
   }
228 222
 
223
+  Widget _buildOriginalRadio() {
224
+    return Container(
225
+      child: StreamBuilder<bool>(
226
+        builder: (ctx, snapshot) {
227
+          return Stack(
228
+            children: <Widget>[
229
+              IgnorePointer(
230
+                child: _buildOriginalRadioContent(snapshot.data),
231
+              ),
232
+              Positioned(
233
+                top: 0.0,
234
+                bottom: 0.0,
235
+                left: 0.0,
236
+                right: 0.0,
237
+                child: GestureDetector(
238
+                  onTap: () {
239
+                    widget.selectedProvider.isFullImage = !snapshot.data;
240
+                    isFullChangeController.add(selectedProvider.isFullImage);
241
+                  },
242
+                  behavior: HitTestBehavior.translucent,
243
+                  child: Container(),
244
+                ),
245
+              ),
246
+            ],
247
+          );
248
+        },
249
+        initialData: selectedProvider.isFullImage,
250
+        stream: isFullStream,
251
+      ),
252
+    );
253
+  }
254
+
255
+  Widget _buildOriginalRadioContent(bool checked) {
256
+    return RadioCheckBoxBuilderDelegate().buildCheckBox(
257
+      context,
258
+      checked,
259
+      options,
260
+      config.provider,
261
+    );
262
+  }
263
+
229 264
   Widget _buildCheckboxContent(bool checked, int index) {
230 265
     return options.checkBoxBuilderDelegate.buildCheckBox(
231 266
       context,
@@ -284,7 +319,7 @@ class _PhotoPreviewPageState extends State<PhotoPreviewPage> {
284 319
 
285 320
   Widget _buildLoadingWidget(AssetEntity entity) {
286 321
     return options.loadingDelegate
287
-        .buildBigImageLoading(context, entity, themeColor);
322
+        .buildBigImageLoading(context, entity, options.theme.primaryColor);
288 323
   }
289 324
 
290 325
   void _onPageChanged(int value) {
@@ -315,7 +350,7 @@ class _PhotoPreviewPageState extends State<PhotoPreviewPage> {
315 350
           child: Stack(
316 351
             children: <Widget>[
317 352
               ImageItem(
318
-                themeColor: themeColor,
353
+                themeColor: options.theme.primaryColor,
319 354
                 entity: item,
320 355
                 size: options.thumbSize,
321 356
                 loadingDelegate: options.loadingDelegate,

+ 38
- 45
pubspec.lock 파일 보기

@@ -5,58 +5,58 @@ packages:
5 5
     dependency: transitive
6 6
     description:
7 7
       name: archive
8
-      url: "https://pub.dartlang.org"
8
+      url: "https://pub.flutter-io.cn"
9 9
     source: hosted
10
-    version: "2.0.11"
10
+    version: "2.0.13"
11 11
   args:
12 12
     dependency: transitive
13 13
     description:
14 14
       name: args
15
-      url: "https://pub.dartlang.org"
15
+      url: "https://pub.flutter-io.cn"
16 16
     source: hosted
17
-    version: "1.5.2"
17
+    version: "1.6.0"
18 18
   async:
19 19
     dependency: transitive
20 20
     description:
21 21
       name: async
22
-      url: "https://pub.dartlang.org"
22
+      url: "https://pub.flutter-io.cn"
23 23
     source: hosted
24
-    version: "2.4.0"
24
+    version: "2.4.1"
25 25
   boolean_selector:
26 26
     dependency: transitive
27 27
     description:
28 28
       name: boolean_selector
29
-      url: "https://pub.dartlang.org"
29
+      url: "https://pub.flutter-io.cn"
30 30
     source: hosted
31
-    version: "1.0.5"
31
+    version: "2.0.0"
32 32
   charcode:
33 33
     dependency: transitive
34 34
     description:
35 35
       name: charcode
36
-      url: "https://pub.dartlang.org"
36
+      url: "https://pub.flutter-io.cn"
37 37
     source: hosted
38
-    version: "1.1.2"
38
+    version: "1.1.3"
39 39
   collection:
40 40
     dependency: transitive
41 41
     description:
42 42
       name: collection
43
-      url: "https://pub.dartlang.org"
43
+      url: "https://pub.flutter-io.cn"
44 44
     source: hosted
45
-    version: "1.14.11"
45
+    version: "1.14.12"
46 46
   convert:
47 47
     dependency: transitive
48 48
     description:
49 49
       name: convert
50
-      url: "https://pub.dartlang.org"
50
+      url: "https://pub.flutter-io.cn"
51 51
     source: hosted
52 52
     version: "2.1.1"
53 53
   crypto:
54 54
     dependency: transitive
55 55
     description:
56 56
       name: crypto
57
-      url: "https://pub.dartlang.org"
57
+      url: "https://pub.flutter-io.cn"
58 58
     source: hosted
59
-    version: "2.1.3"
59
+    version: "2.1.4"
60 60
   flutter:
61 61
     dependency: "direct main"
62 62
     description: flutter
@@ -71,58 +71,51 @@ packages:
71 71
     dependency: transitive
72 72
     description:
73 73
       name: image
74
-      url: "https://pub.dartlang.org"
74
+      url: "https://pub.flutter-io.cn"
75 75
     source: hosted
76
-    version: "2.1.4"
76
+    version: "2.1.12"
77 77
   matcher:
78 78
     dependency: transitive
79 79
     description:
80 80
       name: matcher
81
-      url: "https://pub.dartlang.org"
81
+      url: "https://pub.flutter-io.cn"
82 82
     source: hosted
83 83
     version: "0.12.6"
84 84
   meta:
85 85
     dependency: transitive
86 86
     description:
87 87
       name: meta
88
-      url: "https://pub.dartlang.org"
88
+      url: "https://pub.flutter-io.cn"
89 89
     source: hosted
90 90
     version: "1.1.8"
91 91
   path:
92 92
     dependency: transitive
93 93
     description:
94 94
       name: path
95
-      url: "https://pub.dartlang.org"
95
+      url: "https://pub.flutter-io.cn"
96 96
     source: hosted
97 97
     version: "1.6.4"
98
-  pedantic:
99
-    dependency: transitive
100
-    description:
101
-      name: pedantic
102
-      url: "https://pub.dartlang.org"
103
-    source: hosted
104
-    version: "1.8.0+1"
105 98
   petitparser:
106 99
     dependency: transitive
107 100
     description:
108 101
       name: petitparser
109
-      url: "https://pub.dartlang.org"
102
+      url: "https://pub.flutter-io.cn"
110 103
     source: hosted
111 104
     version: "2.4.0"
112 105
   photo_manager:
113 106
     dependency: "direct main"
114 107
     description:
115 108
       name: photo_manager
116
-      url: "https://pub.dartlang.org"
109
+      url: "https://pub.flutter-io.cn"
117 110
     source: hosted
118
-    version: "0.5.1-dev.2"
111
+    version: "0.5.1-dev.4"
119 112
   quiver:
120 113
     dependency: transitive
121 114
     description:
122 115
       name: quiver
123
-      url: "https://pub.dartlang.org"
116
+      url: "https://pub.flutter-io.cn"
124 117
     source: hosted
125
-    version: "2.0.5"
118
+    version: "2.1.3"
126 119
   sky_engine:
127 120
     dependency: transitive
128 121
     description: flutter
@@ -132,64 +125,64 @@ packages:
132 125
     dependency: transitive
133 126
     description:
134 127
       name: source_span
135
-      url: "https://pub.dartlang.org"
128
+      url: "https://pub.flutter-io.cn"
136 129
     source: hosted
137
-    version: "1.5.5"
130
+    version: "1.7.0"
138 131
   stack_trace:
139 132
     dependency: transitive
140 133
     description:
141 134
       name: stack_trace
142
-      url: "https://pub.dartlang.org"
135
+      url: "https://pub.flutter-io.cn"
143 136
     source: hosted
144 137
     version: "1.9.3"
145 138
   stream_channel:
146 139
     dependency: transitive
147 140
     description:
148 141
       name: stream_channel
149
-      url: "https://pub.dartlang.org"
142
+      url: "https://pub.flutter-io.cn"
150 143
     source: hosted
151 144
     version: "2.0.0"
152 145
   string_scanner:
153 146
     dependency: transitive
154 147
     description:
155 148
       name: string_scanner
156
-      url: "https://pub.dartlang.org"
149
+      url: "https://pub.flutter-io.cn"
157 150
     source: hosted
158 151
     version: "1.0.5"
159 152
   term_glyph:
160 153
     dependency: transitive
161 154
     description:
162 155
       name: term_glyph
163
-      url: "https://pub.dartlang.org"
156
+      url: "https://pub.flutter-io.cn"
164 157
     source: hosted
165 158
     version: "1.1.0"
166 159
   test_api:
167 160
     dependency: transitive
168 161
     description:
169 162
       name: test_api
170
-      url: "https://pub.dartlang.org"
163
+      url: "https://pub.flutter-io.cn"
171 164
     source: hosted
172
-    version: "0.2.11"
165
+    version: "0.2.15"
173 166
   typed_data:
174 167
     dependency: transitive
175 168
     description:
176 169
       name: typed_data
177
-      url: "https://pub.dartlang.org"
170
+      url: "https://pub.flutter-io.cn"
178 171
     source: hosted
179 172
     version: "1.1.6"
180 173
   vector_math:
181 174
     dependency: transitive
182 175
     description:
183 176
       name: vector_math
184
-      url: "https://pub.dartlang.org"
177
+      url: "https://pub.flutter-io.cn"
185 178
     source: hosted
186 179
     version: "2.0.8"
187 180
   xml:
188 181
     dependency: transitive
189 182
     description:
190 183
       name: xml
191
-      url: "https://pub.dartlang.org"
184
+      url: "https://pub.flutter-io.cn"
192 185
     source: hosted
193
-    version: "3.5.0"
186
+    version: "3.6.1"
194 187
 sdks:
195
-  dart: ">=2.4.0 <3.0.0"
188
+  dart: ">=2.7.0 <3.0.0"

+ 1
- 1
pubspec.yaml 파일 보기

@@ -5,7 +5,7 @@ author: caijinglong<cjl_spy@163.com>
5 5
 homepage: https://github.com/CaiJingLong/flutter_photo
6 6
 
7 7
 environment:
8
-  sdk: ">=2.0.0-dev.68.0 <3.0.0"
8
+  sdk: ">=2.7.0 <3.0.0"
9 9
 
10 10
 dependencies:
11 11
   flutter: