2021-08-24 14:56:14 +02:00
|
|
|
import 'dart:async';
|
2021-04-10 06:28:12 +02:00
|
|
|
import 'dart:math';
|
|
|
|
|
2024-10-06 19:15:45 +02:00
|
|
|
import 'package:copy_with/copy_with.dart';
|
2021-04-10 06:28:12 +02:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter/rendering.dart';
|
|
|
|
import 'package:flutter/services.dart';
|
2023-04-17 18:15:29 +02:00
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
2024-05-29 15:55:16 +02:00
|
|
|
import 'package:intl/intl.dart';
|
2022-01-25 11:17:19 +01:00
|
|
|
import 'package:kiwi/kiwi.dart';
|
2021-04-10 06:28:12 +02:00
|
|
|
import 'package:logging/logging.dart';
|
|
|
|
import 'package:nc_photos/account.dart';
|
2021-07-25 07:00:38 +02:00
|
|
|
import 'package:nc_photos/app_localizations.dart';
|
2024-06-07 19:33:27 +02:00
|
|
|
import 'package:nc_photos/asset.dart';
|
2024-10-06 19:15:45 +02:00
|
|
|
import 'package:nc_photos/bloc_util.dart';
|
2023-04-17 18:15:29 +02:00
|
|
|
import 'package:nc_photos/controller/account_controller.dart';
|
|
|
|
import 'package:nc_photos/controller/collection_items_controller.dart';
|
2024-10-06 19:15:45 +02:00
|
|
|
import 'package:nc_photos/controller/collections_controller.dart';
|
|
|
|
import 'package:nc_photos/controller/files_controller.dart';
|
|
|
|
import 'package:nc_photos/controller/pref_controller.dart';
|
2022-01-25 11:17:19 +01:00
|
|
|
import 'package:nc_photos/di_container.dart';
|
2021-09-28 22:56:44 +02:00
|
|
|
import 'package:nc_photos/download_handler.dart';
|
2023-04-17 18:15:29 +02:00
|
|
|
import 'package:nc_photos/entity/collection.dart';
|
|
|
|
import 'package:nc_photos/entity/collection/adapter.dart';
|
|
|
|
import 'package:nc_photos/entity/collection_item.dart';
|
2022-10-15 16:29:18 +02:00
|
|
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
2024-10-06 19:15:45 +02:00
|
|
|
import 'package:nc_photos/exception_event.dart';
|
2022-09-07 11:37:50 +02:00
|
|
|
import 'package:nc_photos/flutter_util.dart';
|
2021-04-10 06:28:12 +02:00
|
|
|
import 'package:nc_photos/k.dart' as k;
|
2024-06-07 19:33:27 +02:00
|
|
|
import 'package:nc_photos/live_photo_util.dart';
|
2022-05-04 10:42:46 +02:00
|
|
|
import 'package:nc_photos/platform/features.dart' as features;
|
2021-07-10 17:06:04 +02:00
|
|
|
import 'package:nc_photos/share_handler.dart';
|
2023-04-17 18:15:29 +02:00
|
|
|
import 'package:nc_photos/snack_bar_manager.dart';
|
2021-04-10 06:28:12 +02:00
|
|
|
import 'package:nc_photos/theme.dart';
|
2021-08-23 19:28:25 +02:00
|
|
|
import 'package:nc_photos/widget/disposable.dart';
|
2024-10-06 19:15:45 +02:00
|
|
|
import 'package:nc_photos/widget/file_content_view.dart';
|
2021-12-02 11:47:37 +01:00
|
|
|
import 'package:nc_photos/widget/handler/remove_selection_handler.dart';
|
2021-07-31 17:38:26 +02:00
|
|
|
import 'package:nc_photos/widget/horizontal_page_viewer.dart';
|
2022-07-12 22:11:27 +02:00
|
|
|
import 'package:nc_photos/widget/image_editor.dart';
|
2022-09-15 12:44:38 +02:00
|
|
|
import 'package:nc_photos/widget/image_enhancer.dart';
|
2024-10-06 19:15:45 +02:00
|
|
|
import 'package:nc_photos/widget/page_visibility_mixin.dart';
|
2024-06-07 19:33:27 +02:00
|
|
|
import 'package:nc_photos/widget/png_icon.dart';
|
2021-09-14 23:00:24 +02:00
|
|
|
import 'package:nc_photos/widget/slideshow_dialog.dart';
|
|
|
|
import 'package:nc_photos/widget/slideshow_viewer.dart';
|
2021-04-10 06:28:12 +02:00
|
|
|
import 'package:nc_photos/widget/viewer_detail_pane.dart';
|
2021-09-14 23:00:24 +02:00
|
|
|
import 'package:nc_photos/widget/viewer_mixin.dart';
|
2022-12-16 16:01:04 +01:00
|
|
|
import 'package:np_codegen/np_codegen.dart';
|
2024-10-06 19:15:45 +02:00
|
|
|
import 'package:np_collection/np_collection.dart';
|
|
|
|
import 'package:np_common/object_util.dart';
|
2024-05-08 18:18:05 +02:00
|
|
|
import 'package:np_common/or_null.dart';
|
2024-10-06 19:15:45 +02:00
|
|
|
import 'package:np_common/unique.dart';
|
2024-05-29 15:55:16 +02:00
|
|
|
import 'package:np_platform_util/np_platform_util.dart';
|
2024-10-06 19:15:45 +02:00
|
|
|
import 'package:to_string/to_string.dart';
|
|
|
|
|
2024-10-09 14:47:47 +02:00
|
|
|
part 'viewer.g.dart';
|
2024-10-06 19:15:45 +02:00
|
|
|
part 'viewer/app_bar.dart';
|
2024-10-09 14:47:47 +02:00
|
|
|
part 'viewer/app_bar_buttons.dart';
|
2024-10-06 19:15:45 +02:00
|
|
|
part 'viewer/bloc.dart';
|
|
|
|
part 'viewer/detail_pane.dart';
|
|
|
|
part 'viewer/state_event.dart';
|
|
|
|
part 'viewer/type.dart';
|
|
|
|
part 'viewer/view.dart';
|
2021-04-10 06:28:12 +02:00
|
|
|
|
|
|
|
class ViewerArguments {
|
2023-04-17 18:15:29 +02:00
|
|
|
const ViewerArguments(
|
2024-10-06 19:15:45 +02:00
|
|
|
this.fileIds,
|
2021-08-13 22:18:35 +02:00
|
|
|
this.startIndex, {
|
2024-10-06 19:15:45 +02:00
|
|
|
this.collectionId,
|
2021-08-13 22:18:35 +02:00
|
|
|
});
|
2021-04-10 06:28:12 +02:00
|
|
|
|
2024-10-06 19:15:45 +02:00
|
|
|
final List<int> fileIds;
|
2021-04-10 06:28:12 +02:00
|
|
|
final int startIndex;
|
2024-10-06 19:15:45 +02:00
|
|
|
final String? collectionId;
|
2021-04-10 06:28:12 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 19:15:45 +02:00
|
|
|
class Viewer extends StatelessWidget {
|
2021-04-10 06:28:12 +02:00
|
|
|
static const routeName = "/viewer";
|
|
|
|
|
2022-09-07 11:37:50 +02:00
|
|
|
static Route buildRoute(ViewerArguments args) =>
|
|
|
|
CustomizableMaterialPageRoute(
|
|
|
|
transitionDuration: k.heroDurationNormal,
|
|
|
|
reverseTransitionDuration: k.heroDurationNormal,
|
|
|
|
builder: (_) => Viewer.fromArgs(args),
|
2021-07-23 22:05:57 +02:00
|
|
|
);
|
|
|
|
|
2021-09-15 08:58:06 +02:00
|
|
|
const Viewer({
|
2024-05-28 17:10:33 +02:00
|
|
|
super.key,
|
2024-10-06 19:15:45 +02:00
|
|
|
required this.fileIds,
|
2021-07-23 22:05:57 +02:00
|
|
|
required this.startIndex,
|
2024-10-06 19:15:45 +02:00
|
|
|
this.collectionId,
|
2024-05-28 17:10:33 +02:00
|
|
|
});
|
2021-04-10 06:28:12 +02:00
|
|
|
|
2021-07-23 22:05:57 +02:00
|
|
|
Viewer.fromArgs(ViewerArguments args, {Key? key})
|
2021-04-10 06:28:12 +02:00
|
|
|
: this(
|
|
|
|
key: key,
|
2024-10-06 19:15:45 +02:00
|
|
|
fileIds: args.fileIds,
|
2021-04-10 06:28:12 +02:00
|
|
|
startIndex: args.startIndex,
|
2024-10-06 19:15:45 +02:00
|
|
|
collectionId: args.collectionId,
|
2021-04-10 06:28:12 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
@override
|
2024-10-06 19:15:45 +02:00
|
|
|
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(),
|
|
|
|
);
|
|
|
|
}
|
2021-04-10 06:28:12 +02:00
|
|
|
|
2024-10-06 19:15:45 +02:00
|
|
|
final List<int> fileIds;
|
2021-04-10 06:28:12 +02:00
|
|
|
final int startIndex;
|
2021-08-13 22:18:35 +02:00
|
|
|
|
2024-10-06 19:15:45 +02:00
|
|
|
/// ID of the collection these files belongs to, or null
|
|
|
|
final String? collectionId;
|
2021-04-10 06:28:12 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 19:15:45 +02:00
|
|
|
class _WrappedViewer extends StatefulWidget {
|
|
|
|
const _WrappedViewer();
|
|
|
|
|
2022-10-16 17:19:51 +02:00
|
|
|
@override
|
2024-10-06 19:15:45 +02:00
|
|
|
State<StatefulWidget> createState() => _WrappedViewerState();
|
|
|
|
}
|
2022-10-16 17:19:51 +02:00
|
|
|
|
2024-10-06 19:15:45 +02:00
|
|
|
@npLog
|
|
|
|
class _WrappedViewerState extends State<_WrappedViewer>
|
|
|
|
with
|
|
|
|
DisposableManagerMixin<_WrappedViewer>,
|
|
|
|
ViewerControllersMixin<_WrappedViewer>,
|
|
|
|
RouteAware,
|
|
|
|
PageVisibilityMixin {
|
2021-04-10 06:28:12 +02:00
|
|
|
@override
|
2024-10-06 19:15:45 +02:00
|
|
|
Widget build(BuildContext context) {
|
2022-11-12 10:55:33 +01:00
|
|
|
return Theme(
|
2023-08-19 18:47:56 +02:00
|
|
|
data: buildDarkTheme(context),
|
2023-08-04 21:51:28 +02:00
|
|
|
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
|
|
|
value: const SystemUiOverlayStyle(
|
|
|
|
systemNavigationBarColor: Colors.black,
|
|
|
|
systemNavigationBarIconBrightness: Brightness.dark,
|
|
|
|
),
|
2024-10-06 19:15:45 +02:00
|
|
|
child: MultiBlocListener(
|
|
|
|
listeners: [
|
|
|
|
_BlocListenerT(
|
|
|
|
selector: (state) => state.imageEditorRequest,
|
|
|
|
listener: (context, imageEditorRequest) {
|
|
|
|
if (imageEditorRequest.value != null) {
|
|
|
|
Navigator.of(context).pushNamed(ImageEditor.routeName,
|
|
|
|
arguments: imageEditorRequest.value);
|
|
|
|
}
|
|
|
|
},
|
2021-04-10 06:28:12 +02:00
|
|
|
),
|
2024-10-06 19:15:45 +02:00
|
|
|
_BlocListenerT(
|
|
|
|
selector: (state) => state.imageEnhancerRequest,
|
|
|
|
listener: (context, imageEnhancerRequest) {
|
|
|
|
if (imageEnhancerRequest.value != null) {
|
|
|
|
Navigator.of(context).pushNamed(ImageEnhancer.routeName,
|
|
|
|
arguments: imageEnhancerRequest.value);
|
|
|
|
}
|
|
|
|
},
|
2023-03-19 10:02:42 +01:00
|
|
|
),
|
2024-10-06 19:15:45 +02:00
|
|
|
_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]);
|
|
|
|
}
|
|
|
|
},
|
2024-05-29 15:55:16 +02:00
|
|
|
),
|
2024-10-06 19:15:45 +02:00
|
|
|
_BlocListenerT(
|
|
|
|
selector: (state) => state.slideshowRequest,
|
|
|
|
listener: _onSlideshowRequest,
|
|
|
|
),
|
|
|
|
_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(),
|
2022-01-21 19:50:33 +01:00
|
|
|
),
|
2021-04-10 06:28:12 +02:00
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-10-06 19:15:45 +02:00
|
|
|
Future<void> _onSlideshowRequest(
|
|
|
|
BuildContext context,
|
|
|
|
Unique<_SlideshowRequest?> slideshowRequest,
|
|
|
|
) async {
|
|
|
|
if (slideshowRequest.value == null) {
|
2022-05-04 10:42:46 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-09-14 23:00:24 +02:00
|
|
|
final result = await showDialog<SlideshowConfig>(
|
|
|
|
context: context,
|
|
|
|
builder: (_) => SlideshowDialog(
|
2024-10-06 19:15:45 +02:00
|
|
|
duration: context.bloc.prefController.slideshowDurationValue,
|
|
|
|
isShuffle: context.bloc.prefController.isSlideshowShuffleValue,
|
|
|
|
isRepeat: context.bloc.prefController.isSlideshowRepeatValue,
|
|
|
|
isReverse: context.bloc.prefController.isSlideshowReverseValue,
|
2021-09-14 23:00:24 +02:00
|
|
|
),
|
|
|
|
);
|
2024-10-06 19:15:45 +02:00
|
|
|
if (!context.mounted || result == null) {
|
2021-08-24 14:56:14 +02:00
|
|
|
return;
|
|
|
|
}
|
2024-10-06 19:15:45 +02:00
|
|
|
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));
|
2024-08-25 17:29:12 +02:00
|
|
|
final newIndex = await Navigator.of(context).pushNamed<int>(
|
|
|
|
SlideshowViewer.routeName,
|
2024-10-06 19:15:45 +02:00
|
|
|
arguments: SlideshowViewerArguments(
|
|
|
|
slideshowRequest.value!.account,
|
|
|
|
slideshowRequest.value!.files,
|
|
|
|
slideshowRequest.value!.startIndex,
|
|
|
|
result,
|
|
|
|
),
|
2021-09-14 23:00:24 +02:00
|
|
|
);
|
2024-10-06 19:15:45 +02:00
|
|
|
_log.info("[_onSlideshowRequest] Slideshow ended, jump to: $newIndex");
|
2024-08-25 17:29:12 +02:00
|
|
|
if (newIndex != null && context.mounted) {
|
2024-10-06 19:15:45 +02:00
|
|
|
context.addEvent(_RequestPage(newIndex));
|
2024-08-25 17:29:12 +02:00
|
|
|
}
|
2021-08-24 14:56:14 +02:00
|
|
|
}
|
2021-04-10 06:28:12 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 19:15:45 +02:00
|
|
|
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>;
|
2024-05-29 15:55:16 +02:00
|
|
|
|
2024-10-06 19:15:45 +02:00
|
|
|
extension on BuildContext {
|
|
|
|
_Bloc get bloc => read<_Bloc>();
|
|
|
|
_State get state => bloc.state;
|
|
|
|
void addEvent(_Event event) => bloc.add(event);
|
2024-05-29 15:55:16 +02:00
|
|
|
}
|