settings: changed layout for hidden filters and access grants

This commit is contained in:
Thibault Deckers 2021-02-17 11:44:59 +09:00
parent cadd2b4d1c
commit f16d98ba2b
4 changed files with 151 additions and 94 deletions

View file

@ -6,7 +6,7 @@ class EmptyContent extends StatelessWidget {
final AlignmentGeometry alignment; final AlignmentGeometry alignment;
const EmptyContent({ const EmptyContent({
@required this.icon, this.icon,
@required this.text, @required this.text,
this.alignment = const FractionalOffset(.5, .35), this.alignment = const FractionalOffset(.5, .35),
}); });
@ -19,12 +19,14 @@ class EmptyContent extends StatelessWidget {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon( if (icon != null) ...[
icon, Icon(
size: 64, icon,
color: color, size: 64,
), color: color,
SizedBox(height: 16), ),
SizedBox(height: 16)
],
Text( Text(
text, text,
style: TextStyle( style: TextStyle(

View file

@ -1,13 +1,34 @@
import 'package:aves/services/android_file_service.dart'; import 'package:aves/services/android_file_service.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/collection/empty.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class GrantedDirectories extends StatefulWidget { class StorageAccessTile extends StatelessWidget {
@override @override
_GrantedDirectoriesState createState() => _GrantedDirectoriesState(); Widget build(BuildContext context) {
return ListTile(
title: Text('Storage Access'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(name: StorageAccessPage.routeName),
builder: (context) => StorageAccessPage(),
),
);
},
);
}
} }
class _GrantedDirectoriesState extends State<GrantedDirectories> { class StorageAccessPage extends StatefulWidget {
static const routeName = '/settings/storage_access';
@override
_StorageAccessPageState createState() => _StorageAccessPageState();
}
class _StorageAccessPageState extends State<StorageAccessPage> {
Future<List<String>> _pathLoader; Future<List<String>> _pathLoader;
List<String> _lastPaths; List<String> _lastPaths;
@ -21,44 +42,64 @@ class _GrantedDirectoriesState extends State<GrantedDirectories> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme; return Scaffold(
return Padding( appBar: AppBar(
padding: EdgeInsets.symmetric(horizontal: 16), title: Text('Storage Access'),
child: FutureBuilder<List<String>>( ),
future: _pathLoader, body: SafeArea(
builder: (context, snapshot) { child: Column(
if (snapshot.hasError) { crossAxisAlignment: CrossAxisAlignment.start,
return Text(snapshot.error.toString()); children: [
} Padding(
if (snapshot.connectionState != ConnectionState.done && _lastPaths == null) { padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
return SizedBox.shrink(); child: Row(
} children: [
_lastPaths = snapshot.data..sort(); Icon(AIcons.info),
final count = _lastPaths.length; SizedBox(width: 16),
return Column( Expanded(child: Text('Some directories require an explicit access grant to modify files in them. You can review here directories to which you previously gave access.')),
crossAxisAlignment: CrossAxisAlignment.start, ],
children: [
Text(
'Aves has access to ${Intl.plural(count, zero: 'no directories.', one: 'one directory:', other: '$count directories:')}',
style: textTheme.subtitle1,
), ),
..._lastPaths.map((path) => Row( ),
children: [ Divider(),
Expanded(child: Text(path, style: textTheme.caption)), Expanded(
SizedBox(width: 8), child: FutureBuilder<List<String>>(
OutlinedButton( future: _pathLoader,
onPressed: () async { builder: (context, snapshot) {
await AndroidFileService.revokeDirectoryAccess(path); if (snapshot.hasError) {
_load(); return Text(snapshot.error.toString());
setState(() {}); }
}, if (snapshot.connectionState != ConnectionState.done && _lastPaths == null) {
child: Text('Revoke'.toUpperCase()), return SizedBox.shrink();
), }
], _lastPaths = snapshot.data..sort();
)), if (_lastPaths.isEmpty) {
], return EmptyContent(
); text: 'No access grants',
}, );
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _lastPaths
.map((path) => ListTile(
title: Text(path),
dense: true,
trailing: IconButton(
icon: Icon(AIcons.clear),
onPressed: () async {
await AndroidFileService.revokeDirectoryAccess(path);
_load();
setState(() {});
},
tooltip: 'Revoke',
),
))
.toList(),
);
},
),
),
],
),
), ),
); );
} }

View file

@ -1,34 +1,26 @@
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/collection/empty.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class HiddenFilters extends StatelessWidget { class HiddenFilterTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Selector<Settings, Set<CollectionFilter>>( return ListTile(
selector: (context, s) => s.hiddenFilters, title: Text('Hidden filters'),
builder: (context, hiddenFilters, child) { onTap: () {
return ListTile( Navigator.push(
title: hiddenFilters.isEmpty ? Text('There are no hidden filters') : Text('Hidden filters'), context,
trailing: hiddenFilters.isEmpty MaterialPageRoute(
? null settings: RouteSettings(name: HiddenFilterPage.routeName),
: OutlinedButton( builder: (context) => HiddenFilterPage(),
onPressed: () { ),
Navigator.push( );
context, },
MaterialPageRoute( );
settings: RouteSettings(name: HiddenFilterPage.routeName),
builder: (context) => HiddenFilterPage(),
),
);
},
child: Text('Edit'.toUpperCase()),
),
);
});
} }
} }
@ -42,24 +34,49 @@ class HiddenFilterPage extends StatelessWidget {
title: Text('Hidden Filters'), title: Text('Hidden Filters'),
), ),
body: SafeArea( body: SafeArea(
child: Padding( child: Column(
padding: EdgeInsets.all(8), crossAxisAlignment: CrossAxisAlignment.start,
child: Consumer<Settings>( children: [
builder: (context, settings, child) { Padding(
final filterList = settings.hiddenFilters.toList()..sort(); padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
return Wrap( child: Row(
spacing: 8, children: [
runSpacing: 8, Icon(AIcons.info),
children: filterList SizedBox(width: 16),
.map((filter) => AvesFilterChip( Expanded(child: Text('Photos and videos matching hidden filters will not appear in your collection.')),
filter: filter, ],
removable: true, ),
onTap: (filter) => context.read<CollectionSource>().changeFilterVisibility(filter, true), ),
)) Divider(),
.toList(), Expanded(
); child: Padding(
}, padding: EdgeInsets.all(8),
), child: Consumer<Settings>(
builder: (context, settings, child) {
final hiddenFilters = settings.hiddenFilters;
final filterList = hiddenFilters.toList()..sort();
if (hiddenFilters.isEmpty) {
return EmptyContent(
icon: AIcons.hide,
text: 'No hidden filters',
);
}
return Wrap(
spacing: 8,
runSpacing: 8,
children: filterList
.map((filter) => AvesFilterChip(
filter: filter,
removable: true,
onTap: (filter) => context.read<CollectionSource>().changeFilterVisibility(filter, true),
))
.toList(),
);
},
),
),
),
],
), ),
), ),
); );

View file

@ -242,11 +242,8 @@ class _SettingsPageState extends State<SettingsPage> {
onChanged: (v) => settings.isCrashlyticsEnabled = v, onChanged: (v) => settings.isCrashlyticsEnabled = v,
title: Text('Allow anonymous analytics and crash reporting'), title: Text('Allow anonymous analytics and crash reporting'),
), ),
HiddenFilters(), HiddenFilterTile(),
Padding( StorageAccessTile(),
padding: EdgeInsets.only(top: 8, bottom: 16),
child: GrantedDirectories(),
),
], ],
); );
} }