release/1.6.2+20 #76
11 changed files with 128 additions and 91 deletions
|
@ -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
|
||||
|
|
|
@ -39,7 +39,7 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "de.varakh.fbmobile"
|
||||
minSdkVersion 19
|
||||
minSdkVersion 30
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
|
|
|
@ -30,7 +30,10 @@
|
|||
</application>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<DialogService>();
|
||||
final StorageService _storageService = locator<StorageService>();
|
||||
|
||||
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();
|
||||
// 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'));
|
||||
|
||||
if (!response.confirmed!) {
|
||||
await _storageService.storeStoragePermissionDialogIgnored();
|
||||
} else {
|
||||
// not all have been granted, show OS dialog
|
||||
_logger.d(
|
||||
"Not all permissions have been granted yet, initializing permission dialog");
|
||||
_devicePermissionDialogActive = true;
|
||||
Permission.storage.request().then((status) async {
|
||||
if (PermissionStatus.permanentlyDenied == status) {
|
||||
await _storageService.storeStoragePermissionDialogIgnored();
|
||||
}
|
||||
}).whenComplete(() {
|
||||
|
||||
if (_useStoragePermission) {
|
||||
await [Permission.storage].request().whenComplete(() {
|
||||
_logger.d('Device request permission finished');
|
||||
_devicePermissionDialogActive = false;
|
||||
});
|
||||
}
|
||||
|
||||
_ownPermissionDialogActive = 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();
|
||||
|
|
|
@ -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<bool> storeLastUrl(String url) {
|
||||
return _store(_lastUrlKey, url);
|
||||
|
@ -39,14 +37,6 @@ class StorageService {
|
|||
return _remove(_sessionKey);
|
||||
}
|
||||
|
||||
Future<bool> storeStoragePermissionDialogIgnored() {
|
||||
return _store(_storagePermissionDialogIgnoredKey, true.toString());
|
||||
}
|
||||
|
||||
Future<bool> hasStoragePermissionDialogIgnored() {
|
||||
return _exists(_storagePermissionDialogIgnoredKey);
|
||||
}
|
||||
|
||||
Future<bool> _exists(String key) async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
return prefs.containsKey(key);
|
||||
|
|
|
@ -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<SessionService>();
|
||||
final PermissionService _permissionService = locator<PermissionService>();
|
||||
final NavigationService _navigationService = locator<NavigationService>();
|
||||
|
||||
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);
|
||||
|
||||
|
|
24
pubspec.lock
24
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:
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue