Commit f42e767e by Maurits van Beusekom

Release version 5.1.0

parents 31d35513 fd54bade
......@@ -40,7 +40,7 @@ android {
Add permissions to your `AndroidManifest.xml` file.
There's a `debug`, `main` and `profile` version which are chosen depending on how you start your app.
In general, it's sufficient to add permission only to the `main` version.
[Here](https://github.com/Baseflow/flutter-permission-handler/blob/develop/example/android/app/src/main/AndroidManifest.xml)'s an example `AndroidManifest.xml` with a complete list of all possible permissions.
[Here](https://github.com/Baseflow/flutter-permission-handler/blob/develop/permission_handler/example/android/app/src/main/AndroidManifest.xml)'s an example `AndroidManifest.xml` with a complete list of all possible permissions.
</details>
......@@ -199,7 +199,7 @@ Please file any issues, bugs or feature request as an issue on our [GitHub](http
## Want to contribute
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](../CONTRIBUTING.md) and send us your [pull request](https://github.com/Baseflow/flutter-permission-handler/pulls).
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](./CONTRIBUTING.md) and send us your [pull request](https://github.com/Baseflow/flutter-permission-handler/pulls).
## Author
......
# 5.1.0
* Added support for the limited photos permission available on iOS 14 and up;
* Fixed deprecated warning on iOS;
* Added support for the "READ_PHONE_NUMBERS" permission on Android;
* Fix a link to the contribution guide in the README.md.
# 5.0.1+1
* Fixes Typo
......
......@@ -63,7 +63,8 @@ final class PermissionConstants {
static final int PERMISSION_STATUS_GRANTED = 1;
static final int PERMISSION_STATUS_RESTRICTED = 2;
static final int PERMISSION_STATUS_NOT_DETERMINED = 3;
static final int PERMISSION_STATUS_NEVER_ASK_AGAIN = 4;
static final int PERMISSION_STATUS_LIMITED = 4;
static final int PERMISSION_STATUS_NEVER_ASK_AGAIN = 5;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
......@@ -71,7 +72,8 @@ final class PermissionConstants {
PERMISSION_STATUS_GRANTED,
PERMISSION_STATUS_RESTRICTED,
PERMISSION_STATUS_NOT_DETERMINED,
PERMISSION_STATUS_NEVER_ASK_AGAIN,
PERMISSION_STATUS_LIMITED,
PERMISSION_STATUS_NEVER_ASK_AGAIN,
})
@interface PermissionStatus {
}
......
......@@ -38,6 +38,7 @@ public class PermissionUtils {
case Manifest.permission.RECORD_AUDIO:
return PermissionConstants.PERMISSION_GROUP_MICROPHONE;
case Manifest.permission.READ_PHONE_STATE:
case Manifest.permission.READ_PHONE_NUMBERS:
case Manifest.permission.CALL_PHONE:
case Manifest.permission.READ_CALL_LOG:
case Manifest.permission.WRITE_CALL_LOG:
......@@ -118,6 +119,10 @@ public class PermissionUtils {
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_PHONE_STATE))
permissionNames.add(Manifest.permission.READ_PHONE_STATE);
if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.Q && hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_PHONE_NUMBERS)) {
permissionNames.add(Manifest.permission.READ_PHONE_NUMBERS);
}
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.CALL_PHONE))
permissionNames.add(Manifest.permission.CALL_PHONE);
......
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
package com.example.example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
#
# NOTE: This podspec is NOT to be published. It is only used as a local source!
# This is a generated file; do not edit or check into version control.
#
Pod::Spec.new do |s|
s.name = 'Flutter'
s.version = '1.0.0'
s.summary = 'High-performance, high-fidelity mobile apps.'
s.description = <<-DESC
Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS.
DESC
s.homepage = 'https://flutter.io'
s.license = { :type => 'MIT' }
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
s.ios.deployment_target = '8.0'
s.vendored_frameworks = 'Flutter.framework'
# Framework linking is handled by Flutter tooling, not CocoaPods.
# Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs.
s.vendored_frameworks = 'path/to/nothing'
end
......@@ -10,75 +10,32 @@ project 'Runner', {
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_key_values = {}
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
generated_key_values[podname] = podpath
else
puts "Invalid plugin specification: #{line}"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
generated_key_values
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
target 'Runner' do
# Flutter Pod
copied_flutter_dir = File.join(__dir__, 'Flutter')
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
# Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
# That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
unless File.exist?(generated_xcode_build_settings_path)
raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
unless File.exist?(copied_framework_path)
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
end
unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end
end
# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'
flutter_ios_podfile_setup
# Plugin Pods
target 'Runner' do
use_frameworks!
use_modular_headers!
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink)
pod name, :path => File.join(symlink, 'ios')
end
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
flutter_additional_ios_build_settings(target)
end
end
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
......@@ -27,6 +27,8 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
......@@ -36,8 +38,8 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
......@@ -59,6 +61,8 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
......@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
......@@ -11,7 +11,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>permission_handler_example</string>
<string>example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
......@@ -40,7 +40,7 @@
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<false/>
<!-- Permission options for the `location` group -->
<key>NSLocationWhenInUseUsageDescription</key>
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array/>
</dict>
</plist>
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
//
// Generated file. Do not edit.
//
import 'package:url_launcher_web/url_launcher_web.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
// ignore: public_member_api_docs
void registerPlugins(Registrar registrar) {
UrlLauncherPlugin.registerWith(registrar);
registrar.registerMessageHandler();
}
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter/widgets.dart';
import 'template/globals.dart';
void main() {
runApp(BaseflowPluginExample());
}
void main() => runApp(MyApp());
/// A Flutter application demonstrating the functionality of this plugin
class BaseflowPluginExample extends StatelessWidget {
/// [MaterialColor] to be used in the app [ThemeData]
final MaterialColor themeMaterialColor =
createMaterialColor(const Color.fromRGBO(48, 49, 60, 1));
/// Example Flutter Application demonstrating the functionality of the
/// Permission Handler plugin.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.settings),
onPressed: () async {
var hasOpened = openAppSettings();
debugPrint('App Settings opened: ' + hasOpened.toString());
},
)
],
title: 'Baseflow $pluginName',
theme: ThemeData(
accentColor: Colors.white60,
backgroundColor: const Color.fromRGBO(48, 49, 60, 0.8),
buttonTheme: ButtonThemeData(
buttonColor: themeMaterialColor.shade500,
disabledColor: themeMaterialColor.withRed(200),
splashColor: themeMaterialColor.shade50,
textTheme: ButtonTextTheme.primary,
),
bottomAppBarColor: const Color.fromRGBO(57, 58, 71, 1),
hintColor: themeMaterialColor.shade500,
primarySwatch: createMaterialColor(const Color.fromRGBO(48, 49, 60, 1)),
textTheme: TextTheme(
bodyText1: TextStyle(
color: Colors.white,
fontSize: 16,
height: 1.3,
),
bodyText2: TextStyle(
color: Colors.white,
fontSize: 18,
height: 1.2,
),
button: TextStyle(color: Colors.white),
headline1: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
body: Center(
child: ListView(
children: Permission.values
.where((Permission permission) {
if (Platform.isIOS) {
return permission != Permission.unknown &&
permission != Permission.sms &&
//permission != Permission.storage &&
permission != Permission.ignoreBatteryOptimizations &&
permission != Permission.accessMediaLocation;
} else {
return permission != Permission.unknown &&
permission != Permission.mediaLibrary &&
permission != Permission.photos &&
permission != Permission.reminders;
}
})
.map((permission) => PermissionWidget(permission))
.toList()),
visualDensity: VisualDensity.adaptivePlatformDensity,
inputDecorationTheme: InputDecorationTheme(
fillColor: const Color.fromRGBO(37, 37, 37, 1),
filled: true,
),
),
home: AppHome(title: 'Baseflow $pluginName example app'),
);
}
}
/// Permission widget which displays a permission and allows users to request
/// the permissions.
class PermissionWidget extends StatefulWidget {
/// Constructs a [PermissionWidget] for the supplied [Permission].
const PermissionWidget(this._permission);
/// Creates a [MaterialColor] based on the supplied [Color]
static MaterialColor createMaterialColor(Color color) {
List strengths = <double>[.05];
Map swatch = <int, Color>{};
final r = color.red, g = color.green, b = color.blue;
final Permission _permission;
@override
_PermissionState createState() => _PermissionState(_permission);
for (var i = 1; i < 10; i++) {
strengths.add(0.1 * i);
}
for (var strength in strengths) {
final ds = 0.5 - strength;
swatch[(strength * 1000).round()] = Color.fromRGBO(
r + ((ds < 0 ? r : (255 - r)) * ds).round(),
g + ((ds < 0 ? g : (255 - g)) * ds).round(),
b + ((ds < 0 ? b : (255 - b)) * ds).round(),
1,
);
}
return MaterialColor(color.value, swatch);
}
}
class _PermissionState extends State<PermissionWidget> {
_PermissionState(this._permission);
/// A Flutter example demonstrating how the [pluginName] plugin could be used
class AppHome extends StatefulWidget {
/// Constructs the [AppHome] class
AppHome({Key key, this.title}) : super(key: key);
final Permission _permission;
PermissionStatus _permissionStatus = PermissionStatus.undetermined;
/// The [title] of the application, which is shown in the application's
/// title bar.
final String title;
@override
void initState() {
super.initState();
_listenForPermissionStatus();
}
void _listenForPermissionStatus() async {
final status = await _permission.status;
setState(() => _permissionStatus = status);
}
_AppHomeState createState() => _AppHomeState();
}
Color getPermissionColor() {
switch (_permissionStatus) {
case PermissionStatus.denied:
return Colors.red;
case PermissionStatus.granted:
return Colors.green;
default:
return Colors.grey;
}
}
class _AppHomeState extends State<AppHome> {
static final PageController _pageController = PageController(initialPage: 0);
int _currentPage = 0;
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(_permission.toString()),
subtitle: Text(
_permissionStatus.toString(),
style: TextStyle(color: getPermissionColor()),
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).bottomAppBarColor,
title: Center(
child: Image.asset(
'res/images/baseflow_logo_def_light-02.png',
width: 140,
),
),
),
backgroundColor: Theme.of(context).backgroundColor,
body: PageView(
controller: _pageController,
children: pages,
onPageChanged: (page) {
setState(() {
_currentPage = page;
});
},
),
trailing: IconButton(
icon: const Icon(Icons.info),
onPressed: () {
checkServiceStatus(context, _permission);
}),
onTap: () {
requestPermission(_permission);
},
bottomNavigationBar: _bottomAppBar(),
);
}
void checkServiceStatus(BuildContext context, Permission permission) async {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text((await permission.status).toString()),
));
BottomAppBar _bottomAppBar() {
return BottomAppBar(
elevation: 5,
color: Theme.of(context).bottomAppBarColor,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.unmodifiable(() sync* {
for (var i = 0; i < pages.length; i++) {
yield Expanded(
child: IconButton(
iconSize: 30,
icon: Icon(icons.elementAt(i)),
color: _bottomAppBarIconColor(i),
onPressed: () => _animateToPage(i),
),
);
}
}()),
),
);
}
Future<void> requestPermission(Permission permission) async {
final status = await permission.request();
void _animateToPage(int page) {
_pageController.animateToPage(page,
duration: Duration(milliseconds: 200), curve: Curves.linear);
}
setState(() {
print(status);
_permissionStatus = status;
print(_permissionStatus);
});
Color _bottomAppBarIconColor(int page) {
return _currentPage == page ? Colors.white : Theme.of(context).accentColor;
}
}
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'permission_widget.dart';
/// Constructs a [ListView] containing [PermissionWidget] for each available
/// permission.
class PermissionList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: ListView(
children: Permission.values
.where((permission) {
if (Platform.isIOS) {
return permission != Permission.unknown &&
permission != Permission.sms &&
//permission != Permission.storage &&
permission != Permission.ignoreBatteryOptimizations &&
permission != Permission.accessMediaLocation;
} else {
return permission != Permission.unknown &&
permission != Permission.mediaLibrary &&
permission != Permission.photos &&
permission != Permission.reminders;
}
})
.map((permission) => PermissionWidget(permission))
.toList()),
);
}
}
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
/// Permission widget which displays a permission and allows users to request
/// the permissions.
class PermissionWidget extends StatefulWidget {
/// Constructs a [PermissionWidget] for the supplied [Permission].
const PermissionWidget(this._permission);
final Permission _permission;
@override
_PermissionState createState() => _PermissionState(_permission);
}
class _PermissionState extends State<PermissionWidget> {
_PermissionState(this._permission);
final Permission _permission;
PermissionStatus _permissionStatus = PermissionStatus.undetermined;
@override
void initState() {
super.initState();
_listenForPermissionStatus();
}
void _listenForPermissionStatus() async {
final status = await _permission.status;
setState(() => _permissionStatus = status);
}
Color getPermissionColor() {
switch (_permissionStatus) {
case PermissionStatus.denied:
return Colors.red;
case PermissionStatus.granted:
return Colors.green;
case PermissionStatus.limited:
return Colors.orange;
default:
return Colors.grey;
}
}
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(
_permission.toString(),
style: Theme.of(context).textTheme.bodyText1,
),
subtitle: Text(
_permissionStatus.toString(),
style: TextStyle(color: getPermissionColor()),
),
trailing: IconButton(
icon: const Icon(
Icons.info,
color: Colors.white,
),
onPressed: () {
checkServiceStatus(context, _permission);
}),
onTap: () {
requestPermission(_permission);
},
);
}
void checkServiceStatus(BuildContext context, Permission permission) async {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text((await permission.status).toString()),
));
}
Future<void> requestPermission(Permission permission) async {
final status = await permission.request();
setState(() {
print(status);
_permissionStatus = status;
print(_permissionStatus);
});
}
}
import 'dart:core';
import 'package:flutter/material.dart';
import '../plugin_example/permission_list.dart';
import 'info_page.dart';
/// The name of the plugin, which will be displayed throughout the example App.
const String pluginName = 'Permission Handler';
/// Returns Github URL, which is shown in the [InfoPage].
const String githubURL =
'https://github.com/Baseflow/flutter-permission-handler';
/// Returns Baseflow URL, which is shown in the [InfoPage].
const String baseflowURL = 'https://baseflow.com';
/// Returns pub.dev URL, which is shown in the [InfoPage].
const String pubDevURL = 'https://pub.dev/packages/permission_handler';
/// [EdgeInsets] to define horizontal padding throughout the application.
const EdgeInsets defaultHorizontalPadding =
EdgeInsets.symmetric(horizontal: 24);
/// [EdgeInsets] to define vertical padding throughout the application.
const EdgeInsets defaultVerticalPadding = EdgeInsets.symmetric(vertical: 24);
/// Returns a [List] with [IconData] to show in the [AppHome] [AppBar].
final List<IconData> icons = [
Icons.list,
Icons.info_outline,
];
/// Returns a [List] with [Widget]s to construct pages in the [AppBar].
final List<Widget> pages = [
PermissionList(),
InfoPage(),
];
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:url_launcher/url_launcher.dart';
import 'globals.dart';
/// [StatelessWidget] displaying information about Baseflow
class InfoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox.expand(
child: Align(
alignment: Alignment.bottomCenter,
child: SingleChildScrollView(
child: Padding(
padding: defaultHorizontalPadding + defaultVerticalPadding,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Align(
alignment: Alignment.centerLeft,
child: Image.asset(
'res/images/poweredByBaseflowLogoLight@3x.png',
width: 250,
alignment: Alignment.centerLeft,
),
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 24),
),
Text(
'This app showcases the possibilities of the $pluginName '
'plugin, powered by Baseflow. '
'This plugin is available as open source project on Github. '
'\n\n'
'Need help with integrating functionalities within your own '
'apps? Contact us at hello@baseflow.com',
style: Theme.of(context).textTheme.bodyText1,
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8),
),
_launcherRaisedButton(
'Find us on Github',
githubURL,
context,
),
_launcherRaisedButton(
'Find us on pub.dev',
pubDevURL,
context,
),
_launcherRaisedButton(
'Visit baseflow.com',
baseflowURL,
context,
),
const Padding(
padding: EdgeInsets.only(bottom: 30),
),
],
),
),
),
),
);
}
Widget _launcherRaisedButton(String text, String url, BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: 50,
margin: const EdgeInsets.only(top: 24.0),
alignment: Alignment.center,
child: SizedBox.expand(
child: RaisedButton(
textTheme: Theme.of(context).buttonTheme.textTheme,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)),
padding: const EdgeInsets.all(8),
child: Text(text),
onPressed: () => _launchURL(url),
),
),
);
}
Future<void> _launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
}
......@@ -15,5 +15,12 @@ dev_dependencies:
permission_handler:
path: ../
url_launcher: ^5.4.11
flutter:
uses-material-design: true
assets:
- res/images/baseflow_logo_def_light-02.png
- res/images/poweredByBaseflowLogoLight@3x.png
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
......@@ -54,6 +54,13 @@
#define PERMISSION_PHOTOS 1
#endif
// ios: PermissionGroupPhotosAddOnly
// Info.plist: NSPhotoLibraryUsageDescription
// dart: PermissionGroup.photosAddOnly
#ifndef PERMISSION_PHOTOS_ADD_ONLY
#define PERMISSION_PHOTOS_ADD_ONLY 1
#endif
// ios: [PermissionGroupLocation, PermissionGroupLocationAlways, PermissionGroupLocationWhenInUse]
// Info.plist: [NSLocationUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription, NSLocationWhenInUseUsageDescription]
// dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
......@@ -92,6 +99,7 @@ typedef NS_ENUM(int, PermissionGroup) {
PermissionGroupMicrophone,
PermissionGroupPhone,
PermissionGroupPhotos,
PermissionGroupPhotosAddOnly,
PermissionGroupReminders,
PermissionGroupSensors,
PermissionGroupSms,
......@@ -108,6 +116,7 @@ typedef NS_ENUM(int, PermissionStatus) {
PermissionStatusGranted,
PermissionStatusRestricted,
PermissionStatusNotDetermined,
PermissionStatusLimited,
};
typedef NS_ENUM(int, ServiceStatus) {
......
......@@ -32,13 +32,19 @@
}
- (void)requestPermissions:(NSArray *)permissions completion:(PermissionRequestCompletion)completion {
NSMutableSet *requestQueue = [[NSMutableSet alloc] initWithArray:permissions];
NSMutableDictionary *permissionStatusResult = [[NSMutableDictionary alloc] init];
if (permissions.count == 0) {
completion(permissionStatusResult);
return;
}
NSMutableSet *requestQueue = [[NSMutableSet alloc] initWithArray:permissions];
for (int i = 0; i < permissions.count; ++i) {
PermissionGroup value;
[permissions[i] getValue:&value];
PermissionGroup permission = value;
NSNumber *rawNumberValue = permissions[i];
int rawValue = rawNumberValue.intValue;
PermissionGroup permission = (PermissionGroup) rawValue;
id <PermissionStrategy> permissionStrategy = [PermissionManager createPermissionStrategy:permission];
[_strategyInstances addObject:permissionStrategy];
......@@ -96,7 +102,9 @@
case PermissionGroupPhone:
return [PhonePermissionStrategy new];
case PermissionGroupPhotos:
return [PhotoPermissionStrategy new];
return [[PhotoPermissionStrategy alloc] initWithAccessAddOnly:false];
case PermissionGroupPhotosAddOnly:
return [[PhotoPermissionStrategy alloc] initWithAccessAddOnly:true];
case PermissionGroupReminders:
return [EventPermissionStrategy new];
case PermissionGroupSensors:
......
......@@ -11,6 +11,7 @@
#import <Photos/Photos.h>
@interface PhotoPermissionStrategy : NSObject <PermissionStrategy>
-(instancetype)initWithAccessAddOnly:(BOOL) addOnly;
@end
#else
......
......@@ -7,9 +7,21 @@
#if PERMISSION_PHOTOS
@implementation PhotoPermissionStrategy
@implementation PhotoPermissionStrategy{
bool addOnlyAccessLevel;
}
- (instancetype)initWithAccessAddOnly:(BOOL)addOnly {
self = [super init];
if(self) {
addOnlyAccessLevel = addOnly;
}
return self;
}
- (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission {
return [PhotoPermissionStrategy permissionStatus];
return [PhotoPermissionStrategy permissionStatus:addOnlyAccessLevel];
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
......@@ -24,13 +36,24 @@
return;
}
if(@available(iOS 14, *)) {
[PHPhotoLibrary requestAuthorizationForAccessLevel:(addOnlyAccessLevel)?PHAccessLevelAddOnly:PHAccessLevelReadWrite handler:^(PHAuthorizationStatus authorizationStatus) {
completionHandler([PhotoPermissionStrategy determinePermissionStatus:authorizationStatus]);
}];
}else {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus authorizationStatus) {
completionHandler([PhotoPermissionStrategy determinePermissionStatus:authorizationStatus]);
}];
}
}
+ (PermissionStatus)permissionStatus {
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
+ (PermissionStatus)permissionStatus:(BOOL) addOnlyAccessLevel {
PHAuthorizationStatus status;
if(@available(iOS 14, *)){
status = [PHPhotoLibrary authorizationStatusForAccessLevel:(addOnlyAccessLevel)?PHAccessLevelAddOnly:PHAccessLevelReadWrite];
}else {
status = [PHPhotoLibrary authorizationStatus];
}
return [PhotoPermissionStrategy determinePermissionStatus:status];
}
......@@ -45,6 +68,8 @@
return PermissionStatusDenied;
case PHAuthorizationStatusAuthorized:
return PermissionStatusGranted;
case PHAuthorizationStatusLimited:
return PermissionStatusLimited;
}
return PermissionStatusNotDetermined;
......
......@@ -23,8 +23,8 @@ Future<bool> openAppSettings() => _handler.openAppSettings();
/// Actions that can be executed on a permission.
extension PermissionActions on Permission {
/// The current status of this permission.
///
/// The Android-only [PermissionStatus.permanentlyDenied] status will only be
///
/// The Android-only [PermissionStatus.permanentlyDenied] status will only be
/// calculated if the active context is an Activity. If it isn't,
/// [PermissionStatus.denied] will be returned.
Future<PermissionStatus> get status => _handler.checkPermissionStatus(this);
......@@ -67,6 +67,10 @@ extension PermissionCheckShortcuts on Permission {
/// *Only supported on iOS.*
Future<bool> get isRestricted => status.isRestricted;
///User has authorized this application for limited photo library access.
/// *Only supported on iOS.(iOS14+)*
Future<bool> get isLimited => status.isLimited;
/// If the user denied this permission and selected to never again show a
/// request for it. The user may still change the permission's status in the
/// device settings.
......
name: permission_handler
description: Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
version: 5.0.1+1
version: 5.1.0
homepage: https://github.com/baseflowit/flutter-permission-handler
flutter:
......@@ -16,7 +16,7 @@ dependencies:
flutter:
sdk: flutter
meta: ^1.1.6
permission_handler_platform_interface: ^2.0.1
permission_handler_platform_interface: ^2.0.2
dev_dependencies:
effective_dart: ^1.2.1
......
## 2.0.2
* Added support for the limited photos permission available on iOS 14 and up.
## 2.0.1
* Update `platform_interface 1.0.2`
......
......@@ -17,6 +17,10 @@ enum PermissionStatus {
/// *Only supported on iOS.*
restricted,
///User has authorized this application for limited access.
/// *Only supported on iOS (iOS14+).*
limited,
/// The user denied access to the requested feature and selected to never
/// again show a request for this permission. The user may still change the
/// permission status in the settings.
......@@ -36,6 +40,8 @@ extension PermissionStatusValue on PermissionStatus {
case PermissionStatus.undetermined:
return 3;
case PermissionStatus.permanentlyDenied:
return 5;
case PermissionStatus.limited:
return 4;
default:
throw UnimplementedError();
......@@ -48,6 +54,7 @@ extension PermissionStatusValue on PermissionStatus {
PermissionStatus.granted,
PermissionStatus.restricted,
PermissionStatus.undetermined,
PermissionStatus.limited,
PermissionStatus.permanentlyDenied,
][value];
}
......@@ -74,6 +81,8 @@ extension PermissionStatusGetters on PermissionStatus {
/// permission status in the settings.
/// *Only supported on Android.*
bool get isPermanentlyDenied => this == PermissionStatus.permanentlyDenied;
bool get isLimited => this == PermissionStatus.limited;
}
extension FuturePermissionStatusGetters on Future<PermissionStatus> {
......@@ -98,4 +107,6 @@ extension FuturePermissionStatusGetters on Future<PermissionStatus> {
/// *Only supported on Android.*
Future<bool> get isPermanentlyDenied async =>
(await this).isPermanentlyDenied;
Future<bool> get isLimited async => (await this).isLimited;
}
......@@ -55,47 +55,53 @@ class Permission {
/// Android: Nothing
/// iOS: Photos
/// iOS 14+ read & write access level
static const photos = Permission._(9);
/// Android: Nothing
/// iOS: Photos
/// iOS 14+ read & write access level
static const photosAddOnly = Permission._(10);
/// Android: Nothing
/// iOS: Reminders
static const reminders = Permission._(10);
static const reminders = Permission._(11);
/// Android: Body Sensors
/// iOS: CoreMotion
static const sensors = Permission._(11);
static const sensors = Permission._(12);
/// Android: Sms
/// iOS: Nothing
static const sms = Permission._(12);
static const sms = Permission._(13);
/// Android: Microphone
/// iOS: Speech
static const speech = Permission._(13);
static const speech = Permission._(14);
/// Android: External Storage
/// iOS: Access to folders like `Documents` or `Downloads`. Implicitly
/// granted.
static const storage = Permission._(14);
static const storage = Permission._(15);
/// Android: Ignore Battery Optimizations
static const ignoreBatteryOptimizations = Permission._(15);
static const ignoreBatteryOptimizations = Permission._(16);
/// Android: Notification
/// iOS: Notification
static const notification = Permission._(16);
static const notification = Permission._(17);
/// Android: Allows an application to access any geographic locations
/// persisted in the user's shared collection.
static const accessMediaLocation = Permission._(17);
static const accessMediaLocation = Permission._(18);
/// When running on Android Q and above: Activity Recognition
/// When running on Android < Q: Nothing
/// iOS: Nothing
static const activityRecognition = Permission._(18);
static const activityRecognition = Permission._(19);
/// The unknown only used for return type, never requested
static const unknown = Permission._(19);
static const unknown = Permission._(20);
/// Returns a list of all possible [PermissionGroup] values.
static const List<Permission> values = <Permission>[
......@@ -109,6 +115,7 @@ class Permission {
microphone,
phone,
photos,
photosAddOnly,
reminders,
sensors,
sms,
......@@ -132,6 +139,7 @@ class Permission {
'microphone',
'phone',
'photos',
'photosAddOnly',
'reminders',
'sensors',
'sms',
......
......@@ -3,7 +3,7 @@ description: A common platform interface for the permission_handler plugin.
homepage: https://github.com/baseflow/flutter-permission-handler/tree/master/permission_handler_platform_interface
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
version: 2.0.1
version: 2.0.2
dependencies:
flutter:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment