Skip to content

Commit a57a48b

Browse files
authored
Merge pull request #11 from gogovan/feat/adaptive-dialog
Add custom dialog config
2 parents fad89e3 + 0c6693d commit a57a48b

File tree

4 files changed

+283
-74
lines changed

4 files changed

+283
-74
lines changed

README.md

Lines changed: 132 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
11
# flutter-force-permission
2+
23
[![codecov](https://codecov.io/gh/gogovan/flutter-force-permission/branch/main/graph/badge.svg?token=F9DPJUAVAJ)](https://codecov.io/gh/gogovan/flutter-force-permission)
34

4-
Show permission disclosure page and allows required permissions and their associated services before the user can proceed.
5+
Show permission disclosure page and allows required permissions and their associated services before
6+
the user can proceed.
57

6-
This package shows a prominent in-app disclosure page for getting permissions as required by [Google Play](https://support.google.com/googleplay/android-developer/answer/9799150?visit_id=638041800350153935-369621111&p=pd-m&rd=1#prominent_disclosure&zippy=%2Cstep-provide-prominent-in-app-disclosure%2Cstep-review-best-practices-for-accessing-location%2Cstep-consider-alternatives-to-accessing-location-in-the-background%2Cstep-make-access-to-location-in-the-background-clear-to-users%2Csee-an-example-of-prominent-in-app-disclosure).
7-
Also support iOS to ensure a consistent experience.
8+
This package shows a prominent in-app disclosure page for getting permissions as required
9+
by [Google Play](https://support.google.com/googleplay/android-developer/answer/9799150?visit_id=638041800350153935-369621111&p=pd-m&rd=1#prominent_disclosure&zippy=%2Cstep-provide-prominent-in-app-disclosure%2Cstep-review-best-practices-for-accessing-location%2Cstep-consider-alternatives-to-accessing-location-in-the-background%2Cstep-make-access-to-location-in-the-background-clear-to-users%2Csee-an-example-of-prominent-in-app-disclosure)
10+
. Also support iOS to ensure a consistent experience.
811

9-
In addition, permissions and their associated services (e.g. GPS) can be set as "required". If this is set, those required permissions will
10-
be required and if users denied it, this package will show a customizable dialog and redirect user to the appropriate settings page provided by the native OS.
12+
In addition, permissions and their associated services (e.g. GPS) can be set as "required". If this
13+
is set, those required permissions will be required and if users denied it, this package will show a
14+
customizable dialog and redirect user to the appropriate settings page provided by the native OS.
1115

1216
## Setup
17+
1318
1. Add the following to `pubspec.yaml`
19+
1420
```yaml
1521
dependencies:
1622
flutter_force_permission: ^0.1.0
1723
permission_handler: ^10.2.0
1824
```
19-
2. This package depends on [permission_handler](https://pub.dev/packages/permission_handler). Perform setup according to that package.
20-
3. On Android, if you use `POST_NOTIFICATIONS` permission, update the `targetSdkVersion` in `build.gradle` to at least 33 so that the permission request dialog is shown correctly. Refer to [relevant Android Developer page](https://developer.android.com/develop/ui/views/notifications/notification-permission) for details.
25+
26+
2. This package depends on [permission_handler](https://pub.dev/packages/permission_handler).
27+
Perform setup according to that package.
28+
3. On Android, if you use `POST_NOTIFICATIONS` permission, update the `targetSdkVersion`
29+
in `build.gradle` to at least 33 so that the permission request dialog is shown correctly. Refer
30+
to [relevant Android Developer page](https://developer.android.com/develop/ui/views/notifications/notification-permission)
31+
for details.
32+
2133
```groovy
2234
android {
2335
// ...
@@ -29,53 +41,135 @@ android {
2941
// ...
3042
}
3143
```
32-
4. If any features is required, it is highly recommended to also set the `<uses-feature>` tag in AndroidManifest.xml. Refer to [relevant Android Developers page](https://developer.android.com/guide/topics/manifest/uses-feature-element) for details.
44+
45+
4. If any features is required, it is highly recommended to also set the `<uses-feature>` tag in
46+
AndroidManifest.xml. Refer
47+
to [relevant Android Developers page](https://developer.android.com/guide/topics/manifest/uses-feature-element)
48+
for details.
3349

3450
## Usage
35-
1. Create an instance of FlutterForcePermission, providing configuration. Refer to documentation for [FlutterForcePermissionConfig] for details.
51+
52+
1. Create an instance of FlutterForcePermission, providing configuration. Refer to documentation
53+
for [FlutterForcePermissionConfig] for details.
54+
3655
```dart
56+
3757
final perm = FlutterForcePermission(
38-
FlutterForcePermissionConfig(
39-
title: 'Title',
40-
permissionItemConfigs: [
41-
PermissionItemConfig(
42-
permissions: [Permission.locationWhenInUse],
43-
required: true,
44-
itemText: PermissionItemText(
45-
header: 'Foreground Location',
46-
rationaleText: 'Rationale for Foreground location. Required.',
47-
forcedPermissionDialogConfig: ForcedPermissionDialogConfig(
48-
title: 'Please enable location permission',
49-
text: 'Please enable location permission for proper usage.',
50-
buttonText: 'Settings',
51-
),
58+
FlutterForcePermissionConfig(
59+
title: 'Title',
60+
permissionItemConfigs: [
61+
PermissionItemConfig(
62+
permissions: [Permission.locationWhenInUse],
63+
required: true,
64+
itemText: PermissionItemText(
65+
header: 'Foreground Location',
66+
rationaleText: 'Rationale for Foreground location. Required.',
67+
forcedPermissionDialogConfig: ForcedPermissionDialogConfig(
68+
title: 'Please enable location permission',
69+
text: 'Please enable location permission for proper usage.',
70+
buttonText: 'Settings',
5271
),
5372
),
54-
PermissionItemConfig(
55-
permissions: [Permission.locationAlways],
56-
itemText: PermissionItemText(
57-
header: 'Background Location',
58-
rationaleText: 'Rationale for Background location. lorem ipsum dolor sit amet.',
59-
),
73+
),
74+
PermissionItemConfig(
75+
permissions: [Permission.locationAlways],
76+
itemText: PermissionItemText(
77+
header: 'Background Location',
78+
rationaleText: 'Rationale for Background location. lorem ipsum dolor sit amet.',
6079
),
61-
],
62-
),
63-
);
80+
),
81+
],
82+
),
83+
);
6484
```
65-
2. Show the disclosure page as needed. This method will handle showing the disclosure page and requesting permissions.
66-
This function takes a [NavigatorState](https://api.flutter.dev/flutter/widgets/NavigatorState-class.html) which can be retrieved through `Navigator.of(context)` call.
67-
This is an async function. Wrap the function in an `async` block as needed.
68-
Returns a map of permission and their requested status (granted/denied/etc), service status and whether they are requested by this plugin.
85+
86+
2. Show the disclosure page as needed. This method will handle showing the disclosure page and
87+
requesting permissions. This function takes
88+
a [NavigatorState](https://api.flutter.dev/flutter/widgets/NavigatorState-class.html) which can
89+
be retrieved through `Navigator.of(context)` call. This is an async function. Wrap the function
90+
in an `async` block as needed. Returns a map of permission and their requested status (
91+
granted/denied/etc), service status and whether they are requested by this plugin.
92+
6993
```dart
70-
final result = await perm.show(Navigator.of(context));
94+
95+
final result = await
96+
perm.show(Navigator.of(context));
7197
```
7298

7399
### Styling
74-
You can set the style of the text shown by setting up a [TextTheme](https://api.flutter.dev/flutter/material/TextTheme-class.html) of the provided context.
100+
101+
You can set the style of the text shown by setting up
102+
a [TextTheme](https://api.flutter.dev/flutter/material/TextTheme-class.html) of the provided
103+
context.
104+
75105
- Title uses `headline6` text style.
76106
- Item header use `subtitle1` text style.
77107
- Item body use `bodyText2` text style.
78108

109+
## Advanced Usage
110+
111+
### Customize the required permission denied prompt
112+
113+
If you wish to customize the dialog shown when the required permission is denied, provide
114+
a `showDialogCallback` which to show your dialog. Parameters are included for you to compose the
115+
appropriate dialog. In your callback, you SHOULD:
116+
117+
1. Display a non-dismissable dialog. This can be typically achieved by setting `barrierDismissible`
118+
to false and provide an empty callback e.g. (`() async => false`) to `willPopCallback` for your
119+
dialog.
120+
2. Call the provided `callback` parameter in your callback when the user click the confirm button,
121+
and dismiss your dialog by `Navigator.pop`.
122+
123+
```dart
124+
125+
final config = FlutterForcePermissionConfig(
126+
title: 'Title',
127+
confirmText: 'Confirm',
128+
permissionItemConfigs: [
129+
PermissionItemConfig(
130+
permissions: [
131+
Permission.location,
132+
],
133+
itemText: PermissionItemText(
134+
header: 'Foreground location',
135+
rationaleText: 'Rationale',
136+
forcedPermissionDialogConfig: ForcedPermissionDialogConfig(
137+
title: 'Location required',
138+
text: 'Location needed for proper operation',
139+
buttonText: 'Settings',
140+
),
141+
),
142+
required: true,
143+
),
144+
],
145+
showDialogCallback: (context, title, text, button, callback) {
146+
// Store the navigator to avoid storing contexts across async gaps. See https://stackoverflow.com/a/69512692/11675817 for details.
147+
final navigator = Navigator.of(context);
148+
// Show your dialog.
149+
showDialog(context: context,
150+
barrierDismissible: false,
151+
builder: (context) =>
152+
WillPopScope(
153+
onWillPop: () async => false,
154+
child: AlertDialog(
155+
title: Text(title),
156+
content: Text(text),
157+
actions: [
158+
TextButton(
159+
onPressed: () {
160+
callback();
161+
navigator.pop();
162+
},
163+
child: Text(button),
164+
),
165+
],
166+
),
167+
),
168+
);
169+
},
170+
);
171+
```
172+
79173
## Issues
80174

81175
## Contributing

lib/flutter_force_permission_config.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
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(
7+
BuildContext context,
8+
String title,
9+
String content,
10+
String buttonText,
11+
VoidCallback callback,
12+
);
13+
514
/// Configuration for Flutter Force Permission.
615
class FlutterForcePermissionConfig {
716
FlutterForcePermissionConfig({
817
required this.title,
918
required this.confirmText,
1019
required this.permissionItemConfigs,
20+
this.showDialogCallback,
1121
});
1222

1323
/// The title for the disclosure page.
@@ -21,4 +31,18 @@ class FlutterForcePermissionConfig {
2131
/// The list ordering dictates the order of the permissions requested in the disclosure page and the order the OS shows the permission dialogs.
2232
/// See [PermissionItemConfig] for details.
2333
final List<PermissionItemConfig> permissionItemConfigs;
34+
35+
/// Optional callback to show a custom dialog. If you wish to use dialogs other than
36+
/// the provided Material Design dialogs, provide a callback in this parameter.
37+
/// The parameters provided to the callback consists of all the texts to be shown
38+
/// as `title`, `content` and the confirm button `buttonText` respectively, as well
39+
/// as a `callback` for you to call when the confirm button is clicked.
40+
///
41+
/// This callback SHOULD invoke the provided `callback` in your callback upon confirmation
42+
/// to ensure proper functionality as the `callback` will invoke appropriate setting pages provided by the OS.
43+
/// The dialog shown during your callback SHOULD NOT be dismissable. It is typically
44+
/// achieved by setting `barrierDismissible` to false and provide an empty callback
45+
/// e.g. (`() async => false`) to `willPopCallback` for your dialog.
46+
/// Also, you will probably need to dismiss your dialog after confirmation.
47+
final ShowDialogCallback? showDialogCallback;
2448
}

lib/src/views/disclosure_page.dart

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -272,55 +272,59 @@ class _DisclosurePageState extends State<DisclosurePage>
272272
PermissionItemText permConfig,
273273
VoidCallback openSettings,
274274
) async {
275+
final navigator = Navigator.of(context);
275276
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-
),
277+
final callback = widget.permissionConfig.showDialogCallback;
278+
279+
if (callback == null) {
280+
// ignore: avoid-ignoring-return-values, not needed.
281+
await showDialog(
282+
context: context,
283+
barrierDismissible: false,
284+
builder: (context) => WillPopScope(
285+
onWillPop: () async => false,
286+
child: AlertDialog(
287+
title: Text(
288+
dialogConfig?.title ?? '',
289+
),
290+
content: Text(
291+
dialogConfig?.text ?? '',
295292
),
296-
],
293+
actions: [
294+
TextButton(
295+
onPressed: () {
296+
openSettings();
297+
navigator.pop();
298+
},
299+
child: Text(
300+
dialogConfig?.buttonText ?? '',
301+
),
302+
),
303+
],
304+
),
297305
),
298-
),
299-
);
306+
);
307+
} else {
308+
callback(
309+
context,
310+
dialogConfig?.title ?? '',
311+
dialogConfig?.text ?? '',
312+
dialogConfig?.buttonText ?? '',
313+
openSettings,
314+
);
315+
}
300316
}
301317

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

311323
Future<void> _showLocationSettings() async {
312-
final navigator = Navigator.of(context);
313-
314324
await widget._service.openLocationSettings();
315-
316-
navigator.pop();
317325
}
318326

319327
Future<void> _showAppSettings() async {
320-
final navigator = Navigator.of(context);
321-
322328
await widget._service.openAppSettings();
323-
324-
navigator.pop();
325329
}
326330
}

0 commit comments

Comments
 (0)