#136 hidden paths: select directory with custom picker instead of SAF one
This commit is contained in:
parent
b837c0a5b6
commit
db78210a37
13 changed files with 373 additions and 55 deletions
|
@ -151,8 +151,7 @@ class MainActivity : FlutterActivity() {
|
|||
DELETE_SINGLE_PERMISSION_REQUEST,
|
||||
MEDIA_WRITE_BULK_PERMISSION_REQUEST -> onScopedStoragePermissionResult(resultCode)
|
||||
CREATE_FILE_REQUEST,
|
||||
OPEN_FILE_REQUEST,
|
||||
SELECT_DIRECTORY_REQUEST -> onStorageAccessResult(requestCode, data?.data)
|
||||
OPEN_FILE_REQUEST -> onStorageAccessResult(requestCode, data?.data)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,9 +289,8 @@ class MainActivity : FlutterActivity() {
|
|||
const val OPEN_FROM_ANALYSIS_SERVICE = 2
|
||||
const val CREATE_FILE_REQUEST = 3
|
||||
const val OPEN_FILE_REQUEST = 4
|
||||
const val SELECT_DIRECTORY_REQUEST = 5
|
||||
const val DELETE_SINGLE_PERMISSION_REQUEST = 6
|
||||
const val MEDIA_WRITE_BULK_PERMISSION_REQUEST = 7
|
||||
const val DELETE_SINGLE_PERMISSION_REQUEST = 5
|
||||
const val MEDIA_WRITE_BULK_PERMISSION_REQUEST = 6
|
||||
|
||||
const val INTENT_DATA_KEY_ACTION = "action"
|
||||
const val INTENT_DATA_KEY_FILTERS = "filters"
|
||||
|
|
|
@ -11,7 +11,6 @@ import deckers.thibault.aves.MainActivity
|
|||
import deckers.thibault.aves.PendingStorageAccessResultHandler
|
||||
import deckers.thibault.aves.utils.LogUtils
|
||||
import deckers.thibault.aves.utils.PermissionManager
|
||||
import deckers.thibault.aves.utils.StorageUtils
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.flutter.plugin.common.EventChannel.EventSink
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -44,7 +43,6 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
|
|||
"requestMediaFileAccess" -> GlobalScope.launch(Dispatchers.IO) { requestMediaFileAccess() }
|
||||
"createFile" -> GlobalScope.launch(Dispatchers.IO) { createFile() }
|
||||
"openFile" -> GlobalScope.launch(Dispatchers.IO) { openFile() }
|
||||
"selectDirectory" -> GlobalScope.launch(Dispatchers.IO) { selectDirectory() }
|
||||
else -> endOfStream()
|
||||
}
|
||||
}
|
||||
|
@ -158,25 +156,6 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
|
|||
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.pendingStorageAccessResultHandlers[MainActivity.SELECT_DIRECTORY_REQUEST] = PendingStorageAccessResultHandler(null, { uri ->
|
||||
success(StorageUtils.convertTreeUriToDirPath(activity, uri))
|
||||
endOfStream()
|
||||
}, {
|
||||
success(null)
|
||||
endOfStream()
|
||||
})
|
||||
activity.startActivityForResult(intent, MainActivity.SELECT_DIRECTORY_REQUEST)
|
||||
} else {
|
||||
// TODO TLAD support KitKat
|
||||
success(null)
|
||||
endOfStream()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {}
|
||||
|
||||
private fun success(result: Any?) {
|
||||
|
|
|
@ -1064,5 +1064,16 @@
|
|||
"@panoramaDisableSensorControl": {},
|
||||
|
||||
"sourceViewerPageTitle": "Source",
|
||||
"@sourceViewerPageTitle": {}
|
||||
"@sourceViewerPageTitle": {},
|
||||
|
||||
"filePickerShowHiddenFiles": "Show hidden files",
|
||||
"@filePickerShowHiddenFiles": {},
|
||||
"filePickerDoNotShowHiddenFiles": "Don’t show hidden files",
|
||||
"@filePickerDoNotShowHiddenFiles": {},
|
||||
"filePickerOpenFrom": "Open from",
|
||||
"@filePickerOpenFrom": {},
|
||||
"filePickerNoItems": "No items",
|
||||
"@filePickerNoItems": {},
|
||||
"filePickerUseThisFolder": "Use this folder",
|
||||
"@filePickerUseThisFolder": {}
|
||||
}
|
||||
|
|
|
@ -513,5 +513,11 @@
|
|||
"panoramaDisableSensorControl": "Désactiver le contrôle par capteurs",
|
||||
|
||||
"sourceViewerPageTitle": "Code source",
|
||||
"@sourceViewerPageTitle": {}
|
||||
"@sourceViewerPageTitle": {},
|
||||
|
||||
"filePickerShowHiddenFiles": "Afficher les fichiers masqués",
|
||||
"filePickerDoNotShowHiddenFiles": "Ne pas afficher les fichiers masqués",
|
||||
"filePickerOpenFrom": "Ouvrir à partir de",
|
||||
"filePickerNoItems": "Aucun élément",
|
||||
"filePickerUseThisFolder": "Utiliser ce dossier"
|
||||
}
|
||||
|
|
|
@ -508,5 +508,11 @@
|
|||
"panoramaEnableSensorControl": "센서 제어 활성화",
|
||||
"panoramaDisableSensorControl": "센서 제어 비활성화",
|
||||
|
||||
"sourceViewerPageTitle": "소스 코드"
|
||||
"sourceViewerPageTitle": "소스 코드",
|
||||
|
||||
"filePickerShowHiddenFiles": "숨겨진 파일 표시",
|
||||
"filePickerDoNotShowHiddenFiles": "숨겨진 파일 표시 안함",
|
||||
"filePickerOpenFrom": "다음에서 열기:",
|
||||
"filePickerNoItems": "항목 없음",
|
||||
"filePickerUseThisFolder": "이 폴더 사용"
|
||||
}
|
||||
|
|
|
@ -503,5 +503,11 @@
|
|||
"panoramaEnableSensorControl": "Включить сенсорное управление",
|
||||
"panoramaDisableSensorControl": "Отключить сенсорное управление",
|
||||
|
||||
"sourceViewerPageTitle": "Источник"
|
||||
"sourceViewerPageTitle": "Источник",
|
||||
|
||||
"filePickerShowHiddenFiles": "Показывать скрытые файлы",
|
||||
"filePickerDoNotShowHiddenFiles": "Не показывать скрытые файлы",
|
||||
"filePickerOpenFrom": "Открыть",
|
||||
"filePickerNoItems": "Ничего нет.",
|
||||
"filePickerUseThisFolder": "Использовать эту папку"
|
||||
}
|
||||
|
|
|
@ -99,4 +99,7 @@ class SettingsDefaults {
|
|||
// accessibility
|
||||
static const accessibilityAnimations = AccessibilityAnimations.system;
|
||||
static const timeToTakeAction = AccessibilityTimeout.appDefault; // `timeToTakeAction` has a contextual default value
|
||||
|
||||
// file picker
|
||||
static const filePickerShowHiddenFiles = false;
|
||||
}
|
||||
|
|
|
@ -117,6 +117,9 @@ class Settings extends ChangeNotifier {
|
|||
// version
|
||||
static const lastVersionCheckDateKey = 'last_version_check_date';
|
||||
|
||||
// file picker
|
||||
static const filePickerShowHiddenFilesKey = 'file_picker_show_hidden_files';
|
||||
|
||||
// platform settings
|
||||
// cf Android `Settings.System.ACCELEROMETER_ROTATION`
|
||||
static const platformAccelerometerRotationKey = 'accelerometer_rotation';
|
||||
|
@ -451,6 +454,12 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
set lastVersionCheckDate(DateTime newValue) => setAndNotify(lastVersionCheckDateKey, newValue.millisecondsSinceEpoch);
|
||||
|
||||
// file picker
|
||||
|
||||
bool get filePickerShowHiddenFiles => getBoolOrDefault(filePickerShowHiddenFilesKey, SettingsDefaults.filePickerShowHiddenFiles);
|
||||
|
||||
set filePickerShowHiddenFiles(bool newValue) => setAndNotify(filePickerShowHiddenFilesKey, newValue);
|
||||
|
||||
// convenience methods
|
||||
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
|
@ -597,6 +606,7 @@ class Settings extends ChangeNotifier {
|
|||
case enableVideoAutoPlayKey:
|
||||
case subtitleShowOutlineKey:
|
||||
case saveSearchHistoryKey:
|
||||
case filePickerShowHiddenFilesKey:
|
||||
if (value is bool) {
|
||||
_prefs!.setBool(key, value);
|
||||
} else {
|
||||
|
|
|
@ -37,8 +37,6 @@ abstract class StorageService {
|
|||
Future<bool?> createFile(String name, String mimeType, Uint8List bytes);
|
||||
|
||||
Future<Uint8List> openFile(String mimeType);
|
||||
|
||||
Future<String?> selectDirectory();
|
||||
}
|
||||
|
||||
class PlatformStorageService implements StorageService {
|
||||
|
@ -255,25 +253,4 @@ class PlatformStorageService implements StorageService {
|
|||
}
|
||||
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, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,13 @@ class AIcons {
|
|||
static const IconData date = Icons.calendar_today_outlined;
|
||||
static const IconData disc = Icons.fiber_manual_record;
|
||||
static const IconData error = Icons.error_outline;
|
||||
static const IconData folder = Icons.folder_outlined;
|
||||
static const IconData grid = Icons.grid_on_outlined;
|
||||
static const IconData home = Icons.home_outlined;
|
||||
static const IconData language = Icons.translate_outlined;
|
||||
static const IconData location = Icons.place_outlined;
|
||||
static const IconData locationOff = Icons.location_off_outlined;
|
||||
static const IconData mainStorage = Icons.smartphone_outlined;
|
||||
static const IconData privacy = MdiIcons.shieldAccountOutline;
|
||||
static const IconData raw = Icons.raw_on_outlined;
|
||||
static const IconData shooting = Icons.camera_outlined;
|
||||
|
|
107
lib/widgets/settings/privacy/file_picker/crumb_line.dart
Normal file
107
lib/widgets/settings/privacy/file_picker/crumb_line.dart
Normal file
|
@ -0,0 +1,107 @@
|
|||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class CrumbLine extends StatefulWidget {
|
||||
final VolumeRelativeDirectory directory;
|
||||
final void Function(String path) onTap;
|
||||
|
||||
const CrumbLine({
|
||||
Key? key,
|
||||
required this.directory,
|
||||
required this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_CrumbLineState createState() => _CrumbLineState();
|
||||
}
|
||||
|
||||
class _CrumbLineState extends State<CrumbLine> {
|
||||
final ScrollController _controller = ScrollController();
|
||||
|
||||
VolumeRelativeDirectory get directory => widget.directory;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant CrumbLine oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.directory.relativeDir.length > oldWidget.directory.relativeDir.length) {
|
||||
// scroll to show last crumb
|
||||
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
||||
final extent = _controller.position.maxScrollExtent;
|
||||
_controller.animateTo(
|
||||
extent,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeOutQuad,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<String> parts = [
|
||||
directory.getVolumeDescription(context),
|
||||
...p.split(directory.relativeDir),
|
||||
];
|
||||
final crumbStyle = Theme.of(context).textTheme.bodyText2;
|
||||
final crumbColor = crumbStyle!.color!.withOpacity(.4);
|
||||
return DefaultTextStyle(
|
||||
style: crumbStyle.copyWith(
|
||||
color: crumbColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: _controller,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
itemBuilder: (context, index) {
|
||||
Widget _buildText(String text) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text(text),
|
||||
);
|
||||
|
||||
if (index >= parts.length) return const SizedBox();
|
||||
final text = parts[index];
|
||||
if (index == parts.length - 1) {
|
||||
return Center(
|
||||
child: DefaultTextStyle.merge(
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
child: _buildText(text),
|
||||
),
|
||||
);
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
final path = p.joinAll([
|
||||
directory.volumePath,
|
||||
...parts.skip(1).take(index),
|
||||
]);
|
||||
widget.onTap(path);
|
||||
},
|
||||
child: Container(
|
||||
// use a `Container` with a dummy color to make it expand
|
||||
// so that we can also detect taps around the title `Text`
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_buildText(text),
|
||||
Icon(
|
||||
AIcons.next,
|
||||
color: crumbColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: parts.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
203
lib/widgets/settings/privacy/file_picker/file_picker.dart
Normal file
203
lib/widgets/settings/privacy/file_picker/file_picker.dart
Normal file
|
@ -0,0 +1,203 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/basic/menu.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/buttons.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/settings/privacy/file_picker/crumb_line.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class FilePicker extends StatefulWidget {
|
||||
static const routeName = '/file_picker';
|
||||
|
||||
const FilePicker({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_FilePickerState createState() => _FilePickerState();
|
||||
}
|
||||
|
||||
class _FilePickerState extends State<FilePicker> {
|
||||
late VolumeRelativeDirectory _directory;
|
||||
List<Directory>? _contents;
|
||||
|
||||
Set<StorageVolume> get volumes => androidFileUtils.storageVolumes;
|
||||
|
||||
String get currentDirectoryPath => p.join(_directory.volumePath, _directory.relativeDir);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final primaryVolume = volumes.firstWhere((v) => v.isPrimary);
|
||||
_goTo(primaryVolume.path);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final showHidden = settings.filePickerShowHiddenFiles;
|
||||
final visibleContents = _contents?.where((v) {
|
||||
if (showHidden) {
|
||||
return true;
|
||||
} else {
|
||||
final isHidden = p.split(v.path).last.startsWith('.');
|
||||
return !isHidden;
|
||||
}
|
||||
}).toList();
|
||||
return WillPopScope(
|
||||
onWillPop: () {
|
||||
if (_directory.relativeDir.isEmpty) {
|
||||
return SynchronousFuture(true);
|
||||
}
|
||||
final parent = p.dirname(currentDirectoryPath);
|
||||
_goTo(parent);
|
||||
setState(() {});
|
||||
return SynchronousFuture(false);
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(_getTitle(context)),
|
||||
actions: [
|
||||
MenuIconTheme(
|
||||
child: PopupMenuButton<_PickerAction>(
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
value: _PickerAction.toggleHiddenView,
|
||||
child: MenuRow(text: showHidden ? l10n.filePickerDoNotShowHiddenFiles : l10n.filePickerShowHiddenFiles),
|
||||
),
|
||||
];
|
||||
},
|
||||
onSelected: (action) async {
|
||||
// wait for the popup menu to hide before proceeding with the action
|
||||
await Future.delayed(Durations.popupMenuAnimation * timeDilation);
|
||||
switch (action) {
|
||||
case _PickerAction.toggleHiddenView:
|
||||
settings.filePickerShowHiddenFiles = !showHidden;
|
||||
setState(() {});
|
||||
break;
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
drawer: _buildDrawer(context),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: kMinInteractiveDimension,
|
||||
child: CrumbLine(
|
||||
directory: _directory,
|
||||
onTap: (path) {
|
||||
_goTo(path);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(height: 0),
|
||||
Expanded(
|
||||
child: visibleContents == null
|
||||
? const SizedBox()
|
||||
: visibleContents.isEmpty
|
||||
? Center(
|
||||
child: EmptyContent(
|
||||
icon: AIcons.folder,
|
||||
text: l10n.filePickerNoItems,
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: visibleContents.length,
|
||||
itemBuilder: (context, index) {
|
||||
return index < visibleContents.length ? _buildContentLine(context, visibleContents[index]) : const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(height: 0),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: AvesOutlinedButton(
|
||||
label: l10n.filePickerUseThisFolder,
|
||||
onPressed: () => Navigator.pop(context, currentDirectoryPath),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getTitle(BuildContext context) {
|
||||
if (_directory.relativeDir.isEmpty) {
|
||||
return _directory.getVolumeDescription(context);
|
||||
}
|
||||
return p.split(_directory.relativeDir).last;
|
||||
}
|
||||
|
||||
Widget _buildDrawer(BuildContext context) {
|
||||
return Drawer(
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
context.l10n.filePickerOpenFrom,
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
...volumes.map((v) {
|
||||
final icon = v.isRemovable ? AIcons.removableStorage : AIcons.mainStorage;
|
||||
return ListTile(
|
||||
leading: Icon(icon),
|
||||
title: Text(v.getDescription(context)),
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
await Future.delayed(Durations.drawerTransitionAnimation);
|
||||
_goTo(v.path);
|
||||
setState(() {});
|
||||
},
|
||||
selected: _directory.volumePath == v.path,
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContentLine(BuildContext context, FileSystemEntity content) {
|
||||
return ListTile(
|
||||
leading: const Icon(AIcons.folder),
|
||||
title: Text(p.split(content.path).last),
|
||||
onTap: () {
|
||||
_goTo(content.path);
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _goTo(String path) {
|
||||
_directory = VolumeRelativeDirectory.fromPath(path)!;
|
||||
_contents = null;
|
||||
final contents = <Directory>[];
|
||||
Directory(currentDirectoryPath).list().listen((event) {
|
||||
final entity = event.absolute;
|
||||
if (entity is Directory) {
|
||||
contents.add(entity);
|
||||
}
|
||||
}, onDone: () {
|
||||
_contents = contents..sort((a, b) => compareAsciiUpperCase(p.split(a.path).last, p.split(b.path).last));
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum _PickerAction { toggleHiddenView }
|
|
@ -2,14 +2,16 @@ 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/source/collection_source.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
import 'package:aves/widgets/common/identity/buttons.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/settings/privacy/file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
|
@ -164,7 +166,15 @@ class _HiddenPaths extends StatelessWidget {
|
|||
icon: const Icon(AIcons.add),
|
||||
label: context.l10n.addPathTooltip,
|
||||
onPressed: () async {
|
||||
final path = await storageService.selectDirectory();
|
||||
final path = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute<String>(
|
||||
settings: const RouteSettings(name: FilePicker.routeName),
|
||||
builder: (context) => const FilePicker(),
|
||||
),
|
||||
);
|
||||
// wait for the dialog to hide as applying the change may block the UI
|
||||
await Future.delayed(Durations.pageTransitionAnimation * timeDilation);
|
||||
if (path != null && path.isNotEmpty) {
|
||||
context.read<CollectionSource>().changeFilterVisibility({PathFilter(path)}, false);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue