203 lines
6.8 KiB
Dart
203 lines
6.8 KiB
Dart
import 'dart:ui';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:moxxyv2/i18n/strings.g.dart';
|
|
import 'package:moxxyv2/ui/bloc/cropbackground_bloc.dart';
|
|
import 'package:moxxyv2/ui/bloc/navigation_bloc.dart';
|
|
import 'package:moxxyv2/ui/constants.dart';
|
|
import 'package:moxxyv2/ui/widgets/backdrop_spinner.dart';
|
|
import 'package:moxxyv2/ui/widgets/cancel_button.dart';
|
|
|
|
class CropBackgroundPage extends StatefulWidget {
|
|
const CropBackgroundPage({super.key});
|
|
|
|
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
|
builder: (context) => const CropBackgroundPage(),
|
|
settings: const RouteSettings(
|
|
name: backgroundCroppingRoute,
|
|
),
|
|
);
|
|
|
|
@override
|
|
CropBackgroundPageState createState() => CropBackgroundPageState();
|
|
}
|
|
|
|
class CropBackgroundPageState extends State<CropBackgroundPage> {
|
|
CropBackgroundPageState() : super();
|
|
double? _scalingFactorCached;
|
|
TransformationController? _controller;
|
|
|
|
double _scalingFactor(BuildContext context, CropBackgroundState state) {
|
|
if (_scalingFactorCached != null) return _scalingFactorCached!;
|
|
|
|
final query = MediaQuery.of(context);
|
|
final width = query.size.width; // * query.devicePixelRatio;
|
|
final height = query.size.height; // * query.devicePixelRatio;
|
|
|
|
final q = height / state.imageHeight;
|
|
final delta = width - state.imageWidth * q;
|
|
var qp = q;
|
|
if (delta > 0) {
|
|
qp = width / state.imageWidth;
|
|
}
|
|
|
|
_scalingFactorCached = qp;
|
|
return qp;
|
|
}
|
|
|
|
Widget _buildImage(BuildContext context, CropBackgroundState state) {
|
|
if (state.image == null) {
|
|
return const Center(
|
|
child: CircularProgressIndicator(),
|
|
);
|
|
}
|
|
|
|
Widget image;
|
|
if (state.blurEnabled) {
|
|
image = ImageFiltered(
|
|
imageFilter: ImageFilter.blur(
|
|
sigmaX: 10,
|
|
sigmaY: 10,
|
|
),
|
|
child: Image.memory(
|
|
state.image!,
|
|
fit: BoxFit.contain,
|
|
),
|
|
);
|
|
} else {
|
|
image = Image.memory(
|
|
state.image!,
|
|
fit: BoxFit.contain,
|
|
);
|
|
}
|
|
|
|
final q = _scalingFactor(context, state);
|
|
_controller ??=
|
|
TransformationController(Matrix4.identity()..scale(q, q, 1));
|
|
return InteractiveViewer(
|
|
constrained: false,
|
|
maxScale: 4,
|
|
minScale: 1,
|
|
panEnabled: !state.isWorking,
|
|
scaleEnabled: !state.isWorking,
|
|
transformationController: _controller,
|
|
child: image,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return BlocBuilder<CropBackgroundBloc, CropBackgroundState>(
|
|
builder: (BuildContext context, CropBackgroundState state) {
|
|
return PopScope(
|
|
canPop: !state.isWorking,
|
|
onPopInvoked: (didPop) {
|
|
if (didPop) {
|
|
context
|
|
.read<CropBackgroundBloc>()
|
|
.add(CropBackgroundResetEvent());
|
|
}
|
|
},
|
|
child: SafeArea(
|
|
child: Stack(
|
|
children: [
|
|
// ignore: prefer_if_elements_to_conditional_expressions
|
|
state.imageHeight != 0 && state.imageWidth != 0
|
|
? _buildImage(context, state)
|
|
: const SizedBox(),
|
|
Positioned(
|
|
top: 8,
|
|
left: 8,
|
|
child: Material(
|
|
color: const Color.fromRGBO(0, 0, 0, 0),
|
|
child: CancelButton(
|
|
onPressed: () {
|
|
context
|
|
.read<CropBackgroundBloc>()
|
|
.add(CropBackgroundResetEvent());
|
|
context.read<NavigationBloc>().add(PoppedRouteEvent());
|
|
},
|
|
),
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 8,
|
|
right: 8,
|
|
child: Material(
|
|
color: const Color.fromRGBO(0, 0, 0, 0),
|
|
child: DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
color: const Color.fromRGBO(0, 0, 0, 0.6),
|
|
borderRadius: BorderRadius.circular(24),
|
|
),
|
|
child: IntrinsicWidth(
|
|
child: Row(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 8),
|
|
child: Text(t.pages.cropbackground.blur),
|
|
),
|
|
Switch(
|
|
value: state.blurEnabled,
|
|
onChanged: (_) {
|
|
if (state.isWorking) return;
|
|
|
|
context
|
|
.read<CropBackgroundBloc>()
|
|
.add(BlurToggledEvent());
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Positioned(
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 8,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
FilledButton(
|
|
onPressed: state.isWorking
|
|
? null
|
|
: () {
|
|
final q = _scalingFactor(context, state);
|
|
final value = _controller == null
|
|
? (Matrix4.identity()..scale(q, q, 1))
|
|
: _controller!.value;
|
|
final translation = value.getTranslation();
|
|
final scale = _controller == null
|
|
? 1.0
|
|
: value.entry(0, 0);
|
|
|
|
context.read<CropBackgroundBloc>().add(
|
|
BackgroundSetEvent(
|
|
translation.x,
|
|
translation.y,
|
|
scale,
|
|
MediaQuery.of(context).size.height,
|
|
MediaQuery.of(context).size.width,
|
|
),
|
|
);
|
|
},
|
|
child: Text(t.pages.cropbackground.setAsBackground),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
BackdropSpinner(
|
|
enabled: state.isWorking,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|