Files
Client/lib/widget/avatar_crop_page.dart
T

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'),
),
);
}
},
),
),
);
}
}