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/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<AlbumDirPicker>
with DirPickerMixin<AlbumDirPicker> {
class _AlbumDirPickerState extends State<AlbumDirPicker> {
@override
build(BuildContext context) {
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) {
return SafeArea(
child: Column(
@ -102,7 +77,20 @@ class _AlbumDirPickerState extends State<AlbumDirPicker>
),
),
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<AlbumDirPicker>
Text(MaterialLocalizations.of(context).cancelButtonLabel),
),
ElevatedButton(
onPressed: () => _onConfirmPressed(context),
onPressed: _onConfirmPressed,
child: Text(L10n.global().confirmButtonLabel),
),
],
@ -128,19 +116,35 @@ class _AlbumDirPickerState extends State<AlbumDirPicker>
);
}
void _onConfirmPressed(BuildContext context) {
final picked = getPickedDirs();
if (picked.isEmpty) {
void _onConfirmPressed() {
_pickerKey.currentState?.confirm();
}
void _onPickerConfirmed(BuildContext context, List<File> 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<DirPickerState>();
late final _rootDir = _getPickerRoot();
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/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<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
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<LsDirBloc, LsDirBlocState>(
bloc: _bloc,
listener: (context, state) => _onStateChange(context, state),
@ -34,27 +57,14 @@ mixin DirPickerMixin<T extends StatefulWidget> on State<T> {
);
}
@protected
String getPickerRoot();
@protected
Account getAccount();
@protected
List<File> getPickedDirs() => UnmodifiableListView(_picks);
@protected
bool canPickDir(File file) => true;
@protected
void pickAll(List<File> 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<T extends StatefulWidget> on State<T> {
}
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<T extends StatefulWidget> on State<T> {
}
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<T extends StatefulWidget> on State<T> {
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 = <File>[];
static final _log = Logger("widget.dir_picker_mixin.DirPickerMixin");
static final _log = Logger("widget.dir_picker.DirPickerState");
}
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/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<RootPicker>
with DirPickerMixin<RootPicker> {
class _RootPickerState extends State<RootPicker> {
@override
initState() {
super.initState();
@ -61,15 +60,13 @@ class _RootPickerState extends State<RootPicker>
final files = <File>[];
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<RootPicker>
);
}
@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<RootPicker>
),
),
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<RootPicker>
child: Text(L10n.global().skipButtonLabel),
),
ElevatedButton(
onPressed: () => _onConfirmPressed(context),
onPressed: _onConfirmPressed,
child: Text(L10n.global().confirmButtonLabel),
),
],
@ -174,8 +170,12 @@ class _RootPickerState extends State<RootPicker>
});
}
void _onConfirmPressed(BuildContext context) {
final roots = getPickedDirs().map((e) => e.strippedPath).toList();
void _onConfirmPressed() {
_pickerKey.currentState?.confirm();
}
void _onPickerConfirmed(BuildContext context, List<File> 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<RootPicker>
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<RootPicker>
Navigator.of(context).pop();
}
bool _isIniting = false;
final _pickerKey = GlobalKey<DirPickerState>();
List<File>? _initialPicks;
bool _isInitDialogShown = false;
static final _log = Logger("widget.root_picker._RootPickerState");