import 'dart:async'; import 'package:copy_with/copy_with.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/controller/account_controller.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/cover_provider.dart'; import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/album/sort_provider.dart'; import 'package:nc_photos/entity/collection.dart'; import 'package:nc_photos/entity/collection/content_provider/album.dart'; import 'package:nc_photos/entity/collection/content_provider/nc_album.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/nc_album.dart'; import 'package:nc_photos/entity/tag.dart'; import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/widget/album_dir_picker.dart'; import 'package:nc_photos/widget/processing_dialog.dart'; import 'package:nc_photos/widget/tag_picker_dialog.dart'; import 'package:np_codegen/np_codegen.dart'; import 'package:to_string/to_string.dart'; part 'new_collection_dialog.g.dart'; part 'new_collection_dialog/bloc.dart'; part 'new_collection_dialog/state_event.dart'; /// Dialog to create a new collection /// /// Return the created collection, or null if user cancelled class NewCollectionDialog extends StatelessWidget { const NewCollectionDialog({ super.key, required this.account, this.isAllowDynamic = true, }); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => _Bloc( account: account, supportedProviders: { _ProviderOption.appAlbum, _ProviderOption.ncAlbum, if (isAllowDynamic) ...{ _ProviderOption.dir, _ProviderOption.tag, }, }, ), child: const _WrappedNewCollectionDialog(), ); } final Account account; final bool isAllowDynamic; } class _WrappedNewCollectionDialog extends StatefulWidget { const _WrappedNewCollectionDialog(); @override State createState() => _WrappedNewCollectionDialogState(); } @npLog class _WrappedNewCollectionDialogState extends State<_WrappedNewCollectionDialog> { @override Widget build(BuildContext context) { return MultiBlocListener( listeners: [ BlocListener<_Bloc, _State>( listenWhen: (previous, current) => previous.result != current.result && current.result != null, listener: _onResult, ), ], child: BlocBuilder<_Bloc, _State>( buildWhen: (previous, current) => previous.result != current.result || previous.showDialog != current.showDialog, builder: (context, state) => Visibility( visible: state.result == null && state.showDialog, child: AlertDialog( title: Text(L10n.global().createCollectionTooltip), content: Form( key: _formKey, child: Container( constraints: const BoxConstraints.tightFor(width: 280), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: const [ _NameTextField(), _ProviderDropdown(), SizedBox(height: 8), _ProviderDescription(), ], ), ), ), actions: [ TextButton( onPressed: () => _onOkPressed(context), child: Text(MaterialLocalizations.of(context).okButtonLabel), ), ], ), ), ), ); } Future _onOkPressed(BuildContext context) async { if (_formKey.currentState?.validate() == true) { if (_bloc.state.formValue.provider == _ProviderOption.dir) { _bloc.add(const _HideDialog()); final dirs = await Navigator.of(context).pushNamed>( AlbumDirPicker.routeName, arguments: AlbumDirPickerArguments(_bloc.account), ); if (dirs == null) { Navigator.of(context).pop(); return; } _bloc ..add(_SubmitDirs(dirs)) ..add(const _SubmitForm()); } else if (_bloc.state.formValue.provider == _ProviderOption.tag) { _bloc.add(const _HideDialog()); final tags = await showDialog>( context: context, builder: (_) => TagPickerDialog(account: _bloc.account), ); if (tags == null || tags.isEmpty) { Navigator.of(context).pop(); return; } _bloc ..add(_SubmitTags(tags)) ..add(const _SubmitForm()); } else { _bloc.add(const _SubmitForm()); } } } Future _onResult(BuildContext context, _State state) async { unawaited(showDialog( barrierDismissible: false, context: context, builder: (_) => ProcessingDialog(text: L10n.global().genericProcessingDialogContent), )); try { final collection = await context .read() .collectionsController .createNew(state.result!); Navigator.of(context) ..pop() ..pop(collection); } catch (e, stackTrace) { _log.shout("[_onResult] Failed", e, stackTrace); SnackBarManager().showSnackBar(SnackBar( content: Text(exception_util.toUserString(e)), duration: k.snackBarDurationNormal, )); Navigator.of(context).pop(); } } late final _bloc = context.read<_Bloc>(); final _formKey = GlobalKey(); } class _NameTextField extends StatelessWidget { const _NameTextField(); @override Widget build(BuildContext context) { return TextFormField( decoration: InputDecoration( hintText: L10n.global().nameInputHint, ), validator: (value) { if (value!.isEmpty) { return L10n.global().nameInputInvalidEmpty; } return null; }, onChanged: (value) { context.read<_Bloc>().add(_SubmitName(value)); }, ); } } class _ProviderDropdown extends StatelessWidget { const _ProviderDropdown(); @override Widget build(BuildContext context) { return BlocBuilder<_Bloc, _State>( buildWhen: (previous, current) => previous.formValue.provider != current.formValue.provider, builder: (context, state) => DropdownButtonHideUnderline( child: DropdownButtonFormField<_ProviderOption>( value: state.formValue.provider, items: state.supportedProviders .map((e) => DropdownMenuItem<_ProviderOption>( value: e, child: Text(e.toValueString(context)), )) .toList(), onChanged: (value) { context.read<_Bloc>().add(_SubmitProvider(value!)); }, ), ), ); } } class _ProviderDescription extends StatelessWidget { const _ProviderDescription(); @override Widget build(BuildContext context) { return BlocBuilder<_Bloc, _State>( buildWhen: (previous, current) => previous.formValue.provider != current.formValue.provider, builder: (context, state) => Text( state.formValue.provider.toDescription(context), style: Theme.of(context).textTheme.bodyText2, ), ); } } enum _ProviderOption { appAlbum, dir, tag, ncAlbum; String toValueString(BuildContext context) { switch (this) { case appAlbum: return L10n.global().createCollectionDialogAlbumLabel; case dir: return L10n.global().createCollectionDialogFolderLabel; case tag: return L10n.global().createCollectionDialogTagLabel; case ncAlbum: return L10n.global().createCollectionDialogNextcloudAlbumLabel; } } String toDescription(BuildContext context) { switch (this) { case appAlbum: return L10n.global().createCollectionDialogAlbumDescription; case dir: return L10n.global().createCollectionDialogFolderDescription; case tag: return L10n.global().createCollectionDialogTagDescription; case ncAlbum: return L10n.global().createCollectionDialogNextcloudAlbumDescription; } } }