@@ -12,6 +12,7 @@ import 'package:flutter/services.dart';
12
12
13
13
import '../constants/constants.dart' ;
14
14
import '../widget/builder/asset_entity_grid_item_builder.dart' ;
15
+ import '../widget/builder/value_listenable_builder_2.dart' ;
15
16
16
17
typedef IndicatorBuilder = Widget Function (
17
18
BuildContext context,
@@ -27,6 +28,7 @@ typedef IndicatorBuilder = Widget Function(
27
28
abstract class AssetPickerBuilderDelegate <A , P > {
28
29
AssetPickerBuilderDelegate ({
29
30
required this .provider,
31
+ required this .initialPermission,
30
32
this .gridCount = 4 ,
31
33
Color ? themeColor,
32
34
AssetsPickerTextDelegate ? textDelegate,
@@ -48,6 +50,10 @@ abstract class AssetPickerBuilderDelegate<A, P> {
48
50
/// 资源选择器状态保持
49
51
final AssetPickerProvider <A , P > provider;
50
52
53
+ /// The [PermissionState] when the picker is called.
54
+ /// 当选择器被拉起时的权限状态
55
+ final PermissionState initialPermission;
56
+
51
57
/// Assets count for the picker.
52
58
/// 资源网格数
53
59
final int gridCount;
@@ -116,12 +122,25 @@ abstract class AssetPickerBuilderDelegate<A, P> {
116
122
117
123
/// Blur radius in Apple OS layout mode.
118
124
/// 苹果系列系统布局方式下的模糊度
119
- double get appleOSBlurRadius => 15 .0 ;
125
+ double get appleOSBlurRadius => 10 .0 ;
120
126
121
127
/// Height for bottom action bar.
122
128
/// 底部操作栏的高度
123
129
double get bottomActionBarHeight => kToolbarHeight / 1.1 ;
124
130
131
+ /// Notifier for the current [PermissionState] .
132
+ /// 当前 [PermissionState] 的监听
133
+ late final ValueNotifier <PermissionState > permission =
134
+ ValueNotifier <PermissionState >(
135
+ initialPermission,
136
+ );
137
+ final ValueNotifier <bool > permissionOverlayHidden =
138
+ ValueNotifier <bool >(false );
139
+
140
+ /// Whether the permission is limited currently.
141
+ /// 当前的权限是否为受限
142
+ bool get isPermissionLimited => permission.value == PermissionState .limited;
143
+
125
144
/// Path entity select widget builder.
126
145
/// 路径选择部件构建
127
146
Widget pathEntitySelector (BuildContext context);
@@ -341,11 +360,41 @@ abstract class AssetPickerBuilderDelegate<A, P> {
341
360
/// 预览已选资源的按钮
342
361
Widget previewButton (BuildContext context);
343
362
363
+ /// The tip widget displays when the access is limited.
364
+ /// 当访问受限时在底部展示的提示
365
+ Widget accessLimitedBottomTip (BuildContext context) {
366
+ return Container (
367
+ padding: const EdgeInsets .symmetric (horizontal: 10 , vertical: 15 ),
368
+ color: theme.primaryColor.withOpacity (isAppleOS ? 0.90 : 1.0 ),
369
+ child: Row (
370
+ children: < Widget > [
371
+ const SizedBox (width: 5 ),
372
+ Icon (
373
+ Icons .warning,
374
+ color: Colors .orange[400 ]! .withOpacity (.8 ),
375
+ ),
376
+ const SizedBox (width: 15 ),
377
+ Expanded (
378
+ child: Text (
379
+ Constants .textDelegate.accessAllTip,
380
+ style: context.themeData.textTheme.caption? .copyWith (
381
+ fontSize: 14 ,
382
+ ),
383
+ ),
384
+ ),
385
+ Icon (
386
+ Icons .keyboard_arrow_right,
387
+ color: context.themeData.iconTheme.color? .withOpacity (.5 ),
388
+ ),
389
+ ],
390
+ ),
391
+ );
392
+ }
393
+
344
394
/// Action bar widget aligned to bottom.
345
395
/// 底部操作栏部件
346
396
Widget bottomActionBar (BuildContext context) {
347
397
Widget child = Container (
348
- width: Screens .width,
349
398
height: bottomActionBarHeight + Screens .bottomSafeHeight,
350
399
padding: EdgeInsetsDirectional .only (
351
400
start: 20.0 ,
@@ -360,6 +409,12 @@ abstract class AssetPickerBuilderDelegate<A, P> {
360
409
]),
361
410
);
362
411
if (isAppleOS) {
412
+ if (isPermissionLimited) {
413
+ child = Column (
414
+ mainAxisSize: MainAxisSize .min,
415
+ children: < Widget > [accessLimitedBottomTip (context), child],
416
+ );
417
+ }
363
418
child = ClipRect (
364
419
child: BackdropFilter (
365
420
filter: ui.ImageFilter .blur (
@@ -417,6 +472,97 @@ abstract class AssetPickerBuilderDelegate<A, P> {
417
472
/// Android设备的选择器布局
418
473
Widget androidLayout (BuildContext context);
419
474
475
+ /// The overlay when the permission is limited on iOS.
476
+ Widget iOSPermissionOverlay (BuildContext context) {
477
+ final Size size = context.mediaQuery.size;
478
+ final Widget _closeButton = Container (
479
+ margin: const EdgeInsetsDirectional .only (start: 16 , top: 4 ),
480
+ alignment: AlignmentDirectional .centerStart,
481
+ child: IconButton (
482
+ onPressed: Navigator .of (context).maybePop,
483
+ icon: const Icon (Icons .clear, size: 32 ),
484
+ padding: EdgeInsets .zero,
485
+ constraints: BoxConstraints .tight (const Size .square (32 )),
486
+ ),
487
+ );
488
+
489
+ final Widget _limitedTips = Padding (
490
+ padding: const EdgeInsets .symmetric (horizontal: 30 ),
491
+ child: Column (
492
+ mainAxisAlignment: MainAxisAlignment .center,
493
+ children: < Widget > [
494
+ Text (
495
+ Constants .textDelegate.unableToAccessAll,
496
+ style: const TextStyle (fontSize: 22 ),
497
+ textAlign: TextAlign .center,
498
+ ),
499
+ SizedBox (height: size.height / 30 ),
500
+ Text (
501
+ Constants .textDelegate.accessAllTip,
502
+ style: const TextStyle (fontSize: 18 ),
503
+ textAlign: TextAlign .center,
504
+ ),
505
+ ],
506
+ ),
507
+ );
508
+
509
+ final Widget _goToSettingsButton = MaterialButton (
510
+ elevation: 0 ,
511
+ minWidth: size.width / 2 ,
512
+ height: appBarItemHeight * 1.25 ,
513
+ padding: const EdgeInsets .symmetric (horizontal: 24.0 ),
514
+ color: themeColor,
515
+ shape: RoundedRectangleBorder (
516
+ borderRadius: BorderRadius .circular (5 ),
517
+ ),
518
+ child: Text (
519
+ Constants .textDelegate.goToSystemSettings,
520
+ style: const TextStyle (fontSize: 17.0 ),
521
+ ),
522
+ onPressed: () {},
523
+ materialTapTargetSize: MaterialTapTargetSize .shrinkWrap,
524
+ );
525
+
526
+ final Widget _accessLimitedButton = GestureDetector (
527
+ onTap: () => permissionOverlayHidden.value = true ,
528
+ child: Text (
529
+ Constants .textDelegate.accessLimitedAssets,
530
+ style: TextStyle (
531
+ color: Color .lerp (
532
+ context.themeData.iconTheme.color? .withOpacity (.5 ),
533
+ Colors .blue,
534
+ 0.3 ,
535
+ ),
536
+ ),
537
+ ),
538
+ );
539
+
540
+ return ValueListenableBuilder2 <PermissionState , bool >(
541
+ firstNotifier: permission,
542
+ secondNotifier: permissionOverlayHidden,
543
+ builder: (_, PermissionState ps, bool isHidden, __) {
544
+ if (ps.isAuth || isHidden) {
545
+ return const SizedBox .shrink ();
546
+ }
547
+ return Positioned .fill (
548
+ child: Container (
549
+ padding: context.mediaQuery.padding,
550
+ color: context.themeData.canvasColor,
551
+ child: Column (
552
+ children: < Widget > [
553
+ _closeButton,
554
+ Expanded (child: _limitedTips),
555
+ _goToSettingsButton,
556
+ SizedBox (height: size.height / 18 ),
557
+ _accessLimitedButton,
558
+ ],
559
+ ),
560
+ ),
561
+ );
562
+ },
563
+ );
564
+ }
565
+
420
566
/// Yes, the build method.
421
567
/// 没错,是它是它就是它,我们亲爱的 build 方法~
422
568
Widget build (BuildContext context) {
@@ -426,9 +572,15 @@ abstract class AssetPickerBuilderDelegate<A, P> {
426
572
data: theme,
427
573
child: ChangeNotifierProvider <AssetPickerProvider <A , P >>.value (
428
574
value: provider,
429
- child : Material (
575
+ builder : ( BuildContext c, __) => Material (
430
576
color: theme.canvasColor,
431
- child: isAppleOS ? appleOSLayout (context) : androidLayout (context),
577
+ child: Stack (
578
+ fit: StackFit .expand,
579
+ children: < Widget > [
580
+ if (isAppleOS) appleOSLayout (c) else androidLayout (c),
581
+ if (Platform .isIOS) iOSPermissionOverlay (c),
582
+ ],
583
+ ),
432
584
),
433
585
),
434
586
),
@@ -440,6 +592,7 @@ class DefaultAssetPickerBuilderDelegate
440
592
extends AssetPickerBuilderDelegate <AssetEntity , AssetPathEntity > {
441
593
DefaultAssetPickerBuilderDelegate ({
442
594
required DefaultAssetPickerProvider provider,
595
+ required PermissionState initialPermission,
443
596
int gridCount = 4 ,
444
597
Color ? themeColor,
445
598
AssetsPickerTextDelegate ? textDelegate,
@@ -457,6 +610,7 @@ class DefaultAssetPickerBuilderDelegate
457
610
),
458
611
super (
459
612
provider: provider,
613
+ initialPermission: initialPermission,
460
614
gridCount: gridCount,
461
615
themeColor: themeColor,
462
616
textDelegate: textDelegate,
@@ -602,8 +756,8 @@ class DefaultAssetPickerBuilderDelegate
602
756
),
603
757
if ((! isSingleAssetMode || isAppleOS) &&
604
758
isPreviewEnabled)
605
- PositionedDirectional (
606
- bottom : 0.0 ,
759
+ Positioned . fill (
760
+ top : null ,
607
761
child: bottomActionBar (context),
608
762
),
609
763
],
@@ -931,6 +1085,7 @@ class DefaultAssetPickerBuilderDelegate
931
1085
@override
932
1086
Widget pathEntityListWidget (BuildContext context) {
933
1087
return Positioned .fill (
1088
+ top: isAppleOS ? context.mediaQuery.padding.top + kToolbarHeight : 0 ,
934
1089
bottom: null ,
935
1090
child: Selector <DefaultAssetPickerProvider , bool >(
936
1091
selector: (_, DefaultAssetPickerProvider p) => p.isSwitchingPath,
@@ -955,31 +1110,61 @@ class DefaultAssetPickerBuilderDelegate
955
1110
),
956
1111
),
957
1112
),
958
- child: Selector <DefaultAssetPickerProvider , int >(
959
- selector: (_, DefaultAssetPickerProvider p) => p.validPathThumbCount,
960
- builder: (_, int count, __) => Selector <DefaultAssetPickerProvider ,
961
- Map <AssetPathEntity , Uint8List ?>>(
962
- selector: (_, DefaultAssetPickerProvider p) => p.pathEntityList,
963
- builder: (_, Map <AssetPathEntity , Uint8List ?> list, __) {
964
- return ListView .separated (
965
- padding: const EdgeInsetsDirectional .only (top: 1.0 ),
966
- itemCount: list.length,
967
- itemBuilder: (BuildContext c, int index) => pathEntityWidget (
968
- context: c,
969
- list: list,
970
- index: index,
971
- isAudio:
972
- (provider as DefaultAssetPickerProvider ).requestType ==
973
- RequestType .audio,
974
- ),
975
- separatorBuilder: (_, __) => Container (
976
- margin: const EdgeInsetsDirectional .only (start: 60.0 ),
977
- height: 1.0 ,
978
- color: theme.canvasColor,
1113
+ child: Column (
1114
+ children: < Widget > [
1115
+ ValueListenableBuilder <PermissionState >(
1116
+ valueListenable: permission,
1117
+ builder: (_, PermissionState ps, __) {
1118
+ if (isPermissionLimited) {
1119
+ return Padding (
1120
+ padding: const EdgeInsets .symmetric (
1121
+ horizontal: 20 ,
1122
+ vertical: 12 ,
1123
+ ),
1124
+ child: Text (
1125
+ Constants .textDelegate.viewingLimitedAssetsTip,
1126
+ style: context.themeData.textTheme.caption? .copyWith (
1127
+ fontSize: 15 ,
1128
+ ),
1129
+ ),
1130
+ );
1131
+ }
1132
+ return const SizedBox .shrink ();
1133
+ },
1134
+ ),
1135
+ Expanded (
1136
+ child: Selector <DefaultAssetPickerProvider , int >(
1137
+ selector: (_, DefaultAssetPickerProvider p) =>
1138
+ p.validPathThumbCount,
1139
+ builder: (_, int count, __) => Selector <
1140
+ DefaultAssetPickerProvider ,
1141
+ Map <AssetPathEntity , Uint8List ?>>(
1142
+ selector: (_, DefaultAssetPickerProvider p) =>
1143
+ p.pathEntityList,
1144
+ builder: (_, Map <AssetPathEntity , Uint8List ?> list, __) {
1145
+ return ListView .separated (
1146
+ padding: const EdgeInsetsDirectional .only (top: 1.0 ),
1147
+ itemCount: list.length,
1148
+ itemBuilder: (BuildContext c, int index) =>
1149
+ pathEntityWidget (
1150
+ context: c,
1151
+ list: list,
1152
+ index: index,
1153
+ isAudio: (provider as DefaultAssetPickerProvider )
1154
+ .requestType ==
1155
+ RequestType .audio,
1156
+ ),
1157
+ separatorBuilder: (_, __) => Container (
1158
+ margin: const EdgeInsetsDirectional .only (start: 60.0 ),
1159
+ height: 1.0 ,
1160
+ color: theme.canvasColor,
1161
+ ),
1162
+ );
1163
+ },
979
1164
),
980
- );
981
- } ,
982
- ) ,
1165
+ ),
1166
+ ) ,
1167
+ ] ,
983
1168
),
984
1169
),
985
1170
);
@@ -1006,7 +1191,9 @@ class DefaultAssetPickerBuilderDelegate
1006
1191
if (p != null )
1007
1192
Flexible (
1008
1193
child: Text (
1009
- p.name,
1194
+ isPermissionLimited && p.isAll
1195
+ ? Constants .textDelegate.accessiblePathName
1196
+ : p.name,
1010
1197
style: const TextStyle (
1011
1198
fontSize: 18.0 ,
1012
1199
fontWeight: FontWeight .normal,
@@ -1105,7 +1292,9 @@ class DefaultAssetPickerBuilderDelegate
1105
1292
child: Padding (
1106
1293
padding: const EdgeInsetsDirectional .only (end: 10.0 ),
1107
1294
child: Text (
1108
- pathEntity.name,
1295
+ isPermissionLimited && pathEntity.isAll
1296
+ ? Constants .textDelegate.accessiblePathName
1297
+ : pathEntity.name,
1109
1298
style: const TextStyle (fontSize: 18.0 ),
1110
1299
maxLines: 1 ,
1111
1300
overflow: TextOverflow .ellipsis,
0 commit comments