diff --git a/CHANGELOG.md b/CHANGELOG.md
index 34c9438..8358f70 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,10 @@
## 1.6.2+20 - UNRELEASED
* Updated internal dependencies
-* Move progress indicator of _Show Configuration_ into button
+* Moved progress indicator of _Show Configuration_ into the underlying button
+* Bumped Android minSdk to `30` (Android 11)
+* Fixed permission service not handling Android SDK 33 correctly
+* Fixed permission service not being started during application start
## 1.6.1+19
* Updated internal dependencies
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 070d44a..79db66a 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -39,7 +39,7 @@ android {
defaultConfig {
applicationId "de.varakh.fbmobile"
- minSdkVersion 19
+ minSdkVersion 30
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
index 5399da6..9a1fc0d 100644
--- a/android/app/src/debug/AndroidManifest.xml
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -4,7 +4,10 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
-
+
+
+
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 2c6fc11..427ec05 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -30,7 +30,10 @@
-
+
+
+
+
diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml
index 5399da6..9a1fc0d 100644
--- a/android/app/src/profile/AndroidManifest.xml
+++ b/android/app/src/profile/AndroidManifest.xml
@@ -4,7 +4,10 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
-
+
+
+
+
diff --git a/assets/i18n/en.json b/assets/i18n/en.json
index 1d5cea4..bbfd3a8 100644
--- a/assets/i18n/en.json
+++ b/assets/i18n/en.json
@@ -126,14 +126,6 @@
"description": "Could not open '{link}'. Please ensure that you have an application installed which handles opening such link types."
}
},
- "permission_service": {
- "dialog": {
- "title": "Storage permission",
- "description": "Storage permission should be granted to the app so that it can work properly. Do you want to grant permission or ignore this message permanently in the future?",
- "grant": "Grant",
- "ignore": "Ignore"
- }
- },
"dialog": {
"confirm": "OK",
"cancel": "Cancel"
diff --git a/lib/core/services/permission_service.dart b/lib/core/services/permission_service.dart
index 4c5dcdc..ad091f8 100644
--- a/lib/core/services/permission_service.dart
+++ b/lib/core/services/permission_service.dart
@@ -1,108 +1,100 @@
import 'dart:async';
+import 'dart:io' show Platform;
-import 'package:flutter_translate/flutter_translate.dart';
+import 'package:device_info_plus/device_info_plus.dart';
import 'package:logger/logger.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../constants.dart';
-import '../../core/datamodels/dialog_response.dart';
-import '../../core/services/dialog_service.dart';
import '../../core/services/stoppable_service.dart';
import '../../core/util/logger.dart';
-import '../../locator.dart';
-import 'storage_service.dart';
class PermissionService extends StoppableService {
final Logger _logger = getLogger();
- final DialogService _dialogService = locator();
- final StorageService _storageService = locator();
Timer? _serviceCheckTimer;
- PermissionStatus? _permissionStatus;
-
- bool _permanentlyIgnored = false;
bool _devicePermissionDialogActive = false;
- bool _ownPermissionDialogActive = false;
- PermissionService() {
- _devicePermissionDialogActive = true;
+ bool _deviceInformationInitialized = false;
+ bool _useStoragePermission = true;
- Permission.storage.request().then((status) {
- _permissionStatus = status;
- if (PermissionStatus.permanentlyDenied == status) {
- _permanentlyIgnored = true;
- }
- }).whenComplete(() {
- _logger.d('Initial device request permission finished');
- _devicePermissionDialogActive = false;
- });
- }
+ PermissionService();
Future checkEnabledAndPermission() async {
- if (_permanentlyIgnored) {
- await _storageService.storeStoragePermissionDialogIgnored();
- _permanentlyIgnored = false;
- _logger.d('Set permanently ignored permission request');
- stop();
- }
-
if (_devicePermissionDialogActive) {
_logger.d('Device permission dialog active, skipping');
return;
}
- if (_ownPermissionDialogActive) {
- _logger.d('Own permission dialog already active, skipping');
- return;
+ bool allGranted = false;
+ bool anyPermanentlyDenied = false;
+
+ // Since Android compileSdk >= 33, "storage" is deprecated
+ // Instead, request access to all of
+ // - Permission.photos
+ // - Permission.videos
+ // - Permission.audio
+ //
+ // For iOS and Android < 33, keep using "storage"
+ if (_useStoragePermission) {
+ PermissionStatus storagePermission = await Permission.storage.status;
+ allGranted = PermissionStatus.granted == storagePermission;
+ anyPermanentlyDenied =
+ PermissionStatus.permanentlyDenied == storagePermission;
+ } else {
+ PermissionStatus photosPermission = await Permission.photos.status;
+ PermissionStatus videosPermission = await Permission.videos.status;
+ PermissionStatus audioPermission = await Permission.audio.status;
+
+ allGranted = PermissionStatus.granted == photosPermission &&
+ PermissionStatus.granted == videosPermission &&
+ PermissionStatus.granted == audioPermission;
+ anyPermanentlyDenied =
+ PermissionStatus.permanentlyDenied == photosPermission ||
+ PermissionStatus.permanentlyDenied == videosPermission ||
+ PermissionStatus.permanentlyDenied == audioPermission;
}
- var ignoredDialog =
- await _storageService.hasStoragePermissionDialogIgnored();
-
- if (ignoredDialog) {
- _logger.d('Permanently ignored permission request, skipping');
+ // show warning to user to manually handle, don't enforce it over and over again
+ if (anyPermanentlyDenied) {
+ _logger.w(
+ "At least one required permission has been denied permanently, stopping service");
stop();
return;
}
- _permissionStatus = await Permission.storage.status;
- if (_permissionStatus != PermissionStatus.granted) {
- if (_permissionStatus == PermissionStatus.permanentlyDenied) {
- await _storageService.storeStoragePermissionDialogIgnored();
- return;
- }
+ // all good, stop the permission service
+ if (allGranted) {
+ _logger.d("All permissions have been granted, stopping service");
+ stop();
+ return;
+ }
- _ownPermissionDialogActive = true;
- DialogResponse response = await _dialogService.showConfirmationDialog(
- title: translate('permission_service.dialog.title'),
- description: translate('permission_service.dialog.description'),
- buttonTitleAccept: translate('permission_service.dialog.grant'),
- buttonTitleDeny: translate('permission_service.dialog.ignore'));
+ // not all have been granted, show OS dialog
+ _logger.d(
+ "Not all permissions have been granted yet, initializing permission dialog");
+ _devicePermissionDialogActive = true;
- if (!response.confirmed!) {
- await _storageService.storeStoragePermissionDialogIgnored();
- } else {
- _devicePermissionDialogActive = true;
- Permission.storage.request().then((status) async {
- if (PermissionStatus.permanentlyDenied == status) {
- await _storageService.storeStoragePermissionDialogIgnored();
- }
- }).whenComplete(() {
- _logger.d('Device request permission finished');
- _devicePermissionDialogActive = false;
- });
- }
-
- _ownPermissionDialogActive = false;
+ if (_useStoragePermission) {
+ await [Permission.storage].request().whenComplete(() {
+ _logger.d('Device request permission finished');
+ _devicePermissionDialogActive = false;
+ });
} else {
- await _storageService.storeStoragePermissionDialogIgnored();
+ await [Permission.photos, Permission.videos, Permission.audio]
+ .request()
+ .whenComplete(() {
+ _logger.d('Device request permission finished');
+ _devicePermissionDialogActive = false;
+ });
}
}
@override
Future start() async {
super.start();
+ await _determineDeviceInfo();
await checkEnabledAndPermission();
_serviceCheckTimer = Timer.periodic(
@@ -124,6 +116,29 @@ class PermissionService extends StoppableService {
_logger.d('PermissionService stopped');
}
+ Future _determineDeviceInfo() async {
+ if (_deviceInformationInitialized) {
+ _logger.d('Device information already initialized, skipping');
+ return;
+ }
+
+ DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
+ if (Platform.isAndroid) {
+ final androidInfo = await deviceInfoPlugin.androidInfo;
+ if (androidInfo.version.sdkInt >= 33) {
+ _useStoragePermission = false;
+ }
+ }
+
+ if (_useStoragePermission) {
+ _logger.d('Device requires [storage] permission');
+ } else {
+ _logger.d('Device requires [photos,videos,audio] permission');
+ }
+
+ _deviceInformationInitialized = true;
+ }
+
void _removeServiceCheckTimer() {
if (_serviceCheckTimer != null) {
_serviceCheckTimer!.cancel();
diff --git a/lib/core/services/storage_service.dart b/lib/core/services/storage_service.dart
index 95f08c4..ba3ce5c 100644
--- a/lib/core/services/storage_service.dart
+++ b/lib/core/services/storage_service.dart
@@ -7,8 +7,6 @@ import '../models/session.dart';
class StorageService {
static const _sessionKey = 'session';
static const _lastUrlKey = 'last_url';
- static const _storagePermissionDialogIgnoredKey =
- 'storage_permission_ignored';
Future storeLastUrl(String url) {
return _store(_lastUrlKey, url);
@@ -39,14 +37,6 @@ class StorageService {
return _remove(_sessionKey);
}
- Future storeStoragePermissionDialogIgnored() {
- return _store(_storagePermissionDialogIgnoredKey, true.toString());
- }
-
- Future hasStoragePermissionDialogIgnored() {
- return _exists(_storagePermissionDialogIgnoredKey);
- }
-
Future _exists(String key) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.containsKey(key);
diff --git a/lib/core/viewmodels/startup_model.dart b/lib/core/viewmodels/startup_model.dart
index 767e1cb..afc8eac 100644
--- a/lib/core/viewmodels/startup_model.dart
+++ b/lib/core/viewmodels/startup_model.dart
@@ -1,3 +1,4 @@
+import 'package:fbmobile/core/services/permission_service.dart';
import 'package:flutter_translate/flutter_translate.dart';
import '../../locator.dart';
@@ -9,16 +10,18 @@ import 'base_model.dart';
class StartUpViewModel extends BaseModel {
final SessionService _sessionService = locator();
+ final PermissionService _permissionService = locator();
final NavigationService _navigationService = locator();
Future handleStartUpLogic() async {
setStateView(ViewState.busy);
setStateMessage(translate('startup.init'));
- await Future.delayed(const Duration(milliseconds: 150));
+ await Future.delayed(const Duration(milliseconds: 100));
setStateMessage(translate('startup.start_services'));
await _sessionService.start();
- await Future.delayed(const Duration(milliseconds: 150));
+ await _permissionService.start();
+ await Future.delayed(const Duration(milliseconds: 100));
_navigationService.navigateAndReplaceTo(HomeView.routeName);
diff --git a/pubspec.lock b/pubspec.lock
index 0f4f523..1c1344c 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -201,6 +201,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.4"
+ device_info_plus:
+ dependency: "direct main"
+ description:
+ name: device_info_plus
+ sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "9.1.1"
+ device_info_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: device_info_plus_platform_interface
+ sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.0"
dynamic_color:
dependency: "direct main"
description:
@@ -994,6 +1010,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.1.0"
+ win32_registry:
+ dependency: transitive
+ description:
+ name: win32_registry
+ sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
xdg_directories:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index ab73f76..48ac2c2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -43,6 +43,7 @@ dependencies:
intl: 0.18.1
path: 1.8.3
flutter_sharing_intent: 1.1.0
+ device_info_plus: 9.1.1
dev_dependencies:
flutter_test: