mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 16:56:19 +01:00
292 lines
10 KiB
Dart
292 lines
10 KiB
Dart
import 'dart:async';
|
|
import 'dart:math';
|
|
|
|
import 'package:copy_with/copy_with.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:intl/intl.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/asset.dart';
|
|
import 'package:nc_photos/bloc_util.dart';
|
|
import 'package:nc_photos/controller/account_controller.dart';
|
|
import 'package:nc_photos/controller/collection_items_controller.dart';
|
|
import 'package:nc_photos/controller/collections_controller.dart';
|
|
import 'package:nc_photos/controller/files_controller.dart';
|
|
import 'package:nc_photos/controller/pref_controller.dart';
|
|
import 'package:nc_photos/di_container.dart';
|
|
import 'package:nc_photos/download_handler.dart';
|
|
import 'package:nc_photos/entity/collection.dart';
|
|
import 'package:nc_photos/entity/collection/adapter.dart';
|
|
import 'package:nc_photos/entity/collection_item.dart';
|
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
|
import 'package:nc_photos/exception_event.dart';
|
|
import 'package:nc_photos/flutter_util.dart';
|
|
import 'package:nc_photos/k.dart' as k;
|
|
import 'package:nc_photos/live_photo_util.dart';
|
|
import 'package:nc_photos/platform/features.dart' as features;
|
|
import 'package:nc_photos/set_as_handler.dart';
|
|
import 'package:nc_photos/share_handler.dart';
|
|
import 'package:nc_photos/snack_bar_manager.dart';
|
|
import 'package:nc_photos/theme.dart';
|
|
import 'package:nc_photos/widget/app_intermediate_circular_progress_indicator.dart';
|
|
import 'package:nc_photos/widget/disposable.dart';
|
|
import 'package:nc_photos/widget/file_content_view.dart';
|
|
import 'package:nc_photos/widget/handler/remove_selection_handler.dart';
|
|
import 'package:nc_photos/widget/horizontal_page_viewer.dart';
|
|
import 'package:nc_photos/widget/image_editor.dart';
|
|
import 'package:nc_photos/widget/image_enhancer.dart';
|
|
import 'package:nc_photos/widget/page_visibility_mixin.dart';
|
|
import 'package:nc_photos/widget/png_icon.dart';
|
|
import 'package:nc_photos/widget/slideshow_dialog.dart';
|
|
import 'package:nc_photos/widget/slideshow_viewer.dart';
|
|
import 'package:nc_photos/widget/viewer_detail_pane.dart';
|
|
import 'package:nc_photos/widget/viewer_mixin.dart';
|
|
import 'package:np_codegen/np_codegen.dart';
|
|
import 'package:np_collection/np_collection.dart';
|
|
import 'package:np_common/object_util.dart';
|
|
import 'package:np_common/or_null.dart';
|
|
import 'package:np_common/unique.dart';
|
|
import 'package:np_platform_util/np_platform_util.dart';
|
|
import 'package:to_string/to_string.dart';
|
|
|
|
part 'viewer.g.dart';
|
|
part 'viewer/app_bar.dart';
|
|
part 'viewer/app_bar_buttons.dart';
|
|
part 'viewer/bloc.dart';
|
|
part 'viewer/detail_pane.dart';
|
|
part 'viewer/state_event.dart';
|
|
part 'viewer/type.dart';
|
|
part 'viewer/view.dart';
|
|
|
|
class ViewerArguments {
|
|
const ViewerArguments(
|
|
this.fileIds,
|
|
this.startIndex, {
|
|
this.collectionId,
|
|
});
|
|
|
|
final List<int> fileIds;
|
|
final int startIndex;
|
|
final String? collectionId;
|
|
}
|
|
|
|
class Viewer extends StatelessWidget {
|
|
static const routeName = "/viewer";
|
|
|
|
static Route buildRoute(ViewerArguments args, RouteSettings settings) =>
|
|
CustomizableMaterialPageRoute(
|
|
transitionDuration: k.heroDurationNormal,
|
|
reverseTransitionDuration: k.heroDurationNormal,
|
|
builder: (_) => Viewer.fromArgs(args),
|
|
settings: settings,
|
|
);
|
|
|
|
const Viewer({
|
|
super.key,
|
|
required this.fileIds,
|
|
required this.startIndex,
|
|
this.collectionId,
|
|
});
|
|
|
|
Viewer.fromArgs(ViewerArguments args, {Key? key})
|
|
: this(
|
|
key: key,
|
|
fileIds: args.fileIds,
|
|
startIndex: args.startIndex,
|
|
collectionId: args.collectionId,
|
|
);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final accountController = context.read<AccountController>();
|
|
return BlocProvider(
|
|
create: (_) => _Bloc(
|
|
KiwiContainer().resolve(),
|
|
account: accountController.account,
|
|
filesController: accountController.filesController,
|
|
collectionsController: accountController.collectionsController,
|
|
prefController: context.read(),
|
|
fileIds: fileIds,
|
|
startIndex: startIndex,
|
|
brightness: Theme.of(context).brightness,
|
|
collectionId: collectionId,
|
|
)..add(const _Init()),
|
|
child: const _WrappedViewer(),
|
|
);
|
|
}
|
|
|
|
final List<int> fileIds;
|
|
final int startIndex;
|
|
|
|
/// ID of the collection these files belongs to, or null
|
|
final String? collectionId;
|
|
}
|
|
|
|
class _WrappedViewer extends StatefulWidget {
|
|
const _WrappedViewer();
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _WrappedViewerState();
|
|
}
|
|
|
|
@npLog
|
|
class _WrappedViewerState extends State<_WrappedViewer>
|
|
with
|
|
DisposableManagerMixin<_WrappedViewer>,
|
|
ViewerControllersMixin<_WrappedViewer>,
|
|
RouteAware,
|
|
PageVisibilityMixin {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Theme(
|
|
data: buildDarkTheme(context),
|
|
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
|
value: const SystemUiOverlayStyle(
|
|
systemNavigationBarColor: Colors.black,
|
|
systemNavigationBarIconBrightness: Brightness.dark,
|
|
),
|
|
child: MultiBlocListener(
|
|
listeners: [
|
|
_BlocListenerT(
|
|
selector: (state) => state.imageEditorRequest,
|
|
listener: (context, imageEditorRequest) {
|
|
if (imageEditorRequest.value != null) {
|
|
Navigator.of(context).pushNamed(ImageEditor.routeName,
|
|
arguments: imageEditorRequest.value);
|
|
}
|
|
},
|
|
),
|
|
_BlocListenerT(
|
|
selector: (state) => state.imageEnhancerRequest,
|
|
listener: (context, imageEnhancerRequest) {
|
|
if (imageEnhancerRequest.value != null) {
|
|
Navigator.of(context).pushNamed(ImageEnhancer.routeName,
|
|
arguments: imageEnhancerRequest.value);
|
|
}
|
|
},
|
|
),
|
|
_BlocListenerT(
|
|
selector: (state) => state.shareRequest,
|
|
listener: (context, shareRequest) {
|
|
if (shareRequest.value != null) {
|
|
ShareHandler(
|
|
KiwiContainer().resolve<DiContainer>(),
|
|
context: context,
|
|
).shareFiles(
|
|
context.bloc.account, [shareRequest.value!.file]);
|
|
}
|
|
},
|
|
),
|
|
_BlocListenerT(
|
|
selector: (state) => state.slideshowRequest,
|
|
listener: _onSlideshowRequest,
|
|
),
|
|
_BlocListenerT(
|
|
selector: (state) => state.setAsRequest,
|
|
listener: _onSetAsRequest,
|
|
),
|
|
_BlocListenerT(
|
|
selector: (state) => state.error,
|
|
listener: (context, error) {
|
|
if (error != null && isPageVisible()) {
|
|
SnackBarManager().showSnackBarForException(error.error);
|
|
}
|
|
},
|
|
),
|
|
],
|
|
child: _BlocBuilder(
|
|
buildWhen: (previous, current) =>
|
|
previous.isShowAppBar != current.isShowAppBar ||
|
|
previous.isDetailPaneActive != current.isDetailPaneActive,
|
|
builder: (context, state) => Scaffold(
|
|
extendBodyBehindAppBar: true,
|
|
extendBody: true,
|
|
appBar: state.isShowAppBar
|
|
? const PreferredSize(
|
|
preferredSize: Size.fromHeight(kToolbarHeight),
|
|
child: _AppBar(),
|
|
)
|
|
: null,
|
|
bottomNavigationBar:
|
|
state.isShowAppBar && !state.isDetailPaneActive
|
|
? const _BottomAppBar()
|
|
: null,
|
|
body: const _ContentBody(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _onSlideshowRequest(
|
|
BuildContext context,
|
|
Unique<_SlideshowRequest?> slideshowRequest,
|
|
) async {
|
|
if (slideshowRequest.value == null) {
|
|
return;
|
|
}
|
|
final result = await showDialog<SlideshowConfig>(
|
|
context: context,
|
|
builder: (_) => SlideshowDialog(
|
|
duration: context.bloc.prefController.slideshowDurationValue,
|
|
isShuffle: context.bloc.prefController.isSlideshowShuffleValue,
|
|
isRepeat: context.bloc.prefController.isSlideshowRepeatValue,
|
|
isReverse: context.bloc.prefController.isSlideshowReverseValue,
|
|
),
|
|
);
|
|
if (!context.mounted || result == null) {
|
|
return;
|
|
}
|
|
unawaited(
|
|
context.bloc.prefController.setSlideshowDuration(result.duration));
|
|
unawaited(
|
|
context.bloc.prefController.setSlideshowShuffle(result.isShuffle));
|
|
unawaited(context.bloc.prefController.setSlideshowRepeat(result.isRepeat));
|
|
unawaited(
|
|
context.bloc.prefController.setSlideshowReverse(result.isReverse));
|
|
final newIndex = await Navigator.of(context).pushNamed<int>(
|
|
SlideshowViewer.routeName,
|
|
arguments: SlideshowViewerArguments(
|
|
slideshowRequest.value!.fileIds,
|
|
slideshowRequest.value!.startIndex,
|
|
slideshowRequest.value!.collectionId,
|
|
result,
|
|
),
|
|
);
|
|
_log.info("[_onSlideshowRequest] Slideshow ended, jump to: $newIndex");
|
|
if (newIndex != null && context.mounted) {
|
|
context.addEvent(_RequestPage(newIndex));
|
|
}
|
|
}
|
|
|
|
void _onSetAsRequest(
|
|
BuildContext context,
|
|
Unique<_SetAsRequest?> setAsRequest,
|
|
) {
|
|
if (setAsRequest.value == null) {
|
|
return;
|
|
}
|
|
SetAsHandler(
|
|
KiwiContainer().resolve(),
|
|
context: context,
|
|
).setAsFile(setAsRequest.value!.account, setAsRequest.value!.file);
|
|
}
|
|
}
|
|
|
|
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
|
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
|
typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
|
typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
|
typedef _Emitter = Emitter<_State>;
|
|
|
|
extension on BuildContext {
|
|
_Bloc get bloc => read<_Bloc>();
|
|
_State get state => bloc.state;
|
|
void addEvent(_Event event) => bloc.add(event);
|
|
}
|