Refactoring: convert dir picker to widget

This commit is contained in:
Ming Ming 2021-10-21 02:21:37 +08:00
parent 3bc22f285d
commit e5479b7761
3 changed files with 100 additions and 85 deletions

View file

@ -10,7 +10,7 @@ import 'package:nc_photos/iterable_extension.dart';
import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/theme.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 { class AlbumDirPickerArguments {
AlbumDirPickerArguments(this.account); AlbumDirPickerArguments(this.account);
@ -43,8 +43,7 @@ class AlbumDirPicker extends StatefulWidget {
final Account account; final Account account;
} }
class _AlbumDirPickerState extends State<AlbumDirPicker> class _AlbumDirPickerState extends State<AlbumDirPicker> {
with DirPickerMixin<AlbumDirPicker> {
@override @override
build(BuildContext context) { build(BuildContext context) {
return AppTheme( return AppTheme(
@ -54,30 +53,6 @@ class _AlbumDirPickerState extends State<AlbumDirPicker>
); );
} }
@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) { Widget _buildContent(BuildContext context) {
return SafeArea( return SafeArea(
child: Column( child: Column(
@ -102,7 +77,20 @@ class _AlbumDirPickerState extends State<AlbumDirPicker>
), ),
), ),
Expanded( 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(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@ -117,7 +105,7 @@ class _AlbumDirPickerState extends State<AlbumDirPicker>
Text(MaterialLocalizations.of(context).cancelButtonLabel), Text(MaterialLocalizations.of(context).cancelButtonLabel),
), ),
ElevatedButton( ElevatedButton(
onPressed: () => _onConfirmPressed(context), onPressed: _onConfirmPressed,
child: Text(L10n.global().confirmButtonLabel), child: Text(L10n.global().confirmButtonLabel),
), ),
], ],
@ -128,19 +116,35 @@ class _AlbumDirPickerState extends State<AlbumDirPicker>
); );
} }
void _onConfirmPressed(BuildContext context) { void _onConfirmPressed() {
final picked = getPickedDirs(); _pickerKey.currentState?.confirm();
if (picked.isEmpty) { }
void _onPickerConfirmed(BuildContext context, List<File> picks) {
if (picks.isEmpty) {
SnackBarManager().showSnackBar(SnackBar( SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().albumDirPickerListEmptyNotification), content: Text(L10n.global().albumDirPickerListEmptyNotification),
duration: k.snackBarDurationNormal, duration: k.snackBarDurationNormal,
)); ));
} else { } else {
_log.info( _log.info(
"[_onConfirmPressed] Picked: ${picked.map((e) => e.strippedPath).toReadableString()}"); "[_onPickerConfirmed] Picked: ${picks.map((e) => e.strippedPath).toReadableString()}");
Navigator.of(context).pop(picked); 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<DirPickerState>();
late final _rootDir = _getPickerRoot();
static final _log = Logger("widget.album_dir_picker._AlbumDirPickerState"); static final _log = Logger("widget.album_dir_picker._AlbumDirPickerState");
} }

View file

@ -1,5 +1,3 @@
import 'dart:collection';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.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:nc_photos/snack_bar_manager.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
mixin DirPickerMixin<T extends StatefulWidget> on State<T> { 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<File>? initialPicks;
/// Return whether [dir] is a valid target to be picked
final bool Function(File dir)? validator;
final ValueChanged<List<File>>? onConfirmed;
}
class DirPickerState extends State<DirPicker> {
@override @override
initState() { initState() {
super.initState(); super.initState();
_root = LsDirBlocItem(File(path: getPickerRoot()), []); _root = LsDirBlocItem(File(path: widget.rootDir), []);
_initBloc(); _initBloc();
if (widget.initialPicks != null) {
_picks.addAll(widget.initialPicks!);
}
} }
@protected @override
Widget buildDirPicker(BuildContext context) { build(BuildContext context) {
return BlocListener<LsDirBloc, LsDirBlocState>( return BlocListener<LsDirBloc, LsDirBlocState>(
bloc: _bloc, bloc: _bloc,
listener: (context, state) => _onStateChange(context, state), listener: (context, state) => _onStateChange(context, state),
@ -34,27 +57,14 @@ mixin DirPickerMixin<T extends StatefulWidget> on State<T> {
); );
} }
@protected /// Calls the onConfirmed method with the current picked dirs
String getPickerRoot(); void confirm() {
widget.onConfirmed?.call(_picks);
@protected
Account getAccount();
@protected
List<File> getPickedDirs() => UnmodifiableListView(_picks);
@protected
bool canPickDir(File file) => true;
@protected
void pickAll(List<File> dirs) {
_picks.addAll(dirs);
} }
void _initBloc() { void _initBloc() {
_log.info("[_initBloc] Initialize bloc"); _log.info("[_initBloc] Initialize bloc");
_bloc = LsDirBloc(); _navigateInto(File(path: widget.rootDir));
_navigateInto(File(path: getPickerRoot()));
} }
Widget _buildContent(BuildContext context, LsDirBlocState state) { Widget _buildContent(BuildContext context, LsDirBlocState state) {
@ -76,7 +86,7 @@ mixin DirPickerMixin<T extends StatefulWidget> on State<T> {
} }
Widget _buildList(BuildContext context, LsDirBlocState state) { Widget _buildList(BuildContext context, LsDirBlocState state) {
final isTopLevel = _currentPath == getPickerRoot(); final isTopLevel = _currentPath == widget.rootDir;
return AnimatedSwitcher( return AnimatedSwitcher(
duration: k.animationDurationNormal, duration: k.animationDurationNormal,
// see AnimatedSwitcher.defaultLayoutBuilder // see AnimatedSwitcher.defaultLayoutBuilder
@ -118,7 +128,7 @@ mixin DirPickerMixin<T extends StatefulWidget> on State<T> {
} }
Widget _buildItem(BuildContext context, LsDirBlocItem item) { Widget _buildItem(BuildContext context, LsDirBlocItem item) {
final canPick = canPickDir(item.file); final canPick = widget.validator?.call(item.file) != false;
final pickState = _isItemPicked(item); final pickState = _isItemPicked(item);
IconData? iconData; IconData? iconData;
@ -359,17 +369,16 @@ mixin DirPickerMixin<T extends StatefulWidget> on State<T> {
void _navigateInto(File file) { void _navigateInto(File file) {
_currentPath = file.path; _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; late LsDirBlocItem _root;
/// Track where the user is navigating in [_backingFiles] var _currentPath = "";
late String _currentPath;
var _picks = <File>[]; var _picks = <File>[];
static final _log = Logger("widget.dir_picker_mixin.DirPickerMixin"); static final _log = Logger("widget.dir_picker.DirPickerState");
} }
enum _PickState { enum _PickState {

View file

@ -13,7 +13,7 @@ import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/theme.dart'; import 'package:nc_photos/theme.dart';
import 'package:nc_photos/use_case/ls_single_file.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'; import 'package:nc_photos/widget/processing_dialog.dart';
class RootPickerArguments { class RootPickerArguments {
@ -47,8 +47,7 @@ class RootPicker extends StatefulWidget {
final Account account; final Account account;
} }
class _RootPickerState extends State<RootPicker> class _RootPickerState extends State<RootPicker> {
with DirPickerMixin<RootPicker> {
@override @override
initState() { initState() {
super.initState(); super.initState();
@ -61,15 +60,13 @@ class _RootPickerState extends State<RootPicker>
final files = <File>[]; final files = <File>[];
for (final r in widget.account.roots) { for (final r in widget.account.roots) {
if (r.isNotEmpty) { if (r.isNotEmpty) {
_isIniting = true;
_ensureInitDialog(); _ensureInitDialog();
files.add(await LsSingleFile(fileSrc)(widget.account, files.add(await LsSingleFile(fileSrc)(widget.account,
"${api_util.getWebdavRootUrlRelative(widget.account)}/$r")); "${api_util.getWebdavRootUrlRelative(widget.account)}/$r"));
} }
} }
setState(() { setState(() {
_isIniting = false; _initialPicks = files;
pickAll(files);
}); });
} catch (e) { } catch (e) {
SnackBarManager().showSnackBar(SnackBar( SnackBarManager().showSnackBar(SnackBar(
@ -90,12 +87,6 @@ class _RootPickerState extends State<RootPicker>
); );
} }
@override
getPickerRoot() => api_util.getWebdavRootUrlRelative(widget.account);
@override
getAccount() => widget.account;
Widget _buildContent(BuildContext context) { Widget _buildContent(BuildContext context) {
return SafeArea( return SafeArea(
child: Column( child: Column(
@ -120,10 +111,15 @@ class _RootPickerState extends State<RootPicker>
), ),
), ),
Expanded( Expanded(
child: IgnorePointer( child: _initialPicks == null
ignoring: _isIniting, ? Container()
child: buildDirPicker(context), : DirPicker(
), key: _pickerKey,
account: widget.account,
rootDir: api_util.getWebdavRootUrlRelative(widget.account),
initialPicks: _initialPicks,
onConfirmed: (picks) => _onPickerConfirmed(context, picks),
),
), ),
Padding( Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@ -135,7 +131,7 @@ class _RootPickerState extends State<RootPicker>
child: Text(L10n.global().skipButtonLabel), child: Text(L10n.global().skipButtonLabel),
), ),
ElevatedButton( ElevatedButton(
onPressed: () => _onConfirmPressed(context), onPressed: _onConfirmPressed,
child: Text(L10n.global().confirmButtonLabel), child: Text(L10n.global().confirmButtonLabel),
), ),
], ],
@ -174,8 +170,12 @@ class _RootPickerState extends State<RootPicker>
}); });
} }
void _onConfirmPressed(BuildContext context) { void _onConfirmPressed() {
final roots = getPickedDirs().map((e) => e.strippedPath).toList(); _pickerKey.currentState?.confirm();
}
void _onPickerConfirmed(BuildContext context, List<File> picks) {
final roots = picks.map((e) => e.strippedPath).toList();
if (roots.isEmpty) { if (roots.isEmpty) {
SnackBarManager().showSnackBar(SnackBar( SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().rootPickerListEmptyNotification), content: Text(L10n.global().rootPickerListEmptyNotification),
@ -184,7 +184,7 @@ class _RootPickerState extends State<RootPicker>
return; return;
} }
final newAccount = widget.account.copyWith(roots: roots); 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); Navigator.of(context).pop(newAccount);
} }
@ -210,7 +210,9 @@ class _RootPickerState extends State<RootPicker>
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
bool _isIniting = false; final _pickerKey = GlobalKey<DirPickerState>();
List<File>? _initialPicks;
bool _isInitDialogShown = false; bool _isInitDialogShown = false;
static final _log = Logger("widget.root_picker._RootPickerState"); static final _log = Logger("widget.root_picker._RootPickerState");