do not force quit on storage permission denial

This commit is contained in:
Thibault Deckers 2022-06-04 12:24:19 +09:00
parent b7a4da17d8
commit 4079e5bddd
5 changed files with 86 additions and 23 deletions

View file

@ -529,6 +529,7 @@
"collectionEmptyFavourites": "No favorites", "collectionEmptyFavourites": "No favorites",
"collectionEmptyVideos": "No videos", "collectionEmptyVideos": "No videos",
"collectionEmptyImages": "No images", "collectionEmptyImages": "No images",
"collectionEmptyGrantAccessButtonLabel": "Grant access",
"collectionSelectSectionTooltip": "Select section", "collectionSelectSectionTooltip": "Select section",
"collectionDeselectSectionTooltip": "Deselect section", "collectionDeselectSectionTooltip": "Deselect section",

View file

@ -29,6 +29,7 @@ import 'package:aves/widgets/common/grid/section_layout.dart';
import 'package:aves/widgets/common/grid/selector.dart'; import 'package:aves/widgets/common/grid/selector.dart';
import 'package:aves/widgets/common/grid/sliver.dart'; import 'package:aves/widgets/common/grid/sliver.dart';
import 'package:aves/widgets/common/grid/theme.dart'; import 'package:aves/widgets/common/grid/theme.dart';
import 'package:aves/widgets/common/identity/buttons.dart';
import 'package:aves/widgets/common/identity/empty.dart'; import 'package:aves/widgets/common/identity/empty.dart';
import 'package:aves/widgets/common/identity/scroll_thumb.dart'; import 'package:aves/widgets/common/identity/scroll_thumb.dart';
import 'package:aves/widgets/common/providers/tile_extent_controller_provider.dart'; import 'package:aves/widgets/common/providers/tile_extent_controller_provider.dart';
@ -39,6 +40,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -305,13 +307,15 @@ class _CollectionScrollView extends StatefulWidget {
State<_CollectionScrollView> createState() => _CollectionScrollViewState(); State<_CollectionScrollView> createState() => _CollectionScrollViewState();
} }
class _CollectionScrollViewState extends State<_CollectionScrollView> { class _CollectionScrollViewState extends State<_CollectionScrollView> with WidgetsBindingObserver {
Timer? _scrollMonitoringTimer; Timer? _scrollMonitoringTimer;
bool _checkingStoragePermission = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_registerWidget(widget); _registerWidget(widget);
WidgetsBinding.instance.addObserver(this);
} }
@override @override
@ -323,6 +327,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> {
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this);
_unregisterWidget(widget); _unregisterWidget(widget);
_stopScrollMonitoringTimer(); _stopScrollMonitoringTimer();
super.dispose(); super.dispose();
@ -340,6 +345,26 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> {
widget.scrollController.removeListener(_onScrollChange); widget.scrollController.removeListener(_onScrollChange);
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
case AppLifecycleState.detached:
break;
case AppLifecycleState.resumed:
if (_checkingStoragePermission) {
_checkingStoragePermission = false;
_isStoragePermissionGranted.then((granted) {
if (granted) {
widget.collection.source.init();
}
});
}
break;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final scrollView = _buildScrollView(widget.appBar, widget.collection); final scrollView = _buildScrollView(widget.appBar, widget.collection);
@ -423,23 +448,47 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> {
valueListenable: collection.source.stateNotifier, valueListenable: collection.source.stateNotifier,
builder: (context, sourceState, child) { builder: (context, sourceState, child) {
if (sourceState == SourceState.loading) { if (sourceState == SourceState.loading) {
return const SizedBox.shrink(); return const SizedBox();
} }
return FutureBuilder<bool>(
future: _isStoragePermissionGranted,
builder: (context, snapshot) {
final granted = snapshot.data ?? true;
Widget? bottom = granted
? null
: Padding(
padding: const EdgeInsets.only(top: 16),
child: AvesOutlinedButton(
label: context.l10n.collectionEmptyGrantAccessButtonLabel,
onPressed: () async {
if (await openAppSettings()) {
_checkingStoragePermission = true;
}
},
),
);
if (collection.filters.any((filter) => filter is FavouriteFilter)) { if (collection.filters.any((filter) => filter is FavouriteFilter)) {
return EmptyContent( return EmptyContent(
icon: AIcons.favourite, icon: AIcons.favourite,
text: context.l10n.collectionEmptyFavourites, text: context.l10n.collectionEmptyFavourites,
bottom: bottom,
); );
} }
if (collection.filters.any((filter) => filter is MimeFilter && filter.mime == MimeTypes.anyVideo)) { if (collection.filters.any((filter) => filter is MimeFilter && filter.mime == MimeTypes.anyVideo)) {
return EmptyContent( return EmptyContent(
icon: AIcons.video, icon: AIcons.video,
text: context.l10n.collectionEmptyVideos, text: context.l10n.collectionEmptyVideos,
bottom: bottom,
); );
} }
return EmptyContent( return EmptyContent(
icon: AIcons.image, icon: AIcons.image,
text: context.l10n.collectionEmptyImages, text: context.l10n.collectionEmptyImages,
bottom: bottom,
);
},
); );
}, },
); );
@ -519,4 +568,6 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> {
} }
return crumbs; return crumbs;
} }
Future<bool> get _isStoragePermissionGranted => Permission.storage.status.then((status) => status.isGranted);
} }

View file

@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
class EmptyContent extends StatelessWidget { class EmptyContent extends StatelessWidget {
final IconData? icon; final IconData? icon;
final String text; final String text;
final Widget? bottom;
final AlignmentGeometry alignment; final AlignmentGeometry alignment;
final double fontSize; final double fontSize;
final bool safeBottom; final bool safeBottom;
@ -13,6 +14,7 @@ class EmptyContent extends StatelessWidget {
super.key, super.key,
this.icon, this.icon,
required this.text, required this.text,
this.bottom,
this.alignment = const FractionalOffset(.5, .35), this.alignment = const FractionalOffset(.5, .35),
this.fontSize = 22, this.fontSize = 22,
this.safeBottom = true, this.safeBottom = true,
@ -48,6 +50,7 @@ class EmptyContent extends StatelessWidget {
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
if (bottom != null) bottom!,
], ],
), ),
), ),

View file

@ -24,7 +24,6 @@ import 'package:aves/widgets/search/search_delegate.dart';
import 'package:aves/widgets/viewer/entry_viewer_page.dart'; import 'package:aves/widgets/viewer/entry_viewer_page.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -66,15 +65,14 @@ class _HomePageState extends State<HomePage> {
Future<void> _setup() async { Future<void> _setup() async {
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
final permissions = await [ // do not check whether permission was granted,
// as some app stores hide in some countries
// apps that force quit on permission denial
await [
Permission.storage, Permission.storage,
// to access media with unredacted metadata with scoped storage (Android 10+) // to access media with unredacted metadata with scoped storage (Android 10+)
Permission.accessMediaLocation, Permission.accessMediaLocation,
].request(); ].request();
if (permissions[Permission.storage] != PermissionStatus.granted) {
unawaited(SystemNavigator.pop());
return;
}
await androidFileUtils.init(); await androidFileUtils.init();
if (settings.isInstalledAppAccessAllowed) { if (settings.isInstalledAppAccessAllowed) {

View file

@ -1,43 +1,53 @@
{ {
"de": [ "de": [
"collectionEmptyGrantAccessButtonLabel",
"settingsThemeEnableDynamicColor" "settingsThemeEnableDynamicColor"
], ],
"es": [ "es": [
"collectionEmptyGrantAccessButtonLabel",
"settingsShowBottomNavigationBar", "settingsShowBottomNavigationBar",
"settingsThumbnailShowTagIcon", "settingsThumbnailShowTagIcon",
"settingsThemeEnableDynamicColor" "settingsThemeEnableDynamicColor"
], ],
"fr": [ "fr": [
"collectionEmptyGrantAccessButtonLabel",
"settingsThemeEnableDynamicColor" "settingsThemeEnableDynamicColor"
], ],
"id": [ "id": [
"collectionEmptyGrantAccessButtonLabel",
"settingsThemeEnableDynamicColor" "settingsThemeEnableDynamicColor"
], ],
"it": [ "it": [
"collectionEmptyGrantAccessButtonLabel",
"settingsThemeEnableDynamicColor" "settingsThemeEnableDynamicColor"
], ],
"ja": [ "ja": [
"collectionEmptyGrantAccessButtonLabel",
"settingsThemeEnableDynamicColor" "settingsThemeEnableDynamicColor"
], ],
"ko": [ "ko": [
"collectionEmptyGrantAccessButtonLabel",
"settingsThemeEnableDynamicColor" "settingsThemeEnableDynamicColor"
], ],
"pt": [ "pt": [
"collectionEmptyGrantAccessButtonLabel",
"settingsThemeEnableDynamicColor" "settingsThemeEnableDynamicColor"
], ],
"ru": [ "ru": [
"collectionEmptyGrantAccessButtonLabel",
"settingsThemeEnableDynamicColor" "settingsThemeEnableDynamicColor"
], ],
"zh": [ "zh": [
"collectionEmptyGrantAccessButtonLabel",
"settingsThemeEnableDynamicColor" "settingsThemeEnableDynamicColor"
] ]
} }