Skip to content

Commit 6f20c77

Browse files
committed
♻️ Refactored picker delegate to support limited access on iOS
1 parent addb69e commit 6f20c77

File tree

3 files changed

+256
-37
lines changed

3 files changed

+256
-37
lines changed

example/lib/customs/pickers/directory_file_asset_picker.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ class FileAssetPickerBuilder
382382
extends AssetPickerBuilderDelegate<File, Directory> {
383383
FileAssetPickerBuilder({
384384
required FileAssetPickerProvider provider,
385-
}) : super(provider: provider);
385+
}) : super(provider: provider, initialPermission: PermissionState.authorized);
386386

387387
AssetsPickerTextDelegate get textDelegate => AssetsPickerTextDelegate();
388388

lib/src/delegates/asset_picker_builder_delegate.dart

Lines changed: 221 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:flutter/services.dart';
1212

1313
import '../constants/constants.dart';
1414
import '../widget/builder/asset_entity_grid_item_builder.dart';
15+
import '../widget/builder/value_listenable_builder_2.dart';
1516

1617
typedef IndicatorBuilder = Widget Function(
1718
BuildContext context,
@@ -27,6 +28,7 @@ typedef IndicatorBuilder = Widget Function(
2728
abstract class AssetPickerBuilderDelegate<A, P> {
2829
AssetPickerBuilderDelegate({
2930
required this.provider,
31+
required this.initialPermission,
3032
this.gridCount = 4,
3133
Color? themeColor,
3234
AssetsPickerTextDelegate? textDelegate,
@@ -48,6 +50,10 @@ abstract class AssetPickerBuilderDelegate<A, P> {
4850
/// 资源选择器状态保持
4951
final AssetPickerProvider<A, P> provider;
5052

53+
/// The [PermissionState] when the picker is called.
54+
/// 当选择器被拉起时的权限状态
55+
final PermissionState initialPermission;
56+
5157
/// Assets count for the picker.
5258
/// 资源网格数
5359
final int gridCount;
@@ -116,12 +122,25 @@ abstract class AssetPickerBuilderDelegate<A, P> {
116122

117123
/// Blur radius in Apple OS layout mode.
118124
/// 苹果系列系统布局方式下的模糊度
119-
double get appleOSBlurRadius => 15.0;
125+
double get appleOSBlurRadius => 10.0;
120126

121127
/// Height for bottom action bar.
122128
/// 底部操作栏的高度
123129
double get bottomActionBarHeight => kToolbarHeight / 1.1;
124130

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+
125144
/// Path entity select widget builder.
126145
/// 路径选择部件构建
127146
Widget pathEntitySelector(BuildContext context);
@@ -341,11 +360,41 @@ abstract class AssetPickerBuilderDelegate<A, P> {
341360
/// 预览已选资源的按钮
342361
Widget previewButton(BuildContext context);
343362

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+
344394
/// Action bar widget aligned to bottom.
345395
/// 底部操作栏部件
346396
Widget bottomActionBar(BuildContext context) {
347397
Widget child = Container(
348-
width: Screens.width,
349398
height: bottomActionBarHeight + Screens.bottomSafeHeight,
350399
padding: EdgeInsetsDirectional.only(
351400
start: 20.0,
@@ -360,6 +409,12 @@ abstract class AssetPickerBuilderDelegate<A, P> {
360409
]),
361410
);
362411
if (isAppleOS) {
412+
if (isPermissionLimited) {
413+
child = Column(
414+
mainAxisSize: MainAxisSize.min,
415+
children: <Widget>[accessLimitedBottomTip(context), child],
416+
);
417+
}
363418
child = ClipRect(
364419
child: BackdropFilter(
365420
filter: ui.ImageFilter.blur(
@@ -417,6 +472,97 @@ abstract class AssetPickerBuilderDelegate<A, P> {
417472
/// Android设备的选择器布局
418473
Widget androidLayout(BuildContext context);
419474

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+
420566
/// Yes, the build method.
421567
/// 没错,是它是它就是它,我们亲爱的 build 方法~
422568
Widget build(BuildContext context) {
@@ -426,9 +572,15 @@ abstract class AssetPickerBuilderDelegate<A, P> {
426572
data: theme,
427573
child: ChangeNotifierProvider<AssetPickerProvider<A, P>>.value(
428574
value: provider,
429-
child: Material(
575+
builder: (BuildContext c, __) => Material(
430576
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+
),
432584
),
433585
),
434586
),
@@ -440,6 +592,7 @@ class DefaultAssetPickerBuilderDelegate
440592
extends AssetPickerBuilderDelegate<AssetEntity, AssetPathEntity> {
441593
DefaultAssetPickerBuilderDelegate({
442594
required DefaultAssetPickerProvider provider,
595+
required PermissionState initialPermission,
443596
int gridCount = 4,
444597
Color? themeColor,
445598
AssetsPickerTextDelegate? textDelegate,
@@ -457,6 +610,7 @@ class DefaultAssetPickerBuilderDelegate
457610
),
458611
super(
459612
provider: provider,
613+
initialPermission: initialPermission,
460614
gridCount: gridCount,
461615
themeColor: themeColor,
462616
textDelegate: textDelegate,
@@ -602,8 +756,8 @@ class DefaultAssetPickerBuilderDelegate
602756
),
603757
if ((!isSingleAssetMode || isAppleOS) &&
604758
isPreviewEnabled)
605-
PositionedDirectional(
606-
bottom: 0.0,
759+
Positioned.fill(
760+
top: null,
607761
child: bottomActionBar(context),
608762
),
609763
],
@@ -931,6 +1085,7 @@ class DefaultAssetPickerBuilderDelegate
9311085
@override
9321086
Widget pathEntityListWidget(BuildContext context) {
9331087
return Positioned.fill(
1088+
top: isAppleOS ? context.mediaQuery.padding.top + kToolbarHeight : 0,
9341089
bottom: null,
9351090
child: Selector<DefaultAssetPickerProvider, bool>(
9361091
selector: (_, DefaultAssetPickerProvider p) => p.isSwitchingPath,
@@ -955,31 +1110,61 @@ class DefaultAssetPickerBuilderDelegate
9551110
),
9561111
),
9571112
),
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+
},
9791164
),
980-
);
981-
},
982-
),
1165+
),
1166+
),
1167+
],
9831168
),
9841169
),
9851170
);
@@ -1006,7 +1191,9 @@ class DefaultAssetPickerBuilderDelegate
10061191
if (p != null)
10071192
Flexible(
10081193
child: Text(
1009-
p.name,
1194+
isPermissionLimited && p.isAll
1195+
? Constants.textDelegate.accessiblePathName
1196+
: p.name,
10101197
style: const TextStyle(
10111198
fontSize: 18.0,
10121199
fontWeight: FontWeight.normal,
@@ -1105,7 +1292,9 @@ class DefaultAssetPickerBuilderDelegate
11051292
child: Padding(
11061293
padding: const EdgeInsetsDirectional.only(end: 10.0),
11071294
child: Text(
1108-
pathEntity.name,
1295+
isPermissionLimited && pathEntity.isAll
1296+
? Constants.textDelegate.accessiblePathName
1297+
: pathEntity.name,
11091298
style: const TextStyle(fontSize: 18.0),
11101299
maxLines: 1,
11111300
overflow: TextOverflow.ellipsis,

0 commit comments

Comments
 (0)