#52 hidden paths
This commit is contained in:
parent
97e3063998
commit
1f7e70697e
12 changed files with 242 additions and 6 deletions
|
@ -114,7 +114,7 @@ class MainActivity : FlutterActivity() {
|
||||||
MediaStoreImageProvider.pendingDeleteCompleter?.complete(resultCode == RESULT_OK)
|
MediaStoreImageProvider.pendingDeleteCompleter?.complete(resultCode == RESULT_OK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CREATE_FILE_REQUEST, OPEN_FILE_REQUEST -> {
|
CREATE_FILE_REQUEST, OPEN_FILE_REQUEST, SELECT_DIRECTORY_REQUEST -> {
|
||||||
onPermissionResult(requestCode, data?.data)
|
onPermissionResult(requestCode, data?.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,6 +196,7 @@ class MainActivity : FlutterActivity() {
|
||||||
const val DELETE_PERMISSION_REQUEST = 2
|
const val DELETE_PERMISSION_REQUEST = 2
|
||||||
const val CREATE_FILE_REQUEST = 3
|
const val CREATE_FILE_REQUEST = 3
|
||||||
const val OPEN_FILE_REQUEST = 4
|
const val OPEN_FILE_REQUEST = 4
|
||||||
|
const val SELECT_DIRECTORY_REQUEST = 5
|
||||||
|
|
||||||
// permission request code to pending runnable
|
// permission request code to pending runnable
|
||||||
val pendingResultHandlers = ConcurrentHashMap<Int, PendingResultHandler>()
|
val pendingResultHandlers = ConcurrentHashMap<Int, PendingResultHandler>()
|
||||||
|
|
|
@ -10,6 +10,7 @@ import deckers.thibault.aves.MainActivity
|
||||||
import deckers.thibault.aves.PendingResultHandler
|
import deckers.thibault.aves.PendingResultHandler
|
||||||
import deckers.thibault.aves.utils.LogUtils
|
import deckers.thibault.aves.utils.LogUtils
|
||||||
import deckers.thibault.aves.utils.PermissionManager
|
import deckers.thibault.aves.utils.PermissionManager
|
||||||
|
import deckers.thibault.aves.utils.StorageUtils
|
||||||
import io.flutter.plugin.common.EventChannel
|
import io.flutter.plugin.common.EventChannel
|
||||||
import io.flutter.plugin.common.EventChannel.EventSink
|
import io.flutter.plugin.common.EventChannel.EventSink
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -41,6 +42,7 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
|
||||||
"requestVolumeAccess" -> requestVolumeAccess()
|
"requestVolumeAccess" -> requestVolumeAccess()
|
||||||
"createFile" -> GlobalScope.launch(Dispatchers.IO) { createFile() }
|
"createFile" -> GlobalScope.launch(Dispatchers.IO) { createFile() }
|
||||||
"openFile" -> GlobalScope.launch(Dispatchers.IO) { openFile() }
|
"openFile" -> GlobalScope.launch(Dispatchers.IO) { openFile() }
|
||||||
|
"selectDirectory" -> GlobalScope.launch(Dispatchers.IO) { selectDirectory() }
|
||||||
else -> endOfStream()
|
else -> endOfStream()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,6 +130,24 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
|
||||||
activity.startActivityForResult(intent, MainActivity.OPEN_FILE_REQUEST)
|
activity.startActivityForResult(intent, MainActivity.OPEN_FILE_REQUEST)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun selectDirectory() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||||
|
|
||||||
|
MainActivity.pendingResultHandlers[MainActivity.SELECT_DIRECTORY_REQUEST] = PendingResultHandler(null, { uri ->
|
||||||
|
success(StorageUtils.convertTreeUriToDirPath(activity, uri))
|
||||||
|
endOfStream()
|
||||||
|
}, {
|
||||||
|
success(null)
|
||||||
|
endOfStream()
|
||||||
|
})
|
||||||
|
activity.startActivityForResult(intent, MainActivity.SELECT_DIRECTORY_REQUEST)
|
||||||
|
} else {
|
||||||
|
success(null)
|
||||||
|
endOfStream()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCancel(arguments: Any?) {}
|
override fun onCancel(arguments: Any?) {}
|
||||||
|
|
||||||
private fun success(result: Any?) {
|
private fun success(result: Any?) {
|
||||||
|
|
|
@ -659,6 +659,19 @@
|
||||||
"settingsHiddenFiltersEmpty": "No hidden filters",
|
"settingsHiddenFiltersEmpty": "No hidden filters",
|
||||||
"@settingsHiddenFiltersEmpty": {},
|
"@settingsHiddenFiltersEmpty": {},
|
||||||
|
|
||||||
|
"settingsHiddenPathsTile": "Hidden paths",
|
||||||
|
"@settingsHiddenPathsTile": {},
|
||||||
|
"settingsHiddenPathsTitle": "Hidden Paths",
|
||||||
|
"@settingsHiddenPathsTitle": {},
|
||||||
|
"settingsHiddenPathsBanner": "Photos and videos in these folders, or any of their subfolders, will not appear in your collection.",
|
||||||
|
"@settingsHiddenPathsBanner": {},
|
||||||
|
"settingsHiddenPathsEmpty": "No hidden paths",
|
||||||
|
"@settingsHiddenPathsEmpty": {},
|
||||||
|
"settingsHiddenPathsRemoveTooltip": "Remove",
|
||||||
|
"@settingsHiddenPathsRemoveTooltip": {},
|
||||||
|
"addPathTooltip": "Add path",
|
||||||
|
"@addPathTooltip": {},
|
||||||
|
|
||||||
"settingsStorageAccessTile": "Storage access",
|
"settingsStorageAccessTile": "Storage access",
|
||||||
"@settingsStorageAccessTile": {},
|
"@settingsStorageAccessTile": {},
|
||||||
"settingsStorageAccessTitle": "Storage Access",
|
"settingsStorageAccessTitle": "Storage Access",
|
||||||
|
|
|
@ -313,6 +313,13 @@
|
||||||
"settingsHiddenFiltersBanner": "이 필터에 맞는 사진과 동영상이 숨겨지고 있으며 이 앱에서 보여지지 않을 것입니다.",
|
"settingsHiddenFiltersBanner": "이 필터에 맞는 사진과 동영상이 숨겨지고 있으며 이 앱에서 보여지지 않을 것입니다.",
|
||||||
"settingsHiddenFiltersEmpty": "숨겨진 필터가 없습니다",
|
"settingsHiddenFiltersEmpty": "숨겨진 필터가 없습니다",
|
||||||
|
|
||||||
|
"settingsHiddenPathsTile": "숨겨진 경로",
|
||||||
|
"settingsHiddenPathsTitle": "숨겨진 경로",
|
||||||
|
"settingsHiddenPathsBanner": "이 경로에 있는 사진과 동영상이 숨겨지고 있으며 이 앱에서 보여지지 않을 것입니다.",
|
||||||
|
"settingsHiddenPathsEmpty": "숨겨진 경로가 없습니다",
|
||||||
|
"settingsHiddenPathsRemoveTooltip": "제거",
|
||||||
|
"addPathTooltip": "경로 추가",
|
||||||
|
|
||||||
"settingsStorageAccessTile": "저장공간 접근",
|
"settingsStorageAccessTile": "저장공간 접근",
|
||||||
"settingsStorageAccessTitle": "저장공간 접근",
|
"settingsStorageAccessTitle": "저장공간 접근",
|
||||||
"settingsStorageAccessBanner": "어떤 폴더는 사용자의 허용을 받아야만 앱이 파일에 접근이 가능합니다. 이 화면에 허용을 받은 폴더를 확인할 수 있으며 원하지 않으면 취소할 수 있습니다.",
|
"settingsStorageAccessBanner": "어떤 폴더는 사용자의 허용을 받아야만 앱이 파일에 접근이 가능합니다. 이 화면에 허용을 받은 폴더를 확인할 수 있으며 원하지 않으면 취소할 수 있습니다.",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:aves/model/filters/album.dart';
|
||||||
import 'package:aves/model/filters/favourite.dart';
|
import 'package:aves/model/filters/favourite.dart';
|
||||||
import 'package:aves/model/filters/location.dart';
|
import 'package:aves/model/filters/location.dart';
|
||||||
import 'package:aves/model/filters/mime.dart';
|
import 'package:aves/model/filters/mime.dart';
|
||||||
|
import 'package:aves/model/filters/path.dart';
|
||||||
import 'package:aves/model/filters/query.dart';
|
import 'package:aves/model/filters/query.dart';
|
||||||
import 'package:aves/model/filters/tag.dart';
|
import 'package:aves/model/filters/tag.dart';
|
||||||
import 'package:aves/model/filters/type.dart';
|
import 'package:aves/model/filters/type.dart';
|
||||||
|
@ -22,6 +23,7 @@ abstract class CollectionFilter implements Comparable<CollectionFilter> {
|
||||||
AlbumFilter.type,
|
AlbumFilter.type,
|
||||||
LocationFilter.type,
|
LocationFilter.type,
|
||||||
TagFilter.type,
|
TagFilter.type,
|
||||||
|
PathFilter.type,
|
||||||
];
|
];
|
||||||
|
|
||||||
static CollectionFilter? fromJson(String jsonString) {
|
static CollectionFilter? fromJson(String jsonString) {
|
||||||
|
@ -43,6 +45,8 @@ abstract class CollectionFilter implements Comparable<CollectionFilter> {
|
||||||
return QueryFilter.fromMap(jsonMap);
|
return QueryFilter.fromMap(jsonMap);
|
||||||
case TagFilter.type:
|
case TagFilter.type:
|
||||||
return TagFilter.fromMap(jsonMap);
|
return TagFilter.fromMap(jsonMap);
|
||||||
|
case PathFilter.type:
|
||||||
|
return PathFilter.fromMap(jsonMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debugPrint('failed to parse filter from json=$jsonString');
|
debugPrint('failed to parse filter from json=$jsonString');
|
||||||
|
@ -65,7 +69,7 @@ abstract class CollectionFilter implements Comparable<CollectionFilter> {
|
||||||
|
|
||||||
String getTooltip(BuildContext context) => getLabel(context);
|
String getTooltip(BuildContext context) => getLabel(context);
|
||||||
|
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false});
|
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false}) => null;
|
||||||
|
|
||||||
Future<Color> color(BuildContext context) => SynchronousFuture(stringToColor(getLabel(context)));
|
Future<Color> color(BuildContext context) => SynchronousFuture(stringToColor(getLabel(context)));
|
||||||
|
|
||||||
|
|
46
lib/model/filters/path.dart
Normal file
46
lib/model/filters/path.dart
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class PathFilter extends CollectionFilter {
|
||||||
|
static const type = 'path';
|
||||||
|
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
const PathFilter(this.path);
|
||||||
|
|
||||||
|
PathFilter.fromMap(Map<String, dynamic> json)
|
||||||
|
: this(
|
||||||
|
json['path'],
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
'type': type,
|
||||||
|
'path': path,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
EntryFilter get test => (entry) => entry.directory?.startsWith(path) ?? false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get universalLabel => path;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get category => type;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get key => '$type-$path';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other.runtimeType != runtimeType) return false;
|
||||||
|
return other is PathFilter && other.path == path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => hashValues(type, path);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '$runtimeType#${shortHash(this)}{path=$path}';
|
||||||
|
}
|
|
@ -33,6 +33,8 @@ abstract class StorageService {
|
||||||
Future<bool?> createFile(String name, String mimeType, Uint8List bytes);
|
Future<bool?> createFile(String name, String mimeType, Uint8List bytes);
|
||||||
|
|
||||||
Future<Uint8List> openFile(String mimeType);
|
Future<Uint8List> openFile(String mimeType);
|
||||||
|
|
||||||
|
Future<String?> selectDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformStorageService implements StorageService {
|
class PlatformStorageService implements StorageService {
|
||||||
|
@ -217,4 +219,25 @@ class PlatformStorageService implements StorageService {
|
||||||
}
|
}
|
||||||
return Uint8List(0);
|
return Uint8List(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> selectDirectory() async {
|
||||||
|
try {
|
||||||
|
final completer = Completer<String>();
|
||||||
|
storageAccessChannel.receiveBroadcastStream(<String, dynamic>{
|
||||||
|
'op': 'selectDirectory',
|
||||||
|
}).listen(
|
||||||
|
(data) => completer.complete(data as String?),
|
||||||
|
onError: completer.completeError,
|
||||||
|
onDone: () {
|
||||||
|
if (!completer.isCompleted) completer.complete(null);
|
||||||
|
},
|
||||||
|
cancelOnError: true,
|
||||||
|
);
|
||||||
|
return completer.future;
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint('selectDirectory failed with code=${e.code}, exception=${e.message}, details=${e.details}}');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ class AIcons {
|
||||||
static const IconData tagOff = MdiIcons.tagOffOutline;
|
static const IconData tagOff = MdiIcons.tagOffOutline;
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
|
static const IconData addPath = Icons.add_circle_outline;
|
||||||
static const IconData addShortcut = Icons.add_to_home_screen_outlined;
|
static const IconData addShortcut = Icons.add_to_home_screen_outlined;
|
||||||
static const IconData replay10 = Icons.replay_10_outlined;
|
static const IconData replay10 = Icons.replay_10_outlined;
|
||||||
static const IconData skip10 = Icons.forward_10_outlined;
|
static const IconData skip10 = Icons.forward_10_outlined;
|
||||||
|
|
|
@ -77,7 +77,7 @@ class SectionHeader<T> extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _toggleSectionSelection(BuildContext context) {
|
void _toggleSectionSelection(BuildContext context) {
|
||||||
final sectionEntries = context.read<SectionedListLayout<T>>().sections[sectionKey]!;
|
final sectionEntries = context.read<SectionedListLayout<T>>().sections[sectionKey] ?? [];
|
||||||
final selection = context.read<Selection<T>>();
|
final selection = context.read<Selection<T>>();
|
||||||
final isSelected = selection.isSelected(sectionEntries);
|
final isSelected = selection.isSelected(sectionEntries);
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
|
@ -189,7 +189,7 @@ class _SectionSelectingLeading<T> extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sectionEntries = context.watch<SectionedListLayout<T>>().sections[sectionKey]!;
|
final sectionEntries = context.watch<SectionedListLayout<T>>().sections[sectionKey] ?? [];
|
||||||
final selection = context.watch<Selection<T>>();
|
final selection = context.watch<Selection<T>>();
|
||||||
final isSelected = selection.isSelected(sectionEntries);
|
final isSelected = selection.isSelected(sectionEntries);
|
||||||
return AnimatedSwitcher(
|
return AnimatedSwitcher(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:aves/model/filters/path.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/theme/icons.dart';
|
||||||
|
@ -29,7 +30,7 @@ class HiddenFilterTile extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class HiddenFilterPage extends StatelessWidget {
|
class HiddenFilterPage extends StatelessWidget {
|
||||||
static const routeName = '/settings/hidden';
|
static const routeName = '/settings/hidden_filters';
|
||||||
|
|
||||||
const HiddenFilterPage({Key? key}) : super(key: key);
|
const HiddenFilterPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ class HiddenFilterPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Selector<Settings, Set<CollectionFilter>>(
|
child: Selector<Settings, Set<CollectionFilter>>(
|
||||||
selector: (context, s) => settings.hiddenFilters,
|
selector: (context, s) => settings.hiddenFilters.where((v) => v is! PathFilter).toSet(),
|
||||||
builder: (context, hiddenFilters, child) {
|
builder: (context, hiddenFilters, child) {
|
||||||
if (hiddenFilters.isEmpty) {
|
if (hiddenFilters.isEmpty) {
|
||||||
return Column(
|
return Column(
|
||||||
|
|
118
lib/widgets/settings/privacy/hidden_paths.dart
Normal file
118
lib/widgets/settings/privacy/hidden_paths.dart
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import 'package:aves/model/filters/path.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/services/services.dart';
|
||||||
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:aves/widgets/common/identity/empty.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class HiddenPathTile extends StatelessWidget {
|
||||||
|
const HiddenPathTile({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(context.l10n.settingsHiddenPathsTile),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
settings: const RouteSettings(name: HiddenPathPage.routeName),
|
||||||
|
builder: (context) => const HiddenPathPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HiddenPathPage extends StatelessWidget {
|
||||||
|
static const routeName = '/settings/hidden_paths';
|
||||||
|
|
||||||
|
const HiddenPathPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(context.l10n.settingsHiddenPathsTitle),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(AIcons.addPath),
|
||||||
|
onPressed: () async {
|
||||||
|
final path = await storageService.selectDirectory();
|
||||||
|
if (path != null && path.isNotEmpty) {
|
||||||
|
context.read<CollectionSource>().changeFilterVisibility({PathFilter(path)}, false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: context.l10n.addPathTooltip,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Selector<Settings, Set<PathFilter>>(
|
||||||
|
selector: (context, s) => settings.hiddenFilters.whereType<PathFilter>().toSet(),
|
||||||
|
builder: (context, hiddenPaths, child) {
|
||||||
|
if (hiddenPaths.isEmpty) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const _Header(),
|
||||||
|
const Divider(),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: EmptyContent(
|
||||||
|
icon: AIcons.hide,
|
||||||
|
text: context.l10n.settingsHiddenPathsEmpty,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final pathList = hiddenPaths.toList()..sort();
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
const _Header(),
|
||||||
|
const Divider(),
|
||||||
|
...pathList.map((pathFilter) => ListTile(
|
||||||
|
title: Text(pathFilter.path),
|
||||||
|
dense: true,
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(AIcons.clear),
|
||||||
|
onPressed: () {
|
||||||
|
context.read<CollectionSource>().changeFilterVisibility({pathFilter}, true);
|
||||||
|
},
|
||||||
|
tooltip: context.l10n.settingsHiddenPathsRemoveTooltip,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Header extends StatelessWidget {
|
||||||
|
const _Header({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(AIcons.info),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(child: Text(context.l10n.settingsHiddenPathsBanner)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||||
import 'package:aves/widgets/settings/common/tile_leading.dart';
|
import 'package:aves/widgets/settings/common/tile_leading.dart';
|
||||||
import 'package:aves/widgets/settings/privacy/access_grants.dart';
|
import 'package:aves/widgets/settings/privacy/access_grants.dart';
|
||||||
import 'package:aves/widgets/settings/privacy/hidden_filters.dart';
|
import 'package:aves/widgets/settings/privacy/hidden_filters.dart';
|
||||||
|
import 'package:aves/widgets/settings/privacy/hidden_paths.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ class PrivacySection extends StatelessWidget {
|
||||||
title: Text(context.l10n.settingsSaveSearchHistory),
|
title: Text(context.l10n.settingsSaveSearchHistory),
|
||||||
),
|
),
|
||||||
const HiddenFilterTile(),
|
const HiddenFilterTile(),
|
||||||
|
const HiddenPathTile(),
|
||||||
const StorageAccessTile(),
|
const StorageAccessTile(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue