mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-02 14:56:20 +01:00
237 lines
7 KiB
Dart
237 lines
7 KiB
Dart
|
import 'dart:async';
|
||
|
|
||
|
import 'package:collection/collection.dart';
|
||
|
import 'package:copy_with/copy_with.dart';
|
||
|
import 'package:flutter/material.dart';
|
||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||
|
import 'package:kiwi/kiwi.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/controller/collections_controller.dart';
|
||
|
import 'package:nc_photos/di_container.dart';
|
||
|
import 'package:nc_photos/entity/collection.dart';
|
||
|
import 'package:nc_photos/entity/collection/util.dart';
|
||
|
import 'package:nc_photos/entity/sharee.dart';
|
||
|
import 'package:nc_photos/exception.dart';
|
||
|
import 'package:nc_photos/exception_event.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/suggester.dart';
|
||
|
import 'package:np_codegen/np_codegen.dart';
|
||
|
import 'package:np_common/ci_string.dart';
|
||
|
import 'package:to_string/to_string.dart';
|
||
|
|
||
|
part 'share_collection_dialog.g.dart';
|
||
|
part 'share_collection_dialog/bloc.dart';
|
||
|
part 'share_collection_dialog/state_event.dart';
|
||
|
|
||
|
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||
|
|
||
|
/// Dialog to share a new collection to other user on the same server
|
||
|
///
|
||
|
/// Return the created collection, or null if user cancelled
|
||
|
class ShareCollectionDialog extends StatelessWidget {
|
||
|
const ShareCollectionDialog({
|
||
|
super.key,
|
||
|
required this.account,
|
||
|
required this.collection,
|
||
|
});
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
return BlocProvider(
|
||
|
create: (context) => _Bloc(
|
||
|
container: KiwiContainer().resolve<DiContainer>(),
|
||
|
account: account,
|
||
|
collectionsController:
|
||
|
context.read<AccountController>().collectionsController,
|
||
|
collection: collection,
|
||
|
),
|
||
|
child: const _WrappedShareCollectionDialog(),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
final Account account;
|
||
|
final Collection collection;
|
||
|
}
|
||
|
|
||
|
class _WrappedShareCollectionDialog extends StatefulWidget {
|
||
|
const _WrappedShareCollectionDialog();
|
||
|
|
||
|
@override
|
||
|
State<StatefulWidget> createState() => _WrappedShareCollectionDialogState();
|
||
|
}
|
||
|
|
||
|
class _WrappedShareCollectionDialogState
|
||
|
extends State<_WrappedShareCollectionDialog> {
|
||
|
@override
|
||
|
void initState() {
|
||
|
super.initState();
|
||
|
_bloc.add(const _LoadSharee());
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
return MultiBlocListener(
|
||
|
listeners: [
|
||
|
BlocListener<_Bloc, _State>(
|
||
|
listenWhen: (previous, current) => previous.error != current.error,
|
||
|
listener: (context, state) {
|
||
|
if (state.error != null) {
|
||
|
if (state.error!.error is CollectionPartialShareException) {
|
||
|
// TODO localize string
|
||
|
SnackBarManager().showSnackBar(const SnackBar(
|
||
|
content: Text("Collection shared partially"),
|
||
|
duration: k.snackBarDurationNormal,
|
||
|
));
|
||
|
} else {
|
||
|
SnackBarManager().showSnackBar(SnackBar(
|
||
|
content:
|
||
|
Text(exception_util.toUserString(state.error!.error)),
|
||
|
duration: k.snackBarDurationNormal,
|
||
|
));
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
),
|
||
|
],
|
||
|
child: _BlocBuilder(
|
||
|
buildWhen: (previous, current) =>
|
||
|
previous.collection != current.collection ||
|
||
|
previous.processingShares != current.processingShares,
|
||
|
builder: (context, state) {
|
||
|
final shares = {
|
||
|
...state.collection.shares,
|
||
|
...state.processingShares,
|
||
|
}.sortedBy((e) => e.username);
|
||
|
return SimpleDialog(
|
||
|
title: Text(L10n.global().shareAlbumDialogTitle),
|
||
|
children: [
|
||
|
...shares.map((s) => _ShareView(
|
||
|
share: s,
|
||
|
isProcessing: state.processingShares.contains(s),
|
||
|
onPressed: () {
|
||
|
_bloc.add(_Unshare(s));
|
||
|
},
|
||
|
)),
|
||
|
const _ShareeInputView(),
|
||
|
],
|
||
|
);
|
||
|
},
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
late final _bloc = context.read<_Bloc>();
|
||
|
}
|
||
|
|
||
|
class _ShareeInputView extends StatefulWidget {
|
||
|
const _ShareeInputView();
|
||
|
|
||
|
@override
|
||
|
State<StatefulWidget> createState() => _ShareeInputViewState();
|
||
|
}
|
||
|
|
||
|
class _ShareeInputViewState extends State<_ShareeInputView> {
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
return MultiBlocListener(
|
||
|
listeners: [
|
||
|
BlocListener<_Bloc, _State>(
|
||
|
listenWhen: (previous, current) =>
|
||
|
previous.shareeSuggester != current.shareeSuggester,
|
||
|
listener: (context, state) {
|
||
|
// search again
|
||
|
if (_lastPattern != null) {
|
||
|
_onSearch(_lastPattern!);
|
||
|
}
|
||
|
},
|
||
|
),
|
||
|
],
|
||
|
child: Padding(
|
||
|
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||
|
child: TypeAheadField<Sharee>(
|
||
|
textFieldConfiguration: TextFieldConfiguration(
|
||
|
controller: _textController,
|
||
|
decoration: InputDecoration(
|
||
|
hintText: L10n.global().addUserInputHint,
|
||
|
),
|
||
|
),
|
||
|
suggestionsCallback: _onSearch,
|
||
|
itemBuilder: (context, suggestion) => ListTile(
|
||
|
title: Text(suggestion.label),
|
||
|
subtitle: Text(suggestion.shareWith.toString()),
|
||
|
),
|
||
|
onSuggestionSelected: _onSuggestionSelected,
|
||
|
hideOnEmpty: true,
|
||
|
hideOnLoading: true,
|
||
|
autoFlipDirection: true,
|
||
|
),
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
Iterable<Sharee> _onSearch(String pattern) {
|
||
|
_lastPattern = pattern;
|
||
|
final suggester = _bloc.state.shareeSuggester;
|
||
|
return suggester?.search(pattern.toCi()) ?? [];
|
||
|
}
|
||
|
|
||
|
void _onSuggestionSelected(Sharee sharee) {
|
||
|
_textController.clear();
|
||
|
_bloc.add(_Share(sharee));
|
||
|
}
|
||
|
|
||
|
late final _bloc = context.read<_Bloc>();
|
||
|
final _textController = TextEditingController();
|
||
|
|
||
|
String? _lastPattern;
|
||
|
}
|
||
|
|
||
|
class _ShareView extends StatelessWidget {
|
||
|
const _ShareView({
|
||
|
required this.share,
|
||
|
required this.isProcessing,
|
||
|
this.onPressed,
|
||
|
});
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
final Widget trailing;
|
||
|
if (isProcessing) {
|
||
|
trailing = const Padding(
|
||
|
padding: EdgeInsetsDirectional.only(end: 12),
|
||
|
child: SizedBox(
|
||
|
width: 24,
|
||
|
height: 24,
|
||
|
child: CircularProgressIndicator(),
|
||
|
),
|
||
|
);
|
||
|
} else {
|
||
|
trailing = Checkbox(
|
||
|
value: true,
|
||
|
onChanged: (_) {},
|
||
|
);
|
||
|
}
|
||
|
return SimpleDialogOption(
|
||
|
onPressed: isProcessing ? null : onPressed,
|
||
|
child: ListTile(
|
||
|
title: Text(share.username),
|
||
|
subtitle: Text(share.userId.toString()),
|
||
|
// pass through the tap event
|
||
|
trailing: IgnorePointer(
|
||
|
child: trailing,
|
||
|
),
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
final CollectionShare share;
|
||
|
final bool isProcessing;
|
||
|
final VoidCallback? onPressed;
|
||
|
}
|