import 'dart:async'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/viewer/view_state.dart'; import 'package:aves/widgets/editor/control_panel.dart'; import 'package:aves/widgets/editor/image.dart'; import 'package:aves/widgets/editor/transform/controller.dart'; import 'package:aves/widgets/editor/transform/cropper.dart'; import 'package:aves/widgets/viewer/overlay/minimap.dart'; import 'package:aves_magnifier/aves_magnifier.dart'; import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class ImageEditorPage extends StatefulWidget { static const routeName = '/image_editor'; final AvesEntry entry; const ImageEditorPage({ super.key, required this.entry, }); @override State createState() => _ImageEditorPageState(); } class _ImageEditorPageState extends State { final Set _subscriptions = {}; final ValueNotifier _actionNotifier = ValueNotifier(null); final ValueNotifier _marginNotifier = ValueNotifier(EdgeInsets.zero); final ValueNotifier _viewStateNotifier = ValueNotifier(ViewState.zero); final AvesMagnifierController _magnifierController = AvesMagnifierController(); late final TransformController _transformController; @override void initState() { super.initState(); _transformController = TransformController(widget.entry.displaySize); _actionNotifier.addListener(_onActionChanged); _subscriptions.add(_transformController.transformationStream.map((v) => v.matrix).distinct().listen(_onTransformationMatrixChanged)); } @override void dispose() { _subscriptions ..forEach((sub) => sub.cancel()) ..clear(); _actionNotifier.dispose(); _marginNotifier.dispose(); _viewStateNotifier.dispose(); _magnifierController.dispose(); _transformController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: MultiProvider( providers: [ Provider.value(value: _magnifierController), Provider.value(value: _transformController), ], child: SafeArea( child: Column( children: [ Expanded( child: Stack( children: [ ClipRect( child: EditorImage( magnifierController: _magnifierController, transformController: _transformController, actionNotifier: _actionNotifier, marginNotifier: _marginNotifier, viewStateNotifier: _viewStateNotifier, entry: widget.entry, ), ), if (settings.showOverlayMinimap) PositionedDirectional( start: 8, bottom: 8, child: Minimap(viewStateNotifier: _viewStateNotifier), ), ValueListenableBuilder( valueListenable: _actionNotifier, builder: (context, action, child) { switch (action) { case EditorAction.transform: return Cropper( magnifierController: _magnifierController, transformController: _transformController, marginNotifier: _marginNotifier, ); case null: return const SizedBox(); } }, ), ], ), ), const Divider(height: 0), EditorControlPanel( entry: widget.entry, actionNotifier: _actionNotifier, ), ], ), ), ), resizeToAvoidBottomInset: false, ); } void _onActionChanged() { switch (_actionNotifier.value) { case EditorAction.transform: _transformController.reset(); _marginNotifier.value = Cropper.imageMargin; default: _marginNotifier.value = EdgeInsets.zero; } } void _onTransformationMatrixChanged(Matrix4 transformationMatrix) { final boundaries = _magnifierController.scaleBoundaries; if (boundaries != null) { _magnifierController.setScaleBoundaries( boundaries.copyWith( externalTransform: transformationMatrix, ), ); } } }