Skip to content

Commit fad89e3

Browse files
authored
Merge pull request #10 from gogovan/fix/deny-required-perm
Fix iOS hang after denying required permission and then granted
2 parents 607ba3e + 3e71d6a commit fad89e3

File tree

2 files changed

+96
-3
lines changed

2 files changed

+96
-3
lines changed

lib/src/views/disclosure_page.dart

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,18 +237,24 @@ class _DisclosurePageState extends State<DisclosurePage>
237237
}
238238
} else {
239239
for (final Permission perm in item.config.permissions) {
240-
// ignore: avoid-ignoring-return-values, not needed.
241-
await widget._service.request(perm);
240+
// ignore: prefer-moving-to-variable, multiple calls needed to ensure up-to-date data.
241+
var permStatus = await widget._service.status(perm);
242+
if (permStatus != PermissionStatus.permanentlyDenied) {
243+
// ignore: avoid-ignoring-return-values, not needed.
244+
await widget._service.request(perm);
245+
}
242246

243247
if (item.config.required) {
244-
var permStatus = await widget._service.status(perm);
248+
// ignore: prefer-moving-to-variable, multiple calls needed to ensure up-to-date data.
249+
permStatus = await widget._service.status(perm);
245250
while (permStatus != PermissionStatus.granted) {
246251
await _showRequiredPermDialog(
247252
item.config.itemText,
248253
_showAppSettings,
249254
);
250255
// ignore: avoid-ignoring-return-values, not needed.
251256
await widget._resumed.stream.firstWhere((element) => element);
257+
// ignore: prefer-moving-to-variable, multiple calls needed to ensure up-to-date data.
252258
permStatus = await widget._service.status(perm);
253259
}
254260
}

test/widget_test/disclosure_page_test.dart

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ void main() {
3030
.thenAnswer((realInvocation) => Future.value(prefs));
3131
when(testStub.request(Permission.location))
3232
.thenAnswer((realInvocation) => Future.value(PermissionStatus.granted));
33+
when(testStub.status(Permission.location))
34+
.thenAnswer((realInvocation) => Future.value(PermissionStatus.granted));
3335

3436
final config = FlutterForcePermissionConfig(
3537
title: 'Title',
@@ -228,6 +230,91 @@ void main() {
228230
await resumed.close();
229231
});
230232

233+
// If permissions are permanently denied, permission request will not show.
234+
testWidgets('Required permission permanently denied', (tester) async {
235+
final testStub = MockTestStub();
236+
when(testStub.getSharedPreference())
237+
.thenAnswer((realInvocation) => Future.value(prefs));
238+
when(testStub.status(Permission.location))
239+
.thenAnswer((realInvocation) => Future.value(PermissionStatus.permanentlyDenied));
240+
when(testStub.openAppSettings())
241+
.thenAnswer((realInvocation) => Future.value());
242+
243+
final config = FlutterForcePermissionConfig(
244+
title: 'Title',
245+
confirmText: 'Confirm',
246+
permissionItemConfigs: [
247+
PermissionItemConfig(
248+
permissions: [
249+
Permission.location,
250+
],
251+
itemText: PermissionItemText(
252+
header: 'Foreground location',
253+
rationaleText: 'Rationale',
254+
forcedPermissionDialogConfig: ForcedPermissionDialogConfig(
255+
title: 'Location required',
256+
text: 'Location needed for proper operation',
257+
buttonText: 'Settings',
258+
),
259+
),
260+
required: true,
261+
),
262+
],
263+
);
264+
final status = <Permission, PermissionServiceStatus>{
265+
Permission.location: PermissionServiceStatus(
266+
status: PermissionStatus.denied,
267+
requested: false,
268+
serviceStatus: ServiceStatus.enabled,
269+
),
270+
};
271+
final StreamController<bool> resumed = StreamController.broadcast()
272+
..add(true);
273+
274+
await tester.pumpWidget(
275+
MaterialApp(
276+
home: DisclosurePage.stub(
277+
permissionConfig: config,
278+
permissionStatuses: status,
279+
service: testStub,
280+
resumed: resumed,
281+
),
282+
),
283+
);
284+
285+
expect(find.text('Title'), findsOneWidget);
286+
expect(find.text('Foreground location'), findsOneWidget);
287+
expect(find.text('Rationale'), findsOneWidget);
288+
expect(find.text('Confirm'), findsOneWidget);
289+
290+
await tester.tap(find.text('Confirm'));
291+
await tester.pump();
292+
293+
expect(find.text('Location required'), findsOneWidget);
294+
expect(find.text('Location needed for proper operation'), findsOneWidget);
295+
expect(find.text('Settings'), findsOneWidget);
296+
297+
await tester.tap(find.text('Settings'));
298+
await tester.pump();
299+
300+
verify(testStub.openAppSettings());
301+
302+
resumed.add(true);
303+
await tester.pump();
304+
305+
expect(find.text('Settings'), findsOneWidget);
306+
await tester.tap(find.text('Settings'));
307+
308+
when(testStub.status(Permission.location))
309+
.thenAnswer((realInvocation) => Future.value(PermissionStatus.granted));
310+
resumed.add(true);
311+
await tester.pump();
312+
313+
expect(find.text('Settings'), findsNothing);
314+
315+
await resumed.close();
316+
});
317+
231318
testWidgets('Required service permission', (tester) async {
232319
final testStub = MockTestStub();
233320
when(testStub.getSharedPreference())

0 commit comments

Comments
 (0)