96 lines
3.0 KiB
Dart
96 lines
3.0 KiB
Dart
import 'dart:typed_data';
|
|
|
|
import 'package:crop_your_image/crop_your_image.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'app_progress_indicator.dart';
|
|
|
|
/// Full-screen cropper. Pure-Flutter so it inherits the app theme and
|
|
/// MediaQuery insets (no UCrop / native Activity needed). Returns the
|
|
/// cropped JPEG/PNG bytes via Navigator pop, or `null` on cancel.
|
|
///
|
|
/// Defaults to a 1:1 crop (avatar use). Pass [aspectRatio] to override, or
|
|
/// `null` for a free-form crop (e.g. chat backgrounds).
|
|
class AvatarCropPage extends StatefulWidget {
|
|
final Uint8List imageBytes;
|
|
final double? aspectRatio;
|
|
const AvatarCropPage({
|
|
required this.imageBytes,
|
|
this.aspectRatio = 1.0,
|
|
super.key,
|
|
});
|
|
|
|
@override
|
|
State<AvatarCropPage> createState() => _AvatarCropPageState();
|
|
}
|
|
|
|
class _AvatarCropPageState extends State<AvatarCropPage> {
|
|
final _controller = CropController();
|
|
bool _busy = false;
|
|
|
|
void _confirm() {
|
|
if (_busy) return;
|
|
setState(() => _busy = true);
|
|
_controller.crop();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
return Scaffold(
|
|
backgroundColor: theme.colorScheme.surface,
|
|
appBar: AppBar(
|
|
title: const Text('Zuschneiden'),
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.close),
|
|
tooltip: 'Abbrechen',
|
|
onPressed: _busy ? null : () => Navigator.of(context).pop(),
|
|
),
|
|
actions: [
|
|
IconButton(
|
|
icon: _busy
|
|
? Padding(
|
|
padding: const EdgeInsets.all(10),
|
|
child: AppProgressIndicator.small(
|
|
color: theme.colorScheme.onSurface,
|
|
),
|
|
)
|
|
: const Icon(Icons.check),
|
|
tooltip: 'Bestätigen',
|
|
onPressed: _busy ? null : _confirm,
|
|
),
|
|
],
|
|
),
|
|
body: SafeArea(
|
|
// Pinch-Zoom (interactive: true) lässt zwei-Finger-Gesten direkt am
|
|
// Bildschirmrand starten und triggert dann die Android-Zurückgeste.
|
|
// Crop-Rahmen mit Eck-Dots reicht für Avatar-Auswahl völlig aus.
|
|
child: Crop(
|
|
image: widget.imageBytes,
|
|
controller: _controller,
|
|
aspectRatio: widget.aspectRatio,
|
|
interactive: false,
|
|
baseColor: theme.colorScheme.surface,
|
|
maskColor: Colors.black.withValues(alpha: 0.6),
|
|
cornerDotBuilder: (size, _) =>
|
|
DotControl(color: theme.colorScheme.primary),
|
|
onCropped: (result) {
|
|
if (!mounted) return;
|
|
switch (result) {
|
|
case CropSuccess(:final croppedImage):
|
|
Navigator.of(context).pop(croppedImage);
|
|
case CropFailure():
|
|
setState(() => _busy = false);
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Bild konnte nicht zugeschnitten werden'),
|
|
),
|
|
);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|