Skip to content

Commit 215113b

Browse files
committed
Add custom dialog config
1 parent fad89e3 commit 215113b

File tree

3 files changed

+129
-34
lines changed

3 files changed

+129
-34
lines changed

lib/flutter_force_permission_config.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
library flutter_force_permission;
22

3+
import 'package:flutter/material.dart';
34
import 'package:flutter_force_permission/permission_item_config.dart';
45

6+
typedef ShowDialogCallback = void Function(String title, String content, String buttonText, VoidCallback callback);
7+
58
/// Configuration for Flutter Force Permission.
69
class FlutterForcePermissionConfig {
710
FlutterForcePermissionConfig({
811
required this.title,
912
required this.confirmText,
1013
required this.permissionItemConfigs,
14+
this.showDialogCallback,
1115
});
1216

1317
/// The title for the disclosure page.
@@ -21,4 +25,16 @@ class FlutterForcePermissionConfig {
2125
/// The list ordering dictates the order of the permissions requested in the disclosure page and the order the OS shows the permission dialogs.
2226
/// See [PermissionItemConfig] for details.
2327
final List<PermissionItemConfig> permissionItemConfigs;
28+
29+
/// Optional callback to show a custom dialog. If you wish to use dialogs other than
30+
/// the provided Material Design dialogs, provide a callback in this parameter.
31+
/// The parameters provided to the callback consists of all the texts to be shown
32+
/// as `title`, `content` and the confirm button `buttonText` respectively, as well
33+
/// as a `callback` for you to call when the confirm button is clicked.
34+
///
35+
/// This callback SHOULD invoke the provided `callback` in your callback to ensure proper functionality by invoking system OS settings.
36+
/// The dialog shown during your callback SHOULD NOT be dismissable. It is typically
37+
/// achieved by setting `barrierDismissible` to false and provide an empty callback
38+
/// e.g. (`() async => false`) to `willPopCallback` for your dialog.
39+
final ShowDialogCallback? showDialogCallback;
2440
}

lib/src/views/disclosure_page.dart

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -273,54 +273,53 @@ class _DisclosurePageState extends State<DisclosurePage>
273273
VoidCallback openSettings,
274274
) async {
275275
final dialogConfig = permConfig.forcedPermissionDialogConfig;
276-
// ignore: avoid-ignoring-return-values, not needed.
277-
await showDialog(
278-
context: context,
279-
barrierDismissible: false,
280-
builder: (context) => WillPopScope(
281-
onWillPop: () async => false,
282-
child: AlertDialog(
283-
title: Text(
284-
dialogConfig?.title ?? '',
285-
),
286-
content: Text(
287-
dialogConfig?.text ?? '',
288-
),
289-
actions: [
290-
TextButton(
291-
onPressed: openSettings,
292-
child: Text(
293-
dialogConfig?.buttonText ?? '',
294-
),
276+
final callback = widget.permissionConfig.showDialogCallback;
277+
278+
if (callback == null) {
279+
// ignore: avoid-ignoring-return-values, not needed.
280+
await showDialog(
281+
context: context,
282+
barrierDismissible: false,
283+
builder: (context) => WillPopScope(
284+
onWillPop: () async => false,
285+
child: AlertDialog(
286+
title: Text(
287+
dialogConfig?.title ?? '',
288+
),
289+
content: Text(
290+
dialogConfig?.text ?? '',
295291
),
296-
],
292+
actions: [
293+
TextButton(
294+
onPressed: openSettings,
295+
child: Text(
296+
dialogConfig?.buttonText ?? '',
297+
),
298+
),
299+
],
300+
),
297301
),
298-
),
299-
);
302+
);
303+
} else {
304+
callback(
305+
dialogConfig?.title ?? '',
306+
dialogConfig?.text ?? '',
307+
dialogConfig?.buttonText ?? '',
308+
openSettings,
309+
);
310+
}
300311
}
301312

302313
Future<void> _showPhoneSettings() async {
303-
final navigator = Navigator.of(context);
304-
305314
// TODO(peter): find function to open phone settings directly if possible.
306315
await widget._service.openAppSettings();
307-
308-
navigator.pop();
309316
}
310317

311318
Future<void> _showLocationSettings() async {
312-
final navigator = Navigator.of(context);
313-
314319
await widget._service.openLocationSettings();
315-
316-
navigator.pop();
317320
}
318321

319322
Future<void> _showAppSettings() async {
320-
final navigator = Navigator.of(context);
321-
322323
await widget._service.openAppSettings();
323-
324-
navigator.pop();
325324
}
326325
}

test/widget_test/disclosure_page_test.dart

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,4 +411,84 @@ void main() {
411411

412412
await resumed.close();
413413
});
414+
415+
testWidgets('Required permission denied with custom dialog callback `showDialogCallback`', (tester) async {
416+
final testStub = MockTestStub();
417+
when(testStub.getSharedPreference())
418+
.thenAnswer((realInvocation) => Future.value(prefs));
419+
when(testStub.request(Permission.location))
420+
.thenAnswer((realInvocation) => Future.value(PermissionStatus.denied));
421+
when(testStub.status(Permission.location))
422+
.thenAnswer((realInvocation) => Future.value(PermissionStatus.denied));
423+
when(testStub.openAppSettings())
424+
.thenAnswer((realInvocation) => Future.value());
425+
426+
final config = FlutterForcePermissionConfig(
427+
title: 'Title',
428+
confirmText: 'Confirm',
429+
permissionItemConfigs: [
430+
PermissionItemConfig(
431+
permissions: [
432+
Permission.location,
433+
],
434+
itemText: PermissionItemText(
435+
header: 'Foreground location',
436+
rationaleText: 'Rationale',
437+
forcedPermissionDialogConfig: ForcedPermissionDialogConfig(
438+
title: 'Location required',
439+
text: 'Location needed for proper operation',
440+
buttonText: 'Settings',
441+
),
442+
),
443+
required: true,
444+
),
445+
],
446+
showDialogCallback: (title, text, button, callback) {
447+
callback();
448+
},
449+
);
450+
final status = <Permission, PermissionServiceStatus>{
451+
Permission.location: PermissionServiceStatus(
452+
status: PermissionStatus.denied,
453+
requested: false,
454+
serviceStatus: ServiceStatus.enabled,
455+
),
456+
};
457+
final StreamController<bool> resumed = StreamController.broadcast()
458+
..add(true);
459+
460+
await tester.pumpWidget(
461+
MaterialApp(
462+
home: DisclosurePage.stub(
463+
permissionConfig: config,
464+
permissionStatuses: status,
465+
service: testStub,
466+
resumed: resumed,
467+
),
468+
),
469+
);
470+
471+
expect(find.text('Title'), findsOneWidget);
472+
expect(find.text('Foreground location'), findsOneWidget);
473+
expect(find.text('Rationale'), findsOneWidget);
474+
expect(find.text('Confirm'), findsOneWidget);
475+
476+
await tester.tap(find.text('Confirm'));
477+
await tester.pump();
478+
479+
verify(testStub.openAppSettings());
480+
481+
resumed.add(true);
482+
await tester.pump();
483+
484+
when(testStub.status(Permission.location))
485+
.thenAnswer((realInvocation) => Future.value(PermissionStatus.granted));
486+
resumed.add(true);
487+
await tester.pump();
488+
489+
verify(testStub.openAppSettings());
490+
expect(find.text('Settings'), findsNothing);
491+
492+
await resumed.close();
493+
});
414494
}

0 commit comments

Comments
 (0)