Skip to content

Commit 7bde400

Browse files
authored
Merge pull request #8 from BaseflowIT/feature/ios_request_permission
Implemented request permissions on iOS
2 parents 2fbc01a + b86ec86 commit 7bde400

18 files changed

+402
-142
lines changed
Binary file not shown.

example/ios/Runner.xcodeproj/project.pbxproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@
444444
PRODUCT_NAME = "$(TARGET_NAME)";
445445
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
446446
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
447-
SWIFT_SWIFT3_OBJC_INFERENCE = On;
447+
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
448448
SWIFT_VERSION = 4.0;
449449
VERSIONING_SYSTEM = "apple-generic";
450450
};
@@ -471,7 +471,7 @@
471471
PRODUCT_BUNDLE_IDENTIFIER = com.baseflow.permissionHandlerExample;
472472
PRODUCT_NAME = "$(TARGET_NAME)";
473473
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
474-
SWIFT_SWIFT3_OBJC_INFERENCE = On;
474+
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
475475
SWIFT_VERSION = 4.0;
476476
VERSIONING_SYSTEM = "apple-generic";
477477
};

example/ios/Runner/Info.plist

+7-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,13 @@
4646
<key>UIViewControllerBasedStatusBarAppearance</key>
4747
<false/>
4848
<key>NSLocationWhenInUseUsageDescription</key>
49-
<string>Can I haz location?</string>
49+
<string>Need location when in use</string>
50+
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
51+
<string>Always and when in use!</string>
52+
<key>NSLocationUsageDescription</key>
53+
<string>Older devices need location.</string>
54+
<key>NSLocationAlwaysUsageDescription</key>
55+
<string>Can I haz location always?</string>
5056
<key>NSAppleMusicUsageDescription</key>
5157
<string>Music!</string>
5258
<key>NSBluetoothPeripheralUsageDescription</key>

example/lib/main.dart

+41-67
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,61 @@
1-
import 'dart:async';
2-
31
import 'package:flutter/material.dart';
4-
import 'package:flutter/services.dart';
52
import 'package:permission_handler/permission_enums.dart';
63
import 'package:permission_handler/permission_handler.dart';
74

85
void main() => runApp(new MyApp());
96

10-
class MyApp extends StatefulWidget {
11-
@override
12-
_MyAppState createState() => new _MyAppState();
13-
}
14-
15-
class _MyAppState extends State<MyApp> {
16-
String _permissionStatus = 'Unknown';
17-
7+
class MyApp extends StatelessWidget {
8+
189
@override
19-
void initState() {
20-
super.initState();
21-
initPlatformState();
10+
Widget build(BuildContext context) {
11+
return new MaterialApp(
12+
home: new Scaffold(
13+
appBar: new AppBar(
14+
title: const Text('Plugin example app'),
15+
),
16+
body: new Center(
17+
child: new ListView(
18+
children: PermissionGroup.values
19+
.where((PermissionGroup permission) => permission != PermissionGroup.unknown)
20+
.map((PermissionGroup permission) => new PermissionWidget(permission)).toList()
21+
),
22+
),
23+
));
2224
}
25+
}
2326

24-
// Platform messages are asynchronous, so we initialize in an async method.
25-
Future<void> initPlatformState() async {
26-
PermissionStatus permissionStatus;
27+
class PermissionWidget extends StatefulWidget {
28+
final PermissionGroup _permissionGroup;
2729

28-
// Platform messages may fail, so we use a try/catch PlatformException.
29-
try {
30-
permissionStatus = await PermissionHandler
31-
.checkPermissionStatus(PermissionGroup.calendar);
30+
const PermissionWidget(this._permissionGroup);
3231

33-
/*
34-
if (permissionStatus != PermissionStatus.granted) {
35-
final bool shouldShowRationale = await PermissionHandler
36-
.shouldShowRequestPermissionRationale(PermissionGroup.calendar);
32+
@override
33+
_PermissionState createState() => _PermissionState(_permissionGroup);
34+
}
3735

38-
if (shouldShowRationale) {
39-
final Map<PermissionGroup, PermissionStatus> permissions =
40-
await PermissionHandler.requestPermissions(
41-
<PermissionGroup>[PermissionGroup.calendar]);
36+
class _PermissionState extends State<PermissionWidget> {
37+
final PermissionGroup _permissionGroup;
38+
PermissionStatus _permissionStatus = PermissionStatus.unknown;
4239

43-
if (permissions.containsKey(PermissionGroup.calendar)) {
44-
permissionStatus = permissions[PermissionGroup.calendar];
45-
}
46-
}
47-
}
48-
*/
49-
} on PlatformException {
50-
permissionStatus = PermissionStatus.unknown;
51-
}
40+
_PermissionState(this._permissionGroup);
41+
42+
@override
43+
void initState() {
44+
super.initState();
5245

53-
// If the widget was removed from the tree while the asynchronous platform
54-
// message was in flight, we want to discard the reply rather than calling
55-
// setState to update our non-existent appearance.
56-
if (!mounted) {
57-
return;
58-
}
46+
_listenForPermissionStatus();
47+
}
5948

60-
setState(() {
61-
_permissionStatus = permissionStatus.toString();
62-
});
49+
void _listenForPermissionStatus() async {
50+
print(_permissionGroup.toString());
51+
_permissionStatus = await PermissionHandler.checkPermissionStatus(_permissionGroup);
6352
}
6453

65-
@override
54+
@override
6655
Widget build(BuildContext context) {
67-
return new MaterialApp(
68-
home: new Scaffold(
69-
appBar: new AppBar(
70-
title: const Text('Plugin example app'),
71-
),
72-
body: new Center(
73-
child: new Column(
74-
children: <Widget>[
75-
new Text('Running on: $_permissionStatus\n'),
76-
new RaisedButton(
77-
child: const Text('Open settings'),
78-
onPressed: () async =>
79-
await PermissionHandler.openAppSettings(),
80-
),
81-
],
82-
),
83-
),
84-
),
56+
return new ListTile(
57+
title: new Text(_permissionGroup.toString()),
58+
subtitle: new Text(_permissionStatus.toString()),
8559
);
8660
}
87-
}
61+
}

ios/Classes/PermissionManager.swift

+33-2
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,40 @@ class PermissionManager: NSObject {
1313

1414
static func checkPermissionStatus(permission: PermissionGroup, result: @escaping FlutterResult) {
1515
let permissionStrategy = PermissionManager.createPermissionStrategy(permission: permission)
16-
let permissionStatus = permissionStrategy.checkPermissionStatus(permission: permission)
16+
let permissionStatus = permissionStrategy?.checkPermissionStatus(permission: permission) ?? PermissionStatus.unknown
1717

1818
result(Codec.encodePermissionStatus(permissionStatus: permissionStatus))
1919
}
2020

21+
static func requestPermission(permissions: [PermissionGroup], result: @escaping FlutterResult) {
22+
var requestQueue = Set(permissions.map { $0 })
23+
var permissionStatusResult: [PermissionGroup: PermissionStatus] = [:]
24+
25+
for permission in permissions {
26+
let permissionStrategy = PermissionManager.createPermissionStrategy(permission: permission)
27+
28+
if permissionStrategy == nil {
29+
permissionStatusResult[permission] = PermissionStatus.unknown
30+
requestQueue.remove(permission)
31+
32+
if requestQueue.count == 0 {
33+
result(Codec.encodePermissionRequestResult(permissionStatusResult: permissionStatusResult))
34+
return
35+
}
36+
} else {
37+
permissionStrategy!.requestPermission(permission: permission) { (permissionStatus: PermissionStatus) in
38+
permissionStatusResult[permission] = permissionStatus
39+
requestQueue.remove(permission)
40+
41+
if requestQueue.count == 0 {
42+
result(Codec.encodePermissionRequestResult(permissionStatusResult: permissionStatusResult))
43+
return
44+
}
45+
}
46+
}
47+
}
48+
}
49+
2150
static func openAppSettings(result: @escaping FlutterResult) {
2251
if #available(iOS 8.0, *) {
2352
if #available(iOS 10, *) {
@@ -34,7 +63,7 @@ class PermissionManager: NSObject {
3463
result(false)
3564
}
3665

37-
private static func createPermissionStrategy(permission: PermissionGroup) -> PermissionStrategy {
66+
private static func createPermissionStrategy(permission: PermissionGroup) -> PermissionStrategy? {
3867
switch permission {
3968
case PermissionGroup.calendar:
4069
return EventPermissionStrategy()
@@ -58,6 +87,8 @@ class PermissionManager: NSObject {
5887
return SensorPermissionStrategy()
5988
case PermissionGroup.speech:
6089
return SpeechPermissionStrategy()
90+
default:
91+
return nil
6192
}
6293
}
6394
}

ios/Classes/SwiftPermissionHandlerPlugin.swift

+10-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,16 @@ public class SwiftPermissionHandlerPlugin: NSObject, FlutterPlugin {
1919
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
2020
if call.method == "checkPermissionStatus" {
2121
PermissionManager.checkPermissionStatus(
22-
permission: Codec.decodePermissionGroup(from: call.arguments),
23-
result: result)
22+
permission: Codec.decodePermissionGroup(
23+
from: call.arguments),
24+
result: result)
25+
} else if call.method == "requestPermissions" {
26+
PermissionManager.requestPermission(
27+
permissions: Codec.decodePermissionGroups(
28+
from: call.arguments),
29+
result: result)
30+
} else if call.method == "shouldShowRequestPermissionRationale" {
31+
result(false)
2432
} else if call.method == "openAppSettings" {
2533
PermissionManager.openAppSettings(result: result)
2634
} else {

ios/Classes/data/PermissionGroup.swift

+3
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ enum PermissionGroup : String, Codable {
1616
case locationWhenInUse = "locationWhenInUse"
1717
case mediaLibrary = "mediaLibrary"
1818
case microphone = "microphone"
19+
case phone = "phone"
1920
case photos = "photos"
2021
case reminders = "reminders"
2122
case sensors = "sensors"
23+
case sms = "sms"
2224
case speech = "speech"
25+
case storage = "storage"
2326
}

ios/Classes/strategies/AudioVideoPermissionStrategy.swift

+29-5
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ class AudioVideoPermissionStrategy : NSObject, PermissionStrategy {
1919
return PermissionStatus.unknown
2020
}
2121

22-
func requestPermission(permission: PermissionGroup) -> PermissionStatus {
23-
// TODO: Add implementation
24-
return PermissionStatus.unknown
25-
}
26-
2722
private static func getPermissionStatus(mediaType: AVMediaType) -> PermissionStatus {
2823
let status: AVAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: mediaType)
2924

@@ -38,4 +33,33 @@ class AudioVideoPermissionStrategy : NSObject, PermissionStrategy {
3833
return PermissionStatus.unknown
3934
}
4035
}
36+
37+
func requestPermission(permission: PermissionGroup, completionHandler: @escaping PermissionStatusHandler) {
38+
let permissionStatus = checkPermissionStatus(permission: permission)
39+
40+
if permissionStatus != PermissionStatus.unknown {
41+
completionHandler(permissionStatus)
42+
return
43+
}
44+
45+
var mediaType: AVMediaType
46+
47+
if permission == PermissionGroup.camera {
48+
mediaType = AVMediaType.video
49+
} else if permission == PermissionGroup.microphone {
50+
mediaType = AVMediaType.audio
51+
} else {
52+
completionHandler(PermissionStatus.unknown)
53+
return
54+
}
55+
56+
AVCaptureDevice.requestAccess(for: mediaType, completionHandler: {
57+
(granted: Bool) in
58+
if granted {
59+
completionHandler(PermissionStatus.granted)
60+
} else {
61+
completionHandler(PermissionStatus.denied)
62+
}
63+
})
64+
}
4165
}

ios/Classes/strategies/ContactPermissionStrategy.swift

+56-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import AddressBook
9+
import Contacts
910
import Foundation
1011

1112
class ContactPermissionStrategy : NSObject, PermissionStrategy {
@@ -14,12 +15,22 @@ class ContactPermissionStrategy : NSObject, PermissionStrategy {
1415
return ContactPermissionStrategy.getPermissionStatus()
1516
}
1617

17-
func requestPermission(permission: PermissionGroup) -> PermissionStatus {
18-
// TODO: Add implementation
19-
return PermissionStatus.unknown
20-
}
21-
2218
private static func getPermissionStatus() -> PermissionStatus {
19+
if #available(iOS 9.0, *) {
20+
let status: CNAuthorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
21+
22+
switch status {
23+
case CNAuthorizationStatus.authorized:
24+
return PermissionStatus.granted
25+
case CNAuthorizationStatus.denied:
26+
return PermissionStatus.denied
27+
case CNAuthorizationStatus.restricted:
28+
return PermissionStatus.restricted
29+
default:
30+
return PermissionStatus.unknown
31+
}
32+
}
33+
2334
let status: ABAuthorizationStatus = ABAddressBookGetAuthorizationStatus()
2435

2536
switch status {
@@ -33,4 +44,44 @@ class ContactPermissionStrategy : NSObject, PermissionStrategy {
3344
return PermissionStatus.unknown
3445
}
3546
}
47+
48+
func requestPermission(permission: PermissionGroup, completionHandler: @escaping PermissionStatusHandler) {
49+
let permissionStatus = checkPermissionStatus(permission: permission)
50+
51+
if permissionStatus != PermissionStatus.unknown {
52+
completionHandler(permissionStatus)
53+
return
54+
}
55+
56+
if #available(iOS 9.0, *) {
57+
ContactPermissionStrategy.requestPermissionsFromContactStore(completionHandler: completionHandler)
58+
} else {
59+
ContactPermissionStrategy.requestPermissionsFromAddressBook(completionHandler: completionHandler)
60+
}
61+
}
62+
63+
@available(iOS 9.0, *)
64+
private static func requestPermissionsFromContactStore(completionHandler: @escaping PermissionStatusHandler) {
65+
let contactStore = CNContactStore.init()
66+
67+
contactStore.requestAccess(for: .contacts, completionHandler: {
68+
(authorized: Bool, error: Error?) in
69+
if authorized {
70+
completionHandler(PermissionStatus.granted)
71+
} else {
72+
completionHandler(PermissionStatus.denied)
73+
}
74+
})
75+
}
76+
77+
private static func requestPermissionsFromAddressBook(completionHandler: @escaping PermissionStatusHandler) {
78+
ABAddressBookRequestAccessWithCompletion(ABAddressBookCreate() as ABAddressBook, {
79+
(granted: Bool, error: CFError?) in
80+
if granted {
81+
completionHandler(PermissionStatus.granted)
82+
} else {
83+
completionHandler(PermissionStatus.denied)
84+
}
85+
})
86+
}
3687
}

0 commit comments

Comments
 (0)