Closed MarcioPereira155 closed 5 months ago
Thank you for your question.
Can you try the example below and give me feedback if it works?
Uint8List? editedBytes;
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ProImageEditor.memory(
widget.uploadedFile.bytes!,
onImageEditingComplete: (Uint8List bytes) async {
editedBytes = bytes;
// I recommend you that you save your image data here
Navigator.pop(context);
},
configs: ProImageEditorConfigs(
// configs...
),
),
),
);
// Save and display the edited image
if (editedBytes != null) {
String? imagePath = await saveImageToDevice(editedBytes);
if (imagePath != null) {
setState(() {
FFAppState().imagemEditadaPath = imagePath;
});
}
}
Thank you for your previous assistance, however, after following the instructions, I continued to experience challenges with the image editing functionality. Allow me to break down the scenario more precisely:
Problem Flow:
When editing the image using the editing widget, I can complete the editing process without apparent errors. Once complete, I select the "Done" option, which causes the image editor to close as expected, indicating the end of editing. I am redirected to the previous screen, which is the expected behavior. However, I encounter a critical problem here: the edited image is not updated in AppState nor is it displayed in the image widget intended for this purpose.
Research and Tests Performed:
I performed a direct test, trying to send the edited image bytes directly to the image widget, with the aim of displaying the edited image. Unfortunately, the edited image bytes do not appear to be transmitted correctly, indicating that the problem may lie in the stage of capturing or transmitting these bytes after editing.
Modified part of the code:
onPressed: () async {
if (widget.uploadedFile.bytes != null) {
// Edita a imagem usando ProImageEditor.memory com os bytes do arquivo enviado
Uint8List? editedBytes;
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ProImageEditor.memory(
widget.uploadedFile.bytes!,
onImageEditingComplete: (Uint8List bytes) async {
editedBytes = bytes;
// Save your image data here as recommended
String? imagePath = await saveImageToDevice(
editedBytes!); // Use the ! operator to assert that editedBytes is not null.
if (imagePath != null) {
setState(() {
FFAppState().imagemEditadaPath =
imagePath; // Update the state with the new image path
});
}
Navigator.pop(
context); // Close the image editor and return to the previous screen
},
Below is the complete code with the changes:
// 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!
import 'package:extended_image/extended_image.dart';
import 'package:pro_image_editor/pro_image_editor.dart';
import 'package:image_editor/image_editor.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:colorfilter_generator/colorfilter_generator.dart';
import 'package:rounded_background_text/rounded_background_text.dart';
import 'package:screenshot/screenshot.dart';
import 'package:vibration/vibration.dart';
import 'dart:async';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart' hide Category;
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:pro_image_editor/widgets/loading_dialog.dart';
import 'package:image/image.dart' as img;
import 'package:http/http.dart';
import 'package:path_provider/path_provider.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:firebase_core/firebase_core.dart';
import 'dart:typed_data';
class MyHomePage extends StatefulWidget {
const MyHomePage({
super.key,
this.width,
this.height,
required this.uploadedFile,
});
final double? width; // Largura opcional para o widget
final double? height; // Altura opcional para o widget
final FFUploadedFile uploadedFile; // O arquivo enviado pelo usuário
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// Define uma variável para armazenar os bytes da imagem a serem exibidos
Uint8List? imageToShow;
// Salva os bytes da imagem editada no dispositivo e retorna o caminho do arquivo
Future<String?> saveImageToDevice(Uint8List imageBytes) async {
try {
// Obtém o diretório de documentos do aplicativo
final directory = await getApplicationDocumentsDirectory();
// Cria um caminho de arquivo único
String fileName =
"edited_image_${DateTime.now().millisecondsSinceEpoch}.png";
File imageFile = File('${directory.path}/$fileName');
// Escreve os bytes da imagem no arquivo
await imageFile.writeAsBytes(imageBytes);
// Retorna o caminho do arquivo onde a imagem foi salva
return imageFile.path;
} catch (e) {
print("Erro ao salvar a imagem no dispositivo: $e");
return null;
}
}
// Local onde você deseja decidir qual imagem mostrar
Future<Uint8List?> getImageToShow() async {
if (FFAppState().imagemEditadaPath.isNotEmpty) {
// Tenta carregar a imagem editada do caminho do arquivo de forma assíncrona
try {
final file = File(FFAppState().imagemEditadaPath);
return await file.readAsBytes();
} catch (e) {
print("Erro ao ler a imagem editada: $e");
return null;
}
} else if (widget.uploadedFile.bytes != null) {
// Retorna os bytes da imagem original enviada
return widget.uploadedFile.bytes;
}
return null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// Botão para iniciar a edição de imagem
OutlinedButton.icon(
onPressed: () async {
if (widget.uploadedFile.bytes != null) {
// Edita a imagem usando ProImageEditor.memory com os bytes do arquivo enviado
Uint8List? editedBytes;
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ProImageEditor.memory(
widget.uploadedFile.bytes!,
onImageEditingComplete: (Uint8List bytes) async {
editedBytes = bytes;
// Save your image data here as recommended
String? imagePath = await saveImageToDevice(
editedBytes!); // Use the ! operator to assert that editedBytes is not null.
if (imagePath != null) {
setState(() {
FFAppState().imagemEditadaPath =
imagePath; // Update the state with the new image path
});
}
Navigator.pop(
context); // Close the image editor and return to the previous screen
},
configs: ProImageEditorConfigs(
// Configurações para o ProImageEditor
// Inclui localização, personalizações de UI e alternâncias de recursos
// Consulte a documentação do ProImageEditor para detalhes
i18n: const I18n(
various: I18nVarious(
loadingDialogMsg: 'Por favor, aguarde...',
closeEditorWarningTitle:
'Fechar o Editor de Imagens?',
closeEditorWarningMessage:
'Tem certeza de que deseja fechar o Editor de imagens? Suas alterações não serão salvas.',
closeEditorWarningConfirmBtn: 'Ok',
closeEditorWarningCancelBtn: 'Cancelar',
),
paintEditor: I18nPaintingEditor(
bottomNavigationBarText: 'Desenhar',
freestyle: 'Livre',
arrow: 'Seta',
line: 'Linha',
rectangle: 'Retângulo',
circle: 'Círculo',
dashLine: 'Linha Tracejada',
lineWidth: 'Espessura da linha',
toggleFill: 'Alternar Preenchimento',
undo: 'Desfazer',
redo: 'Refazer',
done: 'Feito',
back: 'Voltar',
smallScreenMoreTooltip: 'Mais',
),
textEditor: I18nTextEditor(
inputHintText: 'Insira o texto',
bottomNavigationBarText: 'Texto',
back: 'Voltar',
done: 'Feito',
textAlign: 'Alinhar texto',
backgroundMode: 'Modo de fundo',
smallScreenMoreTooltip: 'Mais',
),
cropRotateEditor: I18nCropRotateEditor(
bottomNavigationBarText: 'Cortar/Girar',
rotate: 'Girar',
ratio: 'Recortes',
back: 'Voltar',
done: 'Feito',
prepareImageDialogMsg: 'Por favor, aguarde...',
applyChangesDialogMsg: 'Por favor, aguarde...',
smallScreenMoreTooltip: 'Mais',
),
cancel: 'Cancelar',
undo: 'Desfazer',
redo: 'Refazer',
done: 'Feito',
remove: 'Remover',
doneLoadingMsg:
'As alterações estão sendo aplicadas',
),
helperLines: const HelperLines(
showVerticalLine: true,
showHorizontalLine: true,
showRotateLine: true,
hitVibration: true,
),
customWidgets: const ImageEditorCustomWidgets(),
imageEditorTheme: const ImageEditorTheme(
layerHoverCursor: SystemMouseCursors.move,
helperLine: HelperLineTheme(
horizontalColor: Color(0XFF4C68FC),
verticalColor: Color(0XFF4C68FC),
rotateColor: Color(0xFFE91E63),
),
paintingEditor: PaintingEditorTheme(
appBarBackgroundColor: Color(0xFF000000),
lineWidthBottomSheetColor: Color(0xFF252728),
appBarForegroundColor: Color(0xFFE1E1E1),
background: Color.fromARGB(255, 22, 22, 22),
bottomBarColor: Color(0xFF000000),
bottomBarActiveItemColor: Color(0XFF4C68FC),
bottomBarInactiveItemColor: Color(0xFFEEEEEE),
),
textEditor: TextEditorTheme(
appBarBackgroundColor: Color(0xFF000000),
appBarForegroundColor: Color(0xFFE1E1E1),
background: Color.fromARGB(155, 0, 0, 0),
inputHintColor: Color(0xFFBDBDBD),
inputCursorColor: Color(0XFF4C68FC),
),
cropRotateEditor: CropRotateEditorTheme(
appBarBackgroundColor: Color(0xFF000000),
appBarForegroundColor: Color(0xFFE1E1E1),
background: Color.fromARGB(255, 22, 22, 22),
cropCornerColor: Color(0XFF4C68FC),
),
filterEditor: FilterEditorTheme(
appBarBackgroundColor: Color(0xFF000000),
appBarForegroundColor: Color(0xFFE1E1E1),
previewTextColor: Color(0xFFE1E1E1),
background: Color.fromARGB(255, 22, 22, 22),
),
blurEditor: BlurEditorTheme(
appBarBackgroundColor: Color(0xFF000000),
appBarForegroundColor: Color(0xFFE1E1E1),
background: Color.fromARGB(255, 22, 22, 22),
),
emojiEditor: EmojiEditorTheme(),
stickerEditor: StickerEditorTheme(),
background: Color.fromARGB(255, 22, 22, 22),
loadingDialogTheme: LoadingDialogTheme(
textColor: Color(0xFFE1E1E1),
),
uiOverlayStyle: SystemUiOverlayStyle(
statusBarColor: Color(0x42000000),
statusBarIconBrightness: Brightness.light,
systemNavigationBarIconBrightness:
Brightness.light,
statusBarBrightness: Brightness.dark,
systemNavigationBarColor: Color(0xFF000000),
),
),
icons: const ImageEditorIcons(
paintingEditor: IconsPaintingEditor(
bottomNavBar: Icons.edit_rounded,
lineWeight: Icons.line_weight_rounded,
freeStyle: Icons.edit,
arrow: Icons.arrow_right_alt_outlined,
line: Icons.horizontal_rule,
fill: Icons.format_color_fill,
noFill: Icons.format_color_reset,
rectangle: Icons.crop_free,
circle: Icons.lens_outlined,
dashLine: Icons.power_input,
),
textEditor: IconsTextEditor(
bottomNavBar: Icons.text_fields,
alignLeft: Icons.align_horizontal_left_rounded,
alignCenter:
Icons.align_horizontal_center_rounded,
alignRight: Icons.align_horizontal_right_rounded,
backgroundMode: Icons.layers_rounded,
),
cropRotateEditor: IconsCropRotateEditor(
bottomNavBar: Icons.crop_rotate_rounded,
rotate: Icons.rotate_90_degrees_ccw_outlined,
aspectRatio: Icons.crop,
),
filterEditor: IconsFilterEditor(
bottomNavBar: Icons.filter,
),
emojiEditor: IconsEmojiEditor(
bottomNavBar:
Icons.sentiment_satisfied_alt_rounded,
),
stickerEditor: IconsStickerEditor(
bottomNavBar: Icons.layers_outlined,
),
closeEditor: Icons.clear,
doneIcon: Icons.done,
applyChanges: Icons.done,
backButton: Icons.arrow_back,
undoAction: Icons.undo,
redoAction: Icons.redo,
removeElementZone: Icons.delete_outline_rounded,
),
paintEditorConfigs: const PaintEditorConfigs(
enabled: true,
hasOptionFreeStyle: true,
hasOptionArrow: true,
hasOptionLine: true,
hasOptionRect: true,
hasOptionCircle: true,
hasOptionDashLine: true,
canToggleFill: true,
canChangeLineWidth: true,
initialFill: false,
showColorPicker: true,
freeStyleHighPerformanceScaling: true,
initialStrokeWidth: 10.0,
initialColor: Color(0xffff0000),
initialPaintMode: PaintModeE.freeStyle,
),
textEditorConfigs: const TextEditorConfigs(
enabled: true,
canToggleTextAlign: true,
canToggleBackgroundMode: true,
initFontSize: 24.0,
initialTextAlign: TextAlign.center,
initialBackgroundColorMode:
LayerBackgroundColorModeE.backgroundAndColor,
),
cropRotateEditorConfigs: CropRotateEditorConfigs(
initAspectRatio: 3.0 /
4.0, // Exemplo de como definir um valor inicial
aspectRatios: const [
AspectRatioItem(
text: 'Personalizado', value: null),
AspectRatioItem(
text: 'Original',
value:
0.0), // Supondo que 0.0 representa a proporção original
AspectRatioItem(text: '1x1', value: 1.0 / 1.0),
AspectRatioItem(text: '4x3', value: 4.0 / 3.0),
AspectRatioItem(text: '3x4', value: 3.0 / 4.0),
AspectRatioItem(text: '16x9', value: 16.0 / 9.0),
AspectRatioItem(text: '9x16', value: 9.0 / 16.0),
],
enabled: true,
canRotate: true,
canChangeAspectRatio: true,
),
filterEditorConfigs:
FilterEditorConfigs(enabled: false),
blurEditorConfigs: const BlurEditorConfigs(
enabled: false,
),
emojiEditorConfigs: const EmojiEditorConfigs(
enabled: false,
),
designMode: ImageEditorDesignModeE.material,
heroTag: 'hero',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue.shade800,
brightness: Brightness.dark,
),
),
),
),
),
);
}
},
icon: const Icon(Icons.edit), // Ícone para o botão de edição
label: const Text('Editar imagem'), // Rótulo do botão
// Personalizando a aparência do botão
style: OutlinedButton.styleFrom(
side: BorderSide(
color: Color(0XFF4C68FC),
width: 1), // Cor e espessura da borda
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), // Raio da borda
),
foregroundColor: Color(0XFF4C68FC), // Cor do texto e ícone
),
),
const SizedBox(height: 15), // Espaçamento entre widgets
// Usa um FutureBuilder para esperar pela determinação assíncrona da imagem a mostrar
Flexible(
child: FutureBuilder<Uint8List?>(
future: getImageToShow(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
// A imagem é exibida dentro de um SizedBox para definir suas dimensões máximas.
// O widget de imagem em si irá se adaptar para manter sua proporção dentro deste espaço.
return SizedBox(
width: double.infinity, // Usa toda a largura disponível
height: 500, // Altura máxima
child: Image.memory(
snapshot.data!,
fit: BoxFit
.contain, // Mantém a proporção da imagem sem cortá-la
),
);
} else {
// Exibe um placeholder ou um widget de carregamento enquanto a imagem está sendo preparada
return SizedBox(
width: double.infinity,
height: 500,
child: Center(child: CircularProgressIndicator()),
);
}
},
),
),
],
),
),
);
}
}
I see that the problem is no longer from the ImageEditor. I think the problem is from the FutureBuilder that it has already finished so when you update the image it is not called again to read it. I recommend using a global variable to make sure it reads the new image as below:
Uint8List? newImageData;
// Salva os bytes da imagem editada no dispositivo e retorna o caminho do arquivo
Future<String?> saveImageToDevice(Uint8List imageBytes) async {
try {
newImageData = imageBytes;
// Obtém o diretório de documentos do aplicativo
final directory = await getApplicationDocumentsDirectory();
// Cria um caminho de arquivo único
String fileName =
"edited_image_${DateTime.now().millisecondsSinceEpoch}.png";
File imageFile = File('${directory.path}/$fileName');
// Escreve os bytes da imagem no arquivo
await imageFile.writeAsBytes(imageBytes);
// Retorna o caminho do arquivo onde a imagem foi salva
return imageFile.path;
} catch (e) {
print("Erro ao salvar a imagem no dispositivo: $e");
return null;
}
}
and
return SizedBox(
width: double.infinity, // Usa toda a largura disponível
height: 500, // Altura máxima
child: Image.memory(
newImageData ?? snapshot.data!,
fit: BoxFit
.contain, // Mantém a proporção da imagem sem cortá-la
),
);
I'm working on a FlutterFlow project that utilizes ProImageEditor to allow users to edit images within the app. I've encountered an issue where the edited image is not being returned correctly after the editing process.
Here is an overview of what I'm attempting to do:
The user selects an image to edit, which is then passed to ProImageEditor for editing. After editing, I expect the edited image bytes to be returned so I can save them on the user's device and display them in the app. The relevant code for this process is:
The issue is that the bytes of the edited image do not seem to be returned after editing, resulting in an inability to save and display the edited image. The onImageEditingComplete method does not seem to be functioning as expected, or perhaps I am missing a step in the process.
Full code: