diff --git a/lib/widget/album_dir_picker.dart b/lib/widget/album_dir_picker.dart index 27d767e3..9eff78c2 100644 --- a/lib/widget/album_dir_picker.dart +++ b/lib/widget/album_dir_picker.dart @@ -10,7 +10,7 @@ import 'package:nc_photos/iterable_extension.dart'; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/theme.dart'; -import 'package:nc_photos/widget/dir_picker_mixin.dart'; +import 'package:nc_photos/widget/dir_picker.dart'; class AlbumDirPickerArguments { AlbumDirPickerArguments(this.account); @@ -43,8 +43,7 @@ class AlbumDirPicker extends StatefulWidget { final Account account; } -class _AlbumDirPickerState extends State - with DirPickerMixin { +class _AlbumDirPickerState extends State { @override build(BuildContext context) { return AppTheme( @@ -54,30 +53,6 @@ class _AlbumDirPickerState extends State ); } - @override - getPickerRoot() { - final root = api_util.getWebdavRootUrlRelative(widget.account); - if (widget.account.roots.length == 1 && - widget.account.roots.first.isNotEmpty) { - return "$root/${widget.account.roots.first}"; - } else { - return root; - } - } - - @override - getAccount() => widget.account; - - @override - canPickDir(File dir) { - if (widget.account.roots.contains("")) { - return true; - } - final root = api_util.getWebdavRootUrlRelative(widget.account); - return widget.account.roots - .any((r) => dir.path == "$root/$r" || dir.path.startsWith("$root/$r/")); - } - Widget _buildContent(BuildContext context) { return SafeArea( child: Column( @@ -102,7 +77,20 @@ class _AlbumDirPickerState extends State ), ), Expanded( - child: buildDirPicker(context), + child: DirPicker( + key: _pickerKey, + account: widget.account, + rootDir: _rootDir, + validator: (dir) { + if (widget.account.roots.contains("")) { + return true; + } + final root = api_util.getWebdavRootUrlRelative(widget.account); + return widget.account.roots.any((r) => + dir.path == "$root/$r" || dir.path.startsWith("$root/$r/")); + }, + onConfirmed: (picks) => _onPickerConfirmed(context, picks), + ), ), Padding( padding: const EdgeInsets.all(16), @@ -117,7 +105,7 @@ class _AlbumDirPickerState extends State Text(MaterialLocalizations.of(context).cancelButtonLabel), ), ElevatedButton( - onPressed: () => _onConfirmPressed(context), + onPressed: _onConfirmPressed, child: Text(L10n.global().confirmButtonLabel), ), ], @@ -128,19 +116,35 @@ class _AlbumDirPickerState extends State ); } - void _onConfirmPressed(BuildContext context) { - final picked = getPickedDirs(); - if (picked.isEmpty) { + void _onConfirmPressed() { + _pickerKey.currentState?.confirm(); + } + + void _onPickerConfirmed(BuildContext context, List picks) { + if (picks.isEmpty) { SnackBarManager().showSnackBar(SnackBar( content: Text(L10n.global().albumDirPickerListEmptyNotification), duration: k.snackBarDurationNormal, )); } else { _log.info( - "[_onConfirmPressed] Picked: ${picked.map((e) => e.strippedPath).toReadableString()}"); - Navigator.of(context).pop(picked); + "[_onPickerConfirmed] Picked: ${picks.map((e) => e.strippedPath).toReadableString()}"); + Navigator.of(context).pop(picks); } } + String _getPickerRoot() { + final root = api_util.getWebdavRootUrlRelative(widget.account); + if (widget.account.roots.length == 1 && + widget.account.roots.first.isNotEmpty) { + return "$root/${widget.account.roots.first}"; + } else { + return root; + } + } + + final _pickerKey = GlobalKey(); + late final _rootDir = _getPickerRoot(); + static final _log = Logger("widget.album_dir_picker._AlbumDirPickerState"); } diff --git a/lib/widget/dir_picker_mixin.dart b/lib/widget/dir_picker.dart similarity index 89% rename from lib/widget/dir_picker_mixin.dart rename to lib/widget/dir_picker.dart index b4ab62dc..d2a99d24 100644 --- a/lib/widget/dir_picker_mixin.dart +++ b/lib/widget/dir_picker.dart @@ -1,5 +1,3 @@ -import 'dart:collection'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; @@ -14,16 +12,41 @@ import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:path/path.dart' as path; -mixin DirPickerMixin on State { +class DirPicker extends StatefulWidget { + const DirPicker({ + Key? key, + required this.account, + required this.rootDir, + this.initialPicks, + this.validator, + this.onConfirmed, + }) : super(key: key); + + @override + createState() => DirPickerState(); + + final Account account; + final String rootDir; + final List? initialPicks; + + /// Return whether [dir] is a valid target to be picked + final bool Function(File dir)? validator; + final ValueChanged>? onConfirmed; +} + +class DirPickerState extends State { @override initState() { super.initState(); - _root = LsDirBlocItem(File(path: getPickerRoot()), []); + _root = LsDirBlocItem(File(path: widget.rootDir), []); _initBloc(); + if (widget.initialPicks != null) { + _picks.addAll(widget.initialPicks!); + } } - @protected - Widget buildDirPicker(BuildContext context) { + @override + build(BuildContext context) { return BlocListener( bloc: _bloc, listener: (context, state) => _onStateChange(context, state), @@ -34,27 +57,14 @@ mixin DirPickerMixin on State { ); } - @protected - String getPickerRoot(); - - @protected - Account getAccount(); - - @protected - List getPickedDirs() => UnmodifiableListView(_picks); - - @protected - bool canPickDir(File file) => true; - - @protected - void pickAll(List dirs) { - _picks.addAll(dirs); + /// Calls the onConfirmed method with the current picked dirs + void confirm() { + widget.onConfirmed?.call(_picks); } void _initBloc() { _log.info("[_initBloc] Initialize bloc"); - _bloc = LsDirBloc(); - _navigateInto(File(path: getPickerRoot())); + _navigateInto(File(path: widget.rootDir)); } Widget _buildContent(BuildContext context, LsDirBlocState state) { @@ -76,7 +86,7 @@ mixin DirPickerMixin on State { } Widget _buildList(BuildContext context, LsDirBlocState state) { - final isTopLevel = _currentPath == getPickerRoot(); + final isTopLevel = _currentPath == widget.rootDir; return AnimatedSwitcher( duration: k.animationDurationNormal, // see AnimatedSwitcher.defaultLayoutBuilder @@ -118,7 +128,7 @@ mixin DirPickerMixin on State { } Widget _buildItem(BuildContext context, LsDirBlocItem item) { - final canPick = canPickDir(item.file); + final canPick = widget.validator?.call(item.file) != false; final pickState = _isItemPicked(item); IconData? iconData; @@ -359,17 +369,16 @@ mixin DirPickerMixin on State { void _navigateInto(File file) { _currentPath = file.path; - _bloc.add(LsDirBlocQuery(getAccount(), file, depth: 2)); + _bloc.add(LsDirBlocQuery(widget.account, file, depth: 2)); } - late LsDirBloc _bloc; + final _bloc = LsDirBloc(); late LsDirBlocItem _root; - /// Track where the user is navigating in [_backingFiles] - late String _currentPath; + var _currentPath = ""; var _picks = []; - static final _log = Logger("widget.dir_picker_mixin.DirPickerMixin"); + static final _log = Logger("widget.dir_picker.DirPickerState"); } enum _PickState { diff --git a/lib/widget/root_picker.dart b/lib/widget/root_picker.dart index 62c614a1..9a47beed 100644 --- a/lib/widget/root_picker.dart +++ b/lib/widget/root_picker.dart @@ -13,7 +13,7 @@ import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/use_case/ls_single_file.dart'; -import 'package:nc_photos/widget/dir_picker_mixin.dart'; +import 'package:nc_photos/widget/dir_picker.dart'; import 'package:nc_photos/widget/processing_dialog.dart'; class RootPickerArguments { @@ -47,8 +47,7 @@ class RootPicker extends StatefulWidget { final Account account; } -class _RootPickerState extends State - with DirPickerMixin { +class _RootPickerState extends State { @override initState() { super.initState(); @@ -61,15 +60,13 @@ class _RootPickerState extends State final files = []; for (final r in widget.account.roots) { if (r.isNotEmpty) { - _isIniting = true; _ensureInitDialog(); files.add(await LsSingleFile(fileSrc)(widget.account, "${api_util.getWebdavRootUrlRelative(widget.account)}/$r")); } } setState(() { - _isIniting = false; - pickAll(files); + _initialPicks = files; }); } catch (e) { SnackBarManager().showSnackBar(SnackBar( @@ -90,12 +87,6 @@ class _RootPickerState extends State ); } - @override - getPickerRoot() => api_util.getWebdavRootUrlRelative(widget.account); - - @override - getAccount() => widget.account; - Widget _buildContent(BuildContext context) { return SafeArea( child: Column( @@ -120,10 +111,15 @@ class _RootPickerState extends State ), ), Expanded( - child: IgnorePointer( - ignoring: _isIniting, - child: buildDirPicker(context), - ), + child: _initialPicks == null + ? Container() + : DirPicker( + key: _pickerKey, + account: widget.account, + rootDir: api_util.getWebdavRootUrlRelative(widget.account), + initialPicks: _initialPicks, + onConfirmed: (picks) => _onPickerConfirmed(context, picks), + ), ), Padding( padding: const EdgeInsets.all(16), @@ -135,7 +131,7 @@ class _RootPickerState extends State child: Text(L10n.global().skipButtonLabel), ), ElevatedButton( - onPressed: () => _onConfirmPressed(context), + onPressed: _onConfirmPressed, child: Text(L10n.global().confirmButtonLabel), ), ], @@ -174,8 +170,12 @@ class _RootPickerState extends State }); } - void _onConfirmPressed(BuildContext context) { - final roots = getPickedDirs().map((e) => e.strippedPath).toList(); + void _onConfirmPressed() { + _pickerKey.currentState?.confirm(); + } + + void _onPickerConfirmed(BuildContext context, List picks) { + final roots = picks.map((e) => e.strippedPath).toList(); if (roots.isEmpty) { SnackBarManager().showSnackBar(SnackBar( content: Text(L10n.global().rootPickerListEmptyNotification), @@ -184,7 +184,7 @@ class _RootPickerState extends State return; } final newAccount = widget.account.copyWith(roots: roots); - _log.info("[_onConfirmPressed] Account is good: $newAccount"); + _log.info("[_onPickerConfirmed] Account is good: $newAccount"); Navigator.of(context).pop(newAccount); } @@ -210,7 +210,9 @@ class _RootPickerState extends State Navigator.of(context).pop(); } - bool _isIniting = false; + final _pickerKey = GlobalKey(); + List? _initialPicks; + bool _isInitDialogShown = false; static final _log = Logger("widget.root_picker._RootPickerState");