Working Image Crop and save back to Firebase Storage using crop_your_image package

Here is the best solution I have found to crop uploaded image and save back to Firebase storage, only using the FF interface, without needing to create a separate code branch. Please note, this example works, but is still rough. FF gives me random compile errors, but it works. Feel free to add to it. See more at end of this post. Dependency: https://pub.dev/packages/crop_your_image Steps: 1.  On a page create a button or icon with the Upload Media to Firebase Action. 2. Create a Local/App State variable and name it croppedImage type ImagePath:    [Screenshot 2023-03-28 at 5.49.19 AM.png] 3. Create a Custom Widget name it PhotoCropUI add the parameters and dependency as shown below in screen shot: [Screenshot 2023-03-28 at 5.51.21 AM.png]    Add the below code to the widget


// Automatic FlutterFlow imports
import '/backend/backend.dart';
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/widgets/index.dart'; // Imports other custom widgets
import '/custom_code/actions/index.dart'; // Imports custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!

//https://github.com/chooyan-eng/crop_your_image
import '/flutter_flow/flutter_flow_widgets.dart';
import 'package:crop_your_image/crop_your_image.dart';
import 'package:google_fonts/google_fonts.dart';
import '/auth/auth_util.dart';
import '/backend/firebase_storage/storage.dart';

class PhotoCropUI extends StatefulWidget {
  const PhotoCropUI({
    Key? key,
    this.width,
    this.height,
    this.imageFile,
    this.top,
    this.right,
    this.bottom,
    this.left,
    this.callBackAction,
  }) : super(key: key);

  final double? width;
  final double? height;
  final FFUploadedFile? imageFile;
  final double? top;
  final double? right;
  final double? bottom;
  final double? left;
  final Future Function()? callBackAction;
  @override
  _PhotoCropUIState createState() => _PhotoCropUIState();
}

class _PhotoCropUIState extends State {
  bool loading = false;
  final _crop_controller = CropController();
  @override
  Widget build(BuildContext context) {
    return Column(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Container(
              width: widget.width ?? double.infinity,
              height: widget.height ?? 555,
              child: Center(
                  child: Crop(
                image: Uint8List.fromList(widget.imageFile!.bytes!),
                controller: _crop_controller,
                onCropped: (image) async {
                  // do something with image data
                  // FFAppState().croppedImage = '';
                  //Future downloadUrls;

                  final path = _getStoragePath(
                      _firebasePathPrefix(), widget.imageFile!.name!, false, 0);
                  uploadData(path, image).then((value) {
                    FFAppState().croppedImage = value!;
                    print('image cropped');
                    widget.callBackAction!.call();
                    loading = false;
                  });
                  // add error handling here
                },

                aspectRatio: 1 / 1,
                // initialSize: 0.5,
                // initialArea: Rect.fromLTWH(240, 212, 800, 600),\
                //initialAreaBuilder: (rect) => Rect.fromLTRB(rect.left + 80, rect.top + 80, rect.right - 80, rect.bottom - 80),
                withCircleUi: true,
                baseColor: Color.fromARGB(255, 0, 3, 22),
                maskColor: Colors.white.withAlpha(100),
                radius: 20,
                onMoved: (newRect) {
                  // do something with current cropping area.
                },
                onStatusChanged: (status) {
                  // do something with current CropStatus
                },
                cornerDotBuilder: (size, edgeAlignment) =>
                    const DotControl(color: Color.fromARGB(255, 206, 18, 56)),
                interactive: true,
                // fixArea: true,
              ))),
          Padding(
            padding: EdgeInsetsDirectional.fromSTEB(8, 5, 8, 5),
            child: FFButtonWidget(
              onPressed: () async {
                if (!loading) {
                  loading = true;
                  print('Button pressed ...');
                  await Future.delayed(const Duration(milliseconds: 1555), () {
                    _crop_controller.crop();
                  });
                  //widget.loading = true;
                }
              },
              showLoadingIndicator: true,
              text: 'Crop',
              options: FFButtonOptions(
                width: 250,
                height: 50,
                padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0),
                iconPadding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0),
                color: FlutterFlowTheme.of(context).primaryColor,
                textStyle: FlutterFlowTheme.of(context).subtitle2.override(
                      fontFamily: 'Lexend',
                      color: Colors.white,
                      fontSize: 16,
                      fontWeight: FontWeight.normal,
                      useGoogleFonts: GoogleFonts.asMap().containsKey(
                          FlutterFlowTheme.of(context).subtitle2Family),
                    ),
                borderSide: BorderSide(
                  color: Colors.transparent,
                  width: 0,
                ),
                borderRadius: BorderRadius.circular(100),
              ),
            ),
          ),
        ]);
  }

  String _getStoragePath(
    String? pathPrefix,
    String filePath,
    bool isVideo, [
    int? index,
  ]) {
    pathPrefix ??= _firebasePathPrefix();
    pathPrefix = _removeTrailingSlash(pathPrefix);
    final timestamp = DateTime.now().microsecondsSinceEpoch;
    final prefix = 'cropped-';
    // Workaround fixed by https://github.com/flutter/plugins/pull/3685
    // (not yet in stable).
    final ext = isVideo ? 'mp4' : filePath.split('.').last;
    final indexStr = index != null ? '_$index' : '';
    return '$pathPrefix/$prefix$timestamp$indexStr.$ext';
  }

  String? _removeTrailingSlash(String? path) =>
      path != null && path.endsWith('/')
          ? path.substring(0, path.length - 1)
          : path;

  String _firebasePathPrefix() => 'users/$currentUserUid/uploads';
}

From here it is up to you, I did the following:   Created a Bottom Sheet define a parameter on the bottom sheet - uploadedFile Type Uploaded File (bytes). [Screenshot 2023-03-28 at 6.06.27 AM.png] On the page you created in STEP 1 Upload Button's action add Open Bottom Sheet action after the Upload Media to Firebase Action. Set the uploadedFile parameter to Uploaded Local File    [Screenshot 2023-03-28 at 6.03.23 AM.png]Add the the PhotoCropUI Widget you just created to the bottom sheet: [Screenshot 2023-03-28 at 5.58.59 AM.png]Set the ImageFile parameter of the PhotoCropUI widget to the Bottom Sheet defined parameter uploadedFile   The widget will save the new Firebase Storage URL to the app/local state variable croppedImage you created in STEP2 Lastly, use the widget parameter callBackAction (as show in screenshot above) to save the image URL back to a Firestore record. [Screenshot 2023-03-28 at 6.17.12 AM.png]Set your Firebase imagePath field to the croppedImage local/app state variable. [Screenshot 2023-03-28 at 6.33.32 AM.png]I also add a condition to check if croppedImage is "Is set and not empty" before saving. I plan on making the storagePath a parameter, this example saves the cropped file to the user's storage path using code from the FF SelectedMedia class found here:


/lib/flutter_flow/upload_data.dart 

Also, I added a FF button and it is using styling from my app. These should be parameters on the custom widget. For now you can change the style of the button in the widget code above, here is an excerpt: options:

FFButtonOptions(
                height: 50,
                padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0),
                iconPadding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0),
                color: FlutterFlowTheme.of(context).accentGreen,
                textStyle: FlutterFlowTheme.of(context).subtitle2.override(
                      fontFamily: 'Lexend',
                      color: Colors.white,
                      fontSize: 16,
                      fontWeight: FontWeight.normal,
                      useGoogleFonts: GoogleFonts.asMap().containsKey(
                          FlutterFlowTheme.of(context).subtitle2Family),
                    ),
                borderSide: BorderSide(
                  color: Colors.transparent,
                  width: 0,
                ),
                borderRadius: BorderRadius.circular(100),
              ),

In addition, I need to add parameters for the crop_your_image package options, if you need to change these in the meantime edit them in the custom widget code:


withCircleUi: true
aspectRatio: 4 / 3,
baseColor: Colors.blue.shade900,
maskColor: Colors.white.withAlpha(100),
radius: 20,

NOTE: On locking the aspect ratio of the crop. Comment out this option if you want to lock the aspectRatio, it took me a while to figure this out.  Rect.fromLTRB(rect.left + 80, rect.top + 80, rect.right - 80, rect.bottom - 80), You can see all parameters here:  https://pub.dev/packages/crop_your_image

2
2 replies