Skip to content

Commit

Permalink
fix: openfoodfacts#1538 - new crop tool (cf. dev mode) (openfoodfacts…
Browse files Browse the repository at this point in the history
…#2872)

New files:
* `crop_grid.dart`: heavily inspired from package `crop_image` - ideally we should put it back there.
* `crop_helper.dart`: Crop Helper - which crop tool do we use, and the method to use it.
* `new_crop_page.dart`: Page dedicated to image cropping. Pops the resulting file path if relevant.
* `rotated_crop_controller.dart`: heavily inspired from package `crop_image` BUT with the rotation feature - ideally we should put it back there.
* `rotated_crop_image.dart`: heavily inspired from package `crop_image` BUT with the rotation feature - ideally we should put it back there.
* `rotation.dart`: 90 degree rotations - ideally we should put it back in package `crop_image`

Impacted files:
* `image_crop_page.dart`: now relying on new class `CropHelper` in order to get the appropriate crop tool (e.g. old or new)
* `pubspec.lock`: wtf
* `pubspec.yaml`: added package `image` for good performances regarding image encoding
* `user_preferences_dev_mode.dart`: added a "Use new crop tool" switch (default is `false`)
  • Loading branch information
monsieurtanuki authored Sep 7, 2022
1 parent b102b42 commit 535cddc
Show file tree
Hide file tree
Showing 10 changed files with 1,135 additions and 36 deletions.
75 changes: 75 additions & 0 deletions packages/smooth_app/lib/pages/crop_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/data_models/user_preferences.dart';
import 'package:smooth_app/pages/preferences/user_preferences_dev_mode.dart';
import 'package:smooth_app/tmp_crop_image/new_crop_page.dart';

/// Crop Helper - which crop tool do we use, and the method to use it.
abstract class CropHelper {
/// Returns the crop tool selected in the dev mode preferences.
static CropHelper getCurrent(final BuildContext context) => context
.read<UserPreferences>()
.getFlag(UserPreferencesDevMode.userPreferencesFlagNewCropTool) ??
false
? _NewCropHelper()
: _OldCropHelper();

/// Returns the path of the image file after the crop operation.
Future<String?> getCroppedPath(
final BuildContext context,
final String inputPath,
);
}

/// New version of the image cropper.
class _NewCropHelper extends CropHelper {
@override
Future<String?> getCroppedPath(
final BuildContext context,
final String inputPath,
) async =>
Navigator.push<String>(
context,
MaterialPageRoute<String>(
builder: (BuildContext context) => CropPage(File(inputPath)),
fullscreenDialog: true,
),
);
}

/// Image cropper based on image_cropper. To be forgotten.
class _OldCropHelper extends CropHelper {
@override
Future<String?> getCroppedPath(
final BuildContext context,
final String inputPath,
) async =>
(await ImageCropper().cropImage(
sourcePath: inputPath,
aspectRatioPresets: <CropAspectRatioPreset>[
CropAspectRatioPreset.square,
CropAspectRatioPreset.ratio3x2,
CropAspectRatioPreset.original,
CropAspectRatioPreset.ratio4x3,
CropAspectRatioPreset.ratio16x9
],
uiSettings: <PlatformUiSettings>[
AndroidUiSettings(
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
toolbarTitle: AppLocalizations.of(context).product_edit_photo_title,
// They all need to be the same for dark/light mode as we can't change
// the background color and the action bar color
statusBarColor: Colors.black,
toolbarWidgetColor: Colors.black,
backgroundColor: Colors.black,
activeControlsWidgetColor: const Color(0xFF85746C),
),
],
))
?.path;
}
40 changes: 5 additions & 35 deletions packages/smooth_app/lib/pages/image_crop_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,21 @@ import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart';

/// Returns the file path of an image after it's been cropped.
///
/// This is the "old" problematic version; to be rapidly changed.
Future<String?> getCroppedPath(
final BuildContext context,
final String inputPath,
) async =>
(await ImageCropper().cropImage(
sourcePath: inputPath,
aspectRatioPresets: <CropAspectRatioPreset>[
CropAspectRatioPreset.square,
CropAspectRatioPreset.ratio3x2,
CropAspectRatioPreset.original,
CropAspectRatioPreset.ratio4x3,
CropAspectRatioPreset.ratio16x9
],
uiSettings: <PlatformUiSettings>[
AndroidUiSettings(
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
toolbarTitle: AppLocalizations.of(context).product_edit_photo_title,
// They all need to be the same for dark/light mode as we can't change
// the background color and the action bar color
statusBarColor: Colors.black,
toolbarWidgetColor: Colors.black,
backgroundColor: Colors.black,
activeControlsWidgetColor: const Color(0xFF85746C),
),
],
))
?.path;
import 'package:smooth_app/pages/crop_helper.dart';

/// Crops an image from an existing file.
Future<File?> startImageCroppingNoPick(
final BuildContext context, {
required final File existingImage,
}) async {
final NavigatorState navigator = Navigator.of(context);
final CropHelper cropHelper = CropHelper.getCurrent(context);
await _showScreenBetween(navigator);

// ignore: use_build_context_synchronously
final String? croppedPath = await getCroppedPath(
final String? croppedPath = await cropHelper.getCroppedPath(
context,
existingImage.path,
);
Expand Down Expand Up @@ -105,6 +74,7 @@ Future<File?> startImageCropping(
}) async {
// Show a loading page on the Flutter side
final NavigatorState navigator = Navigator.of(context);
final CropHelper cropHelper = CropHelper.getCurrent(context);
await _showScreenBetween(navigator);

// ignore: use_build_context_synchronously
Expand All @@ -119,7 +89,7 @@ Future<File?> startImageCropping(
}

// ignore: use_build_context_synchronously
final String? croppedPath = await getCroppedPath(
final String? croppedPath = await cropHelper.getCroppedPath(
context,
pickedXFile.path,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class UserPreferencesDevMode extends AbstractUserPreferences {
static const String userPreferencesTestEnvHost = '__testEnvHost';
static const String userPreferencesFlagAdditionalButton =
'__additionalButtonOnProductPage';
static const String userPreferencesFlagNewCropTool = '__newCropTool';
static const String userPreferencesFlagEditIngredients = '__editIngredients';
static const String userPreferencesEnumScanMode = '__scanMode';
static const String userPreferencesAppLanguageCode = '__appLanguage';
Expand Down Expand Up @@ -371,6 +372,16 @@ class UserPreferencesDevMode extends AbstractUserPreferences {
setState(() {});
},
),
SwitchListTile(
title: const Text('Use new crop tool'),
value:
userPreferences.getFlag(userPreferencesFlagNewCropTool) ?? false,
onChanged: (bool value) async {
await userPreferences.setFlag(
userPreferencesFlagNewCropTool, value);
setState(() {});
},
),
ListTile(
// Do not translate
title: const Text('Reset App Language'),
Expand Down
149 changes: 149 additions & 0 deletions packages/smooth_app/lib/tmp_crop_image/crop_grid.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import 'dart:ui';

import 'package:flutter/material.dart';

class CropGrid extends StatelessWidget {
const CropGrid({
Key? key,
required this.crop,
required this.gridColor,
required this.cornerSize,
required this.thinWidth,
required this.thickWidth,
required this.scrimColor,
required this.alwaysShowThirdLines,
required this.isMoving,
required this.onSize,
}) : super(key: key);

final Rect crop;
final Color gridColor;
final double cornerSize;
final double thinWidth;
final double thickWidth;
final Color scrimColor;
final bool alwaysShowThirdLines;
final bool isMoving;
final ValueChanged<Size> onSize;

@override
Widget build(BuildContext context) => RepaintBoundary(
child: CustomPaint(foregroundPainter: _CropGridPainter(this)),
);
}

class _CropGridPainter extends CustomPainter {
_CropGridPainter(this.grid);

final CropGrid grid;

@override
void paint(Canvas canvas, Size size) {
final Rect full = Offset.zero & size;
final Rect bounds = Rect.fromLTRB(
grid.crop.left * full.width,
grid.crop.top * full.height,
grid.crop.right * full.width,
grid.crop.bottom * full.height,
);
grid.onSize(size);

canvas.save();
canvas.clipRect(bounds, clipOp: ClipOp.difference);
canvas.drawRect(
full,
Paint() //
..color = grid.scrimColor
..style = PaintingStyle.fill
..isAntiAlias = true);
canvas.restore();

canvas.drawPath(
Path()
..addPolygon(<Offset>[
bounds.topLeft.translate(0, grid.cornerSize),
bounds.topLeft,
bounds.topLeft.translate(grid.cornerSize, 0)
], false)
..addPolygon(<Offset>[
bounds.topRight.translate(0, grid.cornerSize),
bounds.topRight,
bounds.topRight.translate(-grid.cornerSize, 0)
], false)
..addPolygon(<Offset>[
bounds.bottomLeft.translate(0, -grid.cornerSize),
bounds.bottomLeft,
bounds.bottomLeft.translate(grid.cornerSize, 0)
], false)
..addPolygon(<Offset>[
bounds.bottomRight.translate(0, -grid.cornerSize),
bounds.bottomRight,
bounds.bottomRight.translate(-grid.cornerSize, 0)
], false),
Paint()
..color = grid.gridColor
..style = PaintingStyle.stroke
..strokeWidth = grid.thickWidth
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.miter
..isAntiAlias = true);

final Path path = Path()
..addPolygon(<Offset>[
bounds.topLeft.translate(grid.cornerSize, 0),
bounds.topRight.translate(-grid.cornerSize, 0)
], false)
..addPolygon(<Offset>[
bounds.bottomLeft.translate(grid.cornerSize, 0),
bounds.bottomRight.translate(-grid.cornerSize, 0)
], false)
..addPolygon(<Offset>[
bounds.topLeft.translate(0, grid.cornerSize),
bounds.bottomLeft.translate(0, -grid.cornerSize)
], false)
..addPolygon(<Offset>[
bounds.topRight.translate(0, grid.cornerSize),
bounds.bottomRight.translate(0, -grid.cornerSize)
], false);

if (grid.isMoving || grid.alwaysShowThirdLines) {
final double thirdHeight = bounds.height / 3.0;
path.addPolygon(<Offset>[
bounds.topLeft.translate(0, thirdHeight),
bounds.topRight.translate(0, thirdHeight)
], false);
path.addPolygon(<Offset>[
bounds.bottomLeft.translate(0, -thirdHeight),
bounds.bottomRight.translate(0, -thirdHeight)
], false);

final double thirdWidth = bounds.width / 3.0;
path.addPolygon(<Offset>[
bounds.topLeft.translate(thirdWidth, 0),
bounds.bottomLeft.translate(thirdWidth, 0)
], false);
path.addPolygon(<Offset>[
bounds.topRight.translate(-thirdWidth, 0),
bounds.bottomRight.translate(-thirdWidth, 0)
], false);
}

canvas.drawPath(
path,
Paint()
..color = grid.gridColor
..style = PaintingStyle.stroke
..strokeWidth = grid.thinWidth
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.miter
..isAntiAlias = true);
}

@override
bool shouldRepaint(_CropGridPainter oldDelegate) =>
oldDelegate.grid.crop != grid.crop || //
oldDelegate.grid.isMoving != grid.isMoving;

@override
bool hitTest(Offset position) => true;
}
Loading

0 comments on commit 535cddc

Please sign in to comment.