I have list of images of answer sheet in server.I need to display that images and need to select some particular area of that image, and save only that area as image. I done displaying and selection part but i don't know how to save only that selected area as image. please help me as soon as possible.
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:io';
import 'package:http/http.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
primaryColor: Colors.red,
scaffoldBackgroundColor: Colors.white,
canvasColor: Colors.transparent),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
List<AnswerSheet> answerSheets = [
AnswerSheet(
imageUrl:
'https://d18x2uyjeekruj.cloudfront.net/wp-content/uploads/2019/04/Scan12.jpg',
downloadedImagePath: '',
selectedOffsets: List()),
AnswerSheet(
imageUrl:
'https://d18x2uyjeekruj.cloudfront.net/wp-content/uploads/2019/04/Scan12.jpg',
downloadedImagePath: '',
selectedOffsets: List())
];
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomInset: false,
body: GestureDetector(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) =>
EntryItem(answerSheets[index]),
itemCount: answerSheets.length,
),
));
}
}
class EntryItem extends StatefulWidget {
final AnswerSheet answerSheet;
EntryItem(this.answerSheet);
#override
_EntryItemState createState() => _EntryItemState();
}
class _EntryItemState extends State<EntryItem> {
final GlobalKey _myCanvasKey = new GlobalKey();
MyCustomPainter _customPainter;
Offset _startOffset;
bool isLoading = true;
ui.Image _image;
bool _isImageEditable = true;
BuildContext context;
#override
void initState() {
super.initState();
_downloadImage();
}
#override
Widget build(BuildContext context) {
this.context = context;
if (isLoading) {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Center(child: CircularProgressIndicator()));
} else {
return GestureDetector(
onHorizontalDragStart: _onHorizontalDragStart,
onHorizontalDragUpdate: _onHorizontalDragUpdate,
onHorizontalDragEnd: _onHorizontalDragEnd,
child: Container(
margin: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: Color(0xFFF4F4F4),
borderRadius: BorderRadius.circular(10.0)),
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: CustomPaint(
key: _myCanvasKey,
size: MediaQuery.of(context).size,
painter: _customPainter,
),
),
));
}
}
Future downloadAndSaveImage(AnswerSheet answerSheet) async {
print("downloading....");
var response = await get(answerSheet.imageUrl);
var documentDirectory = await getApplicationDocumentsDirectory();
File file = File(join(documentDirectory.path, '${DateTime.now()}.jpg'));
file.writeAsBytesSync(response.bodyBytes);
answerSheet.downloadedImagePath = file.path;
print(file.path);
return response.bodyBytes;
}
Future<Null> _downloadImage() async {
List<int> img = await downloadAndSaveImage(widget.answerSheet);
_image = await _loadImage(img);
setState(() {
_customPainter =
MyCustomPainter(answerSheet: widget.answerSheet, image: _image);
isLoading = false;
});
}
Future<ui.Image> _loadImage(List<int> img) async {
final Completer<ui.Image> completer = Completer();
ui.decodeImageFromList(img, (ui.Image img) {
return completer.complete(img);
});
return completer.future;
}
Rect getRect(Offset offset) {
return Rect.fromPoints(offset, Offset(offset.dx + 30, offset.dy + 30));
}
_onHorizontalDragStart(DragStartDetails detailData) {
if (_isImageEditable) {
_startOffset = detailData.globalPosition;
widget.answerSheet.selectedOffsets.add(_startOffset);
}
}
_onHorizontalDragUpdate(DragUpdateDetails detailData) {
if (_isImageEditable) {
widget.answerSheet.selectedOffsets
.add(Offset(detailData.localPosition.dx, _startOffset.dy));
_myCanvasKey.currentContext.findRenderObject().markNeedsPaint();
}
}
_onHorizontalDragEnd(DragEndDetails details) {
print('_onHorizontalDragEnd');
if (_isImageEditable) {
_myCanvasKey.currentContext.findRenderObject().markNeedsPaint();
_startOffset = null;
}
}
}
class MyCustomPainter extends CustomPainter {
final AnswerSheet answerSheet;
final ui.Image image;
MyCustomPainter({this.answerSheet, this.image});
Paint _paint = new Paint()
..color = Colors.deepOrange.withOpacity(0.3)
..style = PaintingStyle.stroke;
Offset _prvOffset;
#override
void paint(Canvas canvas, Size size) {
_paint.strokeWidth = 40;
Rect myRect = Offset(0.0, 0.0) & Size(size.width, size.height);
drawImage(myRect, size, canvas, new Paint(), BoxFit.fill);
if (answerSheet.selectedOffsets.length != 0) {
for (Offset currOffset in answerSheet.selectedOffsets) {
if (_prvOffset != null && currOffset != null)
canvas.drawLine(_prvOffset, currOffset, _paint);
_prvOffset = currOffset;
}
_prvOffset = null;
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
Paint getPaint(Color color) {
return new Paint()
..isAntiAlias = true
..strokeWidth = 1.0
..color = color
..style = PaintingStyle.fill;
}
void drawImage(
Rect outputRect, Size size, Canvas canvas, Paint paint, BoxFit fit) {
final Size imageSize =
Size(image.width.toDouble(), image.height.toDouble());
final FittedSizes sizes = applyBoxFit(fit, imageSize, outputRect.size);
final Rect inputSubrect =
Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
final Rect outputSubrect =
Alignment.center.inscribe(sizes.destination, outputRect);
canvas.drawImageRect(image, inputSubrect, outputSubrect, paint);
}
}
class AnswerSheet {
String imageUrl;
String downloadedImagePath;
List<Offset> selectedOffsets;
AnswerSheet({this.imageUrl, this.downloadedImagePath, this.selectedOffsets});
}
screenshot
A simple plugin to capture widgets as Images.
This plugin wraps your widgets inside RenderRepaintBoundary
This handy plugin can be used to capture any Widget including full screen screenshots & individual widgets like Text().
Ok, you need to use image_cropper or crop packages. These are helpful to you. Look at the below example:
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ImageCropper',
theme: ThemeData.light().copyWith(primaryColor: Colors.deepOrange),
home: MyHomePage(
title: 'ImageCropper',
),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
MyHomePage({this.title});
#override
_MyHomePageState createState() => _MyHomePageState();
}
enum AppState {
free,
picked,
cropped,
}
class _MyHomePageState extends State<MyHomePage> {
AppState state;
File imageFile;
#override
void initState() {
super.initState();
state = AppState.free;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: imageFile != null ? Image.file(imageFile) : Container(),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.deepOrange,
onPressed: () {
if (state == AppState.free)
_pickImage();
else if (state == AppState.picked)
_cropImage();
else if (state == AppState.cropped) _clearImage();
},
child: _buildButtonIcon(),
),
);
}
Widget _buildButtonIcon() {
if (state == AppState.free)
return Icon(Icons.add);
else if (state == AppState.picked)
return Icon(Icons.crop);
else if (state == AppState.cropped)
return Icon(Icons.clear);
else
return Container();
}
Future<Null> _pickImage() async {
imageFile = await ImagePicker.pickImage(source: ImageSource.gallery);
if (imageFile != null) {
setState(() {
state = AppState.picked;
});
}
}
Future<Null> _cropImage() async {
File croppedFile = await ImageCropper.cropImage(
sourcePath: imageFile.path,
aspectRatioPresets: Platform.isAndroid
? [
CropAspectRatioPreset.square,
CropAspectRatioPreset.ratio3x2,
CropAspectRatioPreset.original,
CropAspectRatioPreset.ratio4x3,
CropAspectRatioPreset.ratio16x9
]
: [
CropAspectRatioPreset.original,
CropAspectRatioPreset.square,
CropAspectRatioPreset.ratio3x2,
CropAspectRatioPreset.ratio4x3,
CropAspectRatioPreset.ratio5x3,
CropAspectRatioPreset.ratio5x4,
CropAspectRatioPreset.ratio7x5,
CropAspectRatioPreset.ratio16x9
],
androidUiSettings: AndroidUiSettings(
toolbarTitle: 'Cropper',
toolbarColor: Colors.deepOrange,
toolbarWidgetColor: Colors.white,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false),
iosUiSettings: IOSUiSettings(
title: 'Cropper',
));
if (croppedFile != null) {
imageFile = croppedFile;
setState(() {
state = AppState.cropped;
});
}
}
void _clearImage() {
imageFile = null;
setState(() {
state = AppState.free;
});
}
}
Related
Everything works normally, but when I click to load an image from the gallery or from the camera, it does not load the image
The message in the VSC Debug Console is:
D/MediaScannerConnection( 5069): Scanned /data/user/0/com.lot.sig/cache/ce7735c2-8d36-4633-996f-bfae03424df26527509175807023631.jpg to null
This is my user_profile.dart:
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
class UserProfile extends StatefulWidget {
UserProfile();
#override
_UserProfile createState() => _UserProfile();
}
enum AppState {
free,
picked,
cropped,
}
class _UserProfile extends State<UserProfile> {
AppState? state;
File? imageFile;
#override
void initState() {
super.initState();
state = AppState.free;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black87,
title: Text("Hola"),
),
body: Center(
child: imageFile != null ? Image.file(imageFile!) : Container(),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.black87,
onPressed: () {
if (state == AppState.free)
_pickImage();
else if (state == AppState.picked)
_cropImage();
else if (state == AppState.cropped) _clearImage();
},
child: _buildButtonIcon(),
),
);
}
Widget _buildButtonIcon() {
if (state == AppState.free)
return Icon(Icons.add);
else if (state == AppState.picked)
return Icon(Icons.crop);
else if (state == AppState.cropped)
return Icon(Icons.clear);
else
return Container();
}
Future<Null> _pickImage() async {
final _imageFile = await ImagePicker().pickImage(source: ImageSource.gallery);
if (_imageFile != null) {
imageFile = File(_imageFile.path);
setState(() {
state = AppState.picked;
});
}
}
Future<Null> _cropImage() async {
File? croppedFile = await ImageCropper.cropImage(
sourcePath: imageFile!.path,
aspectRatioPresets: Platform.isAndroid
? [
CropAspectRatioPreset.square,
CropAspectRatioPreset.ratio3x2,
CropAspectRatioPreset.original,
CropAspectRatioPreset.ratio4x3,
CropAspectRatioPreset.ratio16x9
]
: [
CropAspectRatioPreset.original,
CropAspectRatioPreset.square,
CropAspectRatioPreset.ratio3x2,
CropAspectRatioPreset.ratio4x3,
CropAspectRatioPreset.ratio5x3,
CropAspectRatioPreset.ratio5x4,
CropAspectRatioPreset.ratio7x5,
CropAspectRatioPreset.ratio16x9
],
androidUiSettings: AndroidUiSettings(
toolbarTitle: 'Cropper',
toolbarColor: Colors.deepOrange,
toolbarWidgetColor: Colors.white,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false),
iosUiSettings: IOSUiSettings(
title: 'Cropper',
));
if (croppedFile != null) {
imageFile = croppedFile;
setState(() {
state = AppState.cropped;
});
}
}
void _clearImage() {
imageFile = null;
setState(() {
state = AppState.free;
});
}
}
And this is my image_picker_handler.dart :
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'package:sig/utilitary/image_picker_dialog.dart';
class ImagePickerHandler {
late ImagePickerDialog imagePicker;
AnimationController? _controller;
ImagePickerListener _listener;
ImagePickerHandler(this._listener, this._controller);
final ImagePicker _picker = ImagePicker();
openCamera() async {
imagePicker.dismissDialog();
var image = await (_picker.pickImage(source: ImageSource.camera) as FutureOr<XFile>);
cropImage(image);
}
openGallery() async {
imagePicker.dismissDialog();
var image = await (_picker.pickImage(source: ImageSource.gallery) as FutureOr<XFile>);
cropImage(image);
}
void init() {
imagePicker = new ImagePickerDialog(this, _controller);
imagePicker.initState();
}
Future cropImage(XFile image) async {
File? croppedFile = await ImageCropper.cropImage(
sourcePath: image.path,
maxWidth: 512,
maxHeight: 512,
);
_listener.userImage(croppedFile);
}
showDialog(BuildContext context) {
imagePicker.getImage(context);
}
}
abstract class ImagePickerListener {
userImage(File? _image);
}
I want to implement that after clicking upload button it will pick image from gallery. After picking image then open Image cropper screen lastly cropped image will show in alert dialog. But here clicking upload button it pick image again clicking upload button open crop screen lastly clicking upload button it shows alert dialog.i want to change state automatically .. How can i fix that. Here is a sample code i have tried
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Home(),
);
}
}
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
late AppState state;
File? imageFile;
#override
void initState() {
super.initState();
state = AppState.free;
}
#override
Widget build(BuildContext context) {
Future pickedImage() async {
final pickedImage =
await ImagePicker().pickImage(source: ImageSource.gallery);
imageFile = pickedImage != null ? File(pickedImage.path) : null;
if (imageFile != null) {
setState(() {
state = AppState.picked;
});
}
}
Future<Null> _cropImage() async {
var croppedFile = await ImageCropper().cropImage(
sourcePath: imageFile!.path,
aspectRatioPresets: Platform.isAndroid
? [
CropAspectRatioPreset.square,
CropAspectRatioPreset.ratio3x2,
CropAspectRatioPreset.original,
CropAspectRatioPreset.ratio4x3,
CropAspectRatioPreset.ratio16x9
]
: [
CropAspectRatioPreset.original,
CropAspectRatioPreset.square,
CropAspectRatioPreset.ratio3x2,
CropAspectRatioPreset.ratio4x3,
CropAspectRatioPreset.ratio5x3,
CropAspectRatioPreset.ratio5x4,
CropAspectRatioPreset.ratio7x5,
CropAspectRatioPreset.ratio16x9
],
androidUiSettings: AndroidUiSettings(
toolbarTitle: 'Cropper',
toolbarColor: Colors.deepOrange,
toolbarWidgetColor: Colors.white,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false),
iosUiSettings: IOSUiSettings(
title: 'Cropper',
));
if (croppedFile != null) {
imageFile = croppedFile;
setState(() {
state = AppState.cropped;
});
}
}
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
ElevatedButton(
onPressed: () {
if (state == AppState.free)
pickedImage();
else if (state == AppState.picked)
_cropImage();
else if (state == AppState.cropped) {
showAlertDialog(context);
} else {
Container();
}
},
child: Text("Upload"))
],
));
}
showAlertDialog(BuildContext context) {
// Create button
Widget okButton = FlatButton(
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
);
// Create AlertDialog
AlertDialog alert = AlertDialog(
title: Text("Simple Alert"),
content: Container(
width: 250,
height: 100,
child: imageFile != null ? Image.file(imageFile!) : Container(),
),
actions: [
okButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
}
enum AppState {
free,
picked,
cropped,
}
Maybe you should add this line to setState block also.
if (croppedFile != null) {
//imageFile = croppedFile;
setState(() {
imageFile = croppedFile; //to here
state = AppState.cropped;
});
}
ImagePicker return a nullable CroppedFile?.
You can create a state variable to hold it.
File? imageFile;
CroppedFile? croppedFile;
/// you can also directly assing it on `croppedFile = await ImageCropper().cropImage(`
if (croppedFile != null) {
croppedFile = croppedFile;
setState(() {
state = AppState.cropped;
});
}
Now to view the croppedFile
croppedFile != null ? Image.file(File(croppedFile!.path)) : null),
My goal is to let a user selected image (via image_picker) and then use an eraser type tool to modify that image and make it transparent. Any ideas on how I should go about this?
Simple example:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image_edit/image_edit_screen.dart';
import 'package:image_edit/image_processor.dart';
import 'dart:ui' as ui;
void main() => runApp(
ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyApp(),
),
),
);
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
}
_enterImageEditController() async {
final imageBytes =
await ImageProcessor.getImageBytesAsset('lib/basic_0_people.png');
final bgImageBytes =
await ImageProcessor.getImageBytesAsset('lib/eraser_bg.jpg');
ui.decodeImageFromList(imageBytes, (result) async {
ui.decodeImageFromList(bgImageBytes, (bgResult) async {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext ctx) => ImageEditScreen(
imageBytes: imageBytes,
bgImageBytes: bgImageBytes,
image: result,
bgImage: bgResult,
),
),
);
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromARGB(255, 56, 66, 66),
floatingActionButtonLocation: FloatingActionButtonLocation.centerTop,
floatingActionButton: Wrap(
direction: Axis.horizontal,
children: [
Center(
child: ElevatedButton(
onPressed: () {
_enterImageEditController();
},
child: Text(
'ImageEdit Controller',
style: TextStyle(fontSize: 25),
),
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ImageProcessor {
static Future<Uint8List> getImageBytesAsset(String path) async {
WidgetsFlutterBinding.ensureInitialized();
final byteData = await rootBundle.load(path);
final uint8List = Uint8List.view(byteData.buffer);
return uint8List;
}
}
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:ui' as ui;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image_edit/image_edit_service.dart';
class ImageEditScreen extends StatelessWidget {
final Uint8List imageBytes;
final Uint8List bgImageBytes;
final ui.Image image;
final ui.Image bgImage;
ImageEditScreen({
Key? key,
required this.imageBytes,
required this.bgImageBytes,
required this.image,
required this.bgImage,
}) : super(key: key);
GlobalKey bgImageKey = GlobalKey();
GlobalKey imageKey = GlobalKey();
GlobalKey bgImageEraserKey = GlobalKey();
GlobalKey imageEraserKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.cyanAccent,
floatingActionButtonLocation: FloatingActionButtonLocation.centerTop,
floatingActionButton: Wrap(
direction: Axis.horizontal,
children: [
Center(
child: FittedBox(
child: SizedBox(
width: image.width.toDouble(),
height: image.height.toDouble(),
child: Consumer(
builder: (context, ref, child) {
return GestureDetector(
onPanStart: (details) {
final imageEditService =
ref.read(imageEditProvider.notifier);
imageEditService.startEdit(details.localPosition);
},
onPanUpdate: (details) {
final imageEditService =
ref.read(imageEditProvider.notifier);
imageEditService.updateEdit(details.localPosition);
},
child: Container(
color: Colors.transparent,
child: Stack(
children: [
Positioned.fill(
child: RepaintBoundary(
key: bgImageKey,
child: ImageEditPaint(
canvasPaths: [],
image: bgImage,
),
),
),
Positioned.fill(
child: Consumer(
builder: (context, ref, child) {
final imageEditState =
ref.watch(imageEditProvider);
return RepaintBoundary(
key: imageEraserKey,
child: ImageEditPaint(
canvasPaths: imageEditState.eraserPath,
image: image,
),
);
},
),
),
],
),
),
);
},
),
),
),
),
Center(
child: Consumer(
builder: (context, ref, child) {
return ElevatedButton(
child: Text(
'undo',
style: TextStyle(
fontSize: 25,
),
),
onPressed: () {
final imageEditService =
ref.read(imageEditProvider.notifier);
imageEditService.undo();
},
);
},
),
),
],
),
);
}
Future<Uint8List> takeScreenShot(GlobalKey screenshotKey) async {
RenderRepaintBoundary boundary = screenshotKey.currentContext!
.findRenderObject() as RenderRepaintBoundary;
ui.Image image = await boundary.toImage();
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData!.buffer.asUint8List();
return pngBytes;
}
}
class ImageEditPaint extends StatelessWidget {
final List<PlaygroundEraserCanvasPath> canvasPaths;
final ui.Image image;
const ImageEditPaint({
Key? key,
required this.canvasPaths,
required this.image,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return CustomPaint(
isComplex: true,
willChange: true,
foregroundPainter: EraserPainter(
canvasPaths: canvasPaths,
image: image,
),
);
}
}
class EraserPainter extends CustomPainter {
final List<PlaygroundEraserCanvasPath> canvasPaths;
final ui.Image image;
EraserPainter({
required this.canvasPaths,
required this.image,
});
#override
void paint(Canvas canvas, Size size) {
canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), Paint());
canvas.drawImage(
image,
Offset.zero,
Paint()..filterQuality = FilterQuality.high,
);
if (canvasPaths.isNotEmpty) {
for (var canvasPath in canvasPaths) {
if (canvasPath.drawPoints.isNotEmpty) {
var eraserPaint = Paint()
..strokeWidth = 50
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round
..blendMode = BlendMode.clear;
for (int i = 0; i < canvasPath.drawPoints.length; i++) {
Offset drawPoint = canvasPath.drawPoints[i];
if (canvasPath.drawPoints.length > 1) {
if (i == 0) {
canvas.drawLine(drawPoint, drawPoint, eraserPaint);
} else {
canvas.drawLine(
canvasPath.drawPoints[i - 1], drawPoint, eraserPaint);
}
} else {
canvas.drawLine(drawPoint, drawPoint, eraserPaint);
}
}
}
}
}
canvas.restore();
}
#override
bool shouldRepaint(covariant EraserPainter oldDelegate) => true;
}
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final imageEditProvider =
StateNotifierProvider.autoDispose<ImageEditService, ImageEditState>((ref) {
return ImageEditService();
});
class ImageEditService extends StateNotifier<ImageEditState> {
ImageEditService()
: super(ImageEditState(
editType: EditType.eraser, eraserPath: [], paintPath: []));
PlaygroundEraserCanvasPath _currentPath =
PlaygroundEraserCanvasPath(drawPoints: []);
void startEdit(Offset position) {
_currentPath = PlaygroundEraserCanvasPath(drawPoints: [position]);
if (state.editType == EditType.eraser) {
_editingHistory.add(EditType.eraser);
List<PlaygroundEraserCanvasPath> tempList = List.from(state.eraserPath);
tempList.add(_currentPath);
state = state.copyWith(eraserPath: tempList);
} else {
_editingHistory.add(EditType.paint);
List<PlaygroundEraserCanvasPath> tempList = List.from(state.paintPath);
tempList.add(_currentPath);
state = state.copyWith(paintPath: tempList);
}
}
void updateEdit(Offset position) {
_currentPath.drawPoints.add(position);
if (state.editType == EditType.eraser) {
List<PlaygroundEraserCanvasPath> tempList = List.from(state.eraserPath);
tempList.last = _currentPath;
state = state.copyWith(eraserPath: tempList);
} else {
List<PlaygroundEraserCanvasPath> tempList = List.from(state.paintPath);
tempList.last = _currentPath;
state = state.copyWith(paintPath: tempList);
}
}
List<EditType> _editingHistory = [];
void undo() {
if (_editingHistory.isEmpty) return;
final historyLast = _editingHistory.last;
if (historyLast == EditType.eraser) {
List<PlaygroundEraserCanvasPath> tempList = List.from(state.eraserPath);
tempList.removeLast();
state = state.copyWith(eraserPath: tempList);
} else {
List<PlaygroundEraserCanvasPath> tempList = List.from(state.paintPath);
tempList.removeLast();
state = state.copyWith(paintPath: tempList);
}
_editingHistory.removeLast();
}
void updateEditType() {
state = state.copyWith(
editType:
state.editType == EditType.eraser ? EditType.paint : EditType.eraser,
);
}
}
class PlaygroundEraserCanvasPath {
final List<Offset> drawPoints;
PlaygroundEraserCanvasPath({
required this.drawPoints,
});
}
#immutable
class ImageEditState {
EditType editType;
List<PlaygroundEraserCanvasPath> eraserPath;
List<PlaygroundEraserCanvasPath> paintPath;
ImageEditState({
required this.editType,
required this.eraserPath,
required this.paintPath,
});
ImageEditState copyWith({
EditType? editType,
List<PlaygroundEraserCanvasPath>? eraserPath,
List<PlaygroundEraserCanvasPath>? paintPath,
}) {
return ImageEditState(
editType: editType ?? this.editType,
eraserPath: eraserPath ?? this.eraserPath,
paintPath: paintPath ?? this.paintPath,
);
}
}
enum EditType {
eraser,
paint,
}
image example
What you need to do is not really straight forward but you can try image package out. So many things can be done with it.
I want to render all of the pdfs' first page as images and pass them to Home Screen. I make Splash Screen's duration to 30second. But I think it is not right because there can be hundreds of pdfs in someone's phone storage and Splash Screen's duration can be longer than 30seconds. So is there any solution to my problem? Here is my code. Enlighten me pls.
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:native_pdf_renderer/native_pdf_renderer.dart';
import 'package:splash_screen_view/SplashScreenView.dart';
import 'constant.dart';
import 'package:permission_handler/permission_handler.dart';
import 'home_screen.dart';
class Splashscreens extends StatefulWidget {
_SplashscreensState createState() => _SplashscreensState();
}
class _SplashscreensState extends State<Splashscreens> {
List<FileSystemEntity>? filepdf;
List<Uint8List>? imagepdf = [];
void initState() {
super.initState();
getFile();
}
getFile() async {
await Permission.storage.request();
final myDir = Directory('/storage/emulated/0/documents/');
filepdf = myDir.listSync(recursive: true, followLinks: true);
for (int index = 0; index < filepdf!.length; index++) {
final document = await PdfDocument.openFile(filepdf![index].path);
final page = await document.getPage(1);
final pageImage = await page.render(width: page.width, height: page.height);
setState(() {
imagepdf!.add(pageImage!.bytes);
});
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: SplashScreenView(
navigateRoute: HomeScreen(filepdf, imagepdf),
duration: 25000,
imageSize: 650,
imageSrc: "assets/image/jensenpdfviewerlogo.jpg",
colors: [
Colors.purple,
Colors.blue,
Colors.yellow,
Colors.red,
Colors.orange,
Color(0xFFECECEC)
],
pageRouteTransition: PageRouteTransition.SlideTransition,
text: "LOADING......",
textType: TextType.ColorizeAnimationText,
textStyle: fontStyle,
backgroundColor: Color(0xFF4E4AC2),
),
),
);
}
}
Here is my suggestion
class Splashscreens extends StatefulWidget {
_SplashscreensState createState() => _SplashscreensState();
}
class _SplashscreensState extends State<Splashscreens> {
List<FileSystemEntity>? filepdf;
List<Uint8List>? imagepdf = [];
Future<Map<String, dynamic>> getFile() async {
await Permission.storage.request();
final myDir = Directory('/storage/emulated/0/documents/');
filepdf = myDir.listSync(recursive: true, followLinks: true);
for (int index = 0; index < filepdf!.length; index++) {
final document = await PdfDocument.openFile(filepdf![index].path);
final page = await document.getPage(1);
final pageImage = await page.render(width: page.width, height: page.height);
setState(() {
imagepdf!.add(pageImage!.bytes);
});
}
var data = {
"file_pdf": filepdf,
"image_pdf": imagepdf
};
return data;
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getFile(),
builder: (BuildContext context, AsyncSnapshot<Map<String, dynamic>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.done:
Map<String, dynamic> data = snapshot.data!;
List<FileSystemEntity>? _filePdf = data["file_pdf"];
List<Uint8List>? _imagepdf = data["image_pdf"];
return YourResultScreen();
default:
return Container();
}
}
);
}
}
I have added some routes to my flutter app so that when a specific link is pressed a specific page will be opened
This is the source code of the main page
import 'dart:convert';
import 'package:admob_flutter/admob_flutter.dart';
import 'package:android_alarm_manager/android_alarm_manager.dart';
import 'package:catcher/catcher.dart';
import 'package:dynamic_theme/dynamic_theme.dart';
import 'package:firebase_admob/firebase_admob.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:videos/c.dart';
import 'package:flutter/foundation.dart';
import 'package:videos/post.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import 'database/database.dart';
import 'database/database/mobile.dart' as mobile;
import 'src/app_route.dart';
void main() async{
WidgetsFlutterBinding.ensureInitialized();
FirebaseAdMob.instance.initialize(appId: APP_ID);
Admob.initialize(APP_ID);
await AndroidAlarmManager.initialize();
CatcherOptions debugOptions =CatcherOptions(DialogReportMode(), [kIsWeb?ConsoleHandler(): EmailManualHandler(["ahmad.rajab#windowslive.com"])]);
CatcherOptions releaseOptions = CatcherOptions(DialogReportMode(), [
kIsWeb?ToastHandler():EmailManualHandler(["ahmad.rajab#windowslive.com"])
]);
Catcher(MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions);
}
class MyApp extends StatefulWidget{
#override
State<StatefulWidget> createState()=>MyAppState();
}
class MyAppState extends State<MyApp> {
static Future configureDatabase()async{
/*database = await openDatabase(DATABASE_NAME, version: DATABASE_VERSION,
onCreate: (Database db, int version) async {
await db.execute('create table $WATCHED_VIDEOS ($VIDEO_ID text,$REACHED_SECOND integer default 0,$NOTE text)');
await db.execute('create table $TABLE_FAVORITE_VIDEOS ($VIDEO_TITLE text,$VIDEO_ID text,$VIDEO_DESCRIPTION text,$VIDEO_THUMBURL text)');
await db.execute('create table $CHANNELS ($ID integer ,$NAME text,$LINK text, $PICTURE_LINK text,$VIEWED integer default 0,$GET_NOTIFICATIONS integer default 1,$LAST_VIDEO_TITLE text)');
},
onUpgrade: (db, oldVersion, newVersion)async {
var tableColumns= await db.query('PRAGMA table_info($WATCHED_VIDEOS)');
bool noteColumnExists=false;
for(int c=0;c<tableColumns.length;c++){
if(tableColumns[c]['name'].toString()==NOTE)noteColumnExists=true;
}
if(!noteColumnExists) await db.execute('alter table $WATCHED_VIDEOS add $NOTE text');
},);
*/
}
AppRouterDelegate _routerDelegate = AppRouterDelegate();
AppRouteInformationParser _routeInformationParser =
AppRouteInformationParser();
#override
void initState() {
super.initState();
initialiseNotification();
initialiseTimeZone();
showDailyReminderNotification();
configureDailyNewVideosFitch();
}
void configureDailyNewVideosFitch()async{
await AndroidAlarmManager.periodic(const Duration(days: 1),DAILY_NEW_VIDEOS_FETCH_ALARAM_ID , ()async{
Database d=RepositoryProvider.of<Database>(context);
List<Channel>subscribedChannels=await (d.select($ChannelsTable(d))..where((tbl) => tbl.getNotifications.equals(1))).get();
String channelsIds='';
for(int i=0;i<subscribedChannels.length;i++)channelsIds+=subscribedChannels[i].link+"/";
Map<String,String> map=Map();
map['channels']=channelsIds;
Post p=Post(context,'getVideosFromTime.php',map);
await p.fetchPost();
if(p.connectionSucceed){
dynamic resultJson=json.decode(p.result);
if(resultJson['result']=='success'){
for(int i=0;i<resultJson['data'].length;i++){
dynamic videoJson=resultJson['data'][i];
await flutterLocalNotificationsPlugin.show(videoJson['id'], videoJson['channelName'], videoJson['title'],
NotificationDetails(
android: AndroidNotificationDetails(
APP_NAME,
APP_NAME, NEW_VIDEO_AVAILABLE,
icon: '#mipmap/ic_launcher',
ticker: videoJson['title']
),
),
payload:'/watch?v='+videoJson['videoId']
);
}
}
}
},rescheduleOnReboot: true);
}
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
void showDailyReminderNotification()async{
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(APP_NAME,
APP_NAME, 'dailyReminder'+APP_NAME,
icon: '#mipmap/ic_launcher',
ticker: TIME_TO_LEARN);
const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.periodicallyShow(DAILY_REMINDER_NOTIFICATION_ID, TIME_TO_LEARN,
TIME_TO_LEARN_DESCRIPTION, RepeatInterval.daily, platformChannelSpecifics,
androidAllowWhileIdle: false);
}
void initialiseTimeZone()async{
tz.initializeTimeZones();
tz.setLocalLocation(tz.getLocation(await FlutterNativeTimezone.getLocalTimezone()));
}
void initialiseNotification()async{
flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('#mipmap/ic_launcher');
final IOSInitializationSettings initializationSettingsIOS =
IOSInitializationSettings(
/*onDidReceiveLocalNotification: onDidReceiveLocalNotification*/);
final MacOSInitializationSettings initializationSettingsMacOS =
MacOSInitializationSettings();
final InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
macOS: initializationSettingsMacOS);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: selectNotification);
await flutterLocalNotificationsPlugin.cancel(DAILY_REMINDER_NOTIFICATION_ID);
}
Future selectNotification(String payload) async {
if(payload!=null)Navigator.pushNamed(context, payload);
}
#override
Widget build(BuildContext context) {
return DynamicTheme(
defaultBrightness: Brightness.light,
data: (brightness) => ThemeData(
primarySwatch: PRIMARY_COLOR,
brightness: brightness
),
themedWidgetBuilder: (context, data) {
return RepositoryProvider<Database>(
create:(context)=>mobile.constructDb() ,
child: BlocProvider(
create: (context){
//final db = RepositoryProvider.of<Database>(context);
//return AppBloc(db);
},
child: MaterialApp.router(
//title: 'Books App',
theme: data,
routerDelegate: _routerDelegate,
routeInformationParser: _routeInformationParser,
)
)
);
}
);
}
}
MobileAdTargetingInfo targetingInfo = MobileAdTargetingInfo(
// ignore: deprecated_member_use
gender: MobileAdGender.unknown,
childDirected: true
);
class AdmobAdd extends StatelessWidget{
#override
Widget build(BuildContext context) {
return !PRO?AdmobBanner(
adUnitId: kReleaseMode?BANNER_AD_UNIT_ID:BannerAd.testAdUnitId,
adSize: AdmobBannerSize.BANNER,
):Container();
}
}
InterstitialAd myInterstitial = InterstitialAd(
adUnitId: InterstitialAd.testAdUnitId,
targetingInfo: targetingInfo,
);
this is the source code of the routes configuration file
import 'package:catcher/catcher.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:videos/home.dart';
import 'package:videos/video.dart';
class AppRouteInformationParser extends RouteInformationParser<AppRoutePath> {
#override
Future<AppRoutePath> parseRouteInformation(
RouteInformation routeInformation) async {
final uri = Uri.parse(routeInformation.location);
// Handle '/'
if (uri.pathSegments.length == 0) {
return AppRoutePath.home();
}
// Handle '/watch?v=fsdfsdfsd
if (uri.pathSegments.length == 1) {
if (!uri.pathSegments[0].startsWith('/watch?')) return AppRoutePath.unknown();
if(uri.pathSegments[0].startsWith('/watch?v=')){
var videoId=uri.queryParameters['v'];
return AppRoutePath.video(videoId);
/*var remaining = uri.pathSegments[1];
var id = int.tryParse(remaining);
if (id == null) return AppRoutePath.unknown();
return AppRoutePath.details(id);*/
}
}
// Handle unknown routes
return AppRoutePath.unknown();
}
#override
RouteInformation restoreRouteInformation(AppRoutePath path) {
if (path.isUnknown) {
return RouteInformation(location: '/404');
}
if (path.isHomePage) {
return RouteInformation(location: '/');
}
if(path.isVideoPage){
return RouteInformation(location: '/watch?v=${path.id}');
}
if (path.isDetailsPage) {
return RouteInformation(location: '/book/${path.id}');
}
return null;
}
}
class AppRouterDelegate extends RouterDelegate<AppRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoutePath> {
final GlobalKey<NavigatorState> navigatorKey;
//Book _selectedBook;
bool show404 = false;
String videoId;
/*List<Book> books = [
Book('Stranger in a Strange Land', 'Robert A. Heinlein'),
Book('Foundation', 'Isaac Asimov'),
Book('Fahrenheit 451', 'Ray Bradbury'),
];*/
AppRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();
AppRoutePath get currentConfiguration {
if (show404) {
return AppRoutePath.unknown();
}
if(videoId!=null) return AppRoutePath.video(videoId);
/*return _selectedBook == null
? AppRoutePath.home()
: AppRoutePath.details(books.indexOf(_selectedBook));*/
return AppRoutePath.home();
}
#override
Widget build(BuildContext context) {
return Navigator(
key: Catcher.navigatorKey,
pages: [
MaterialPage(
key: ValueKey('Home'),
child: MyHomePage()
),
if (show404)
MaterialPage(key: ValueKey('Unknown'), child: UnknownScreen())
else if(videoId!=null) VideoPageRoute(videoId: videoId)
/*else if (_selectedBook != null)
BookDetailsPage(book: _selectedBook)*/
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
// Update the list of pages by setting _selectedBook to null
//_selectedBook = null;
videoId=null;
show404 = false;
notifyListeners();
return true;
},
);
}
#override
Future<void> setNewRoutePath(AppRoutePath path) async {
if (path.isUnknown) {
//_selectedBook = null;
videoId=null;
show404 = true;
return;
}
/*if (path.isDetailsPage) {
if (path.id < 0 || path.id > books.length - 1) {
show404 = true;
return;
}
_selectedBook = books[path.id];
} else {
_selectedBook = null;
}*/
show404 = false;
}
void videoNotFound(){
videoId=null;
show404=true;
notifyListeners();
}
/*void _handleBookTapped(Book book) {
_selectedBook = book;
notifyListeners();
}*/
}
class AppRoutePath {
final String id;
final bool isUnknown;
AppRoutePath.home()
: id = null,
isUnknown = false;
AppRoutePath.details(this.id) : isUnknown = false;
AppRoutePath.video(this.id): isUnknown=false;
AppRoutePath.unknown()
: id = null,
isUnknown = true;
bool get isHomePage => id == null;
bool get isDetailsPage => id != null;
bool get isVideoPage => id!=null;
}
/*class BooksListScreen extends StatelessWidget {
final List<Book> books;
final ValueChanged<Book> onTapped;
BooksListScreen({
#required this.books,
#required this.onTapped,
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView(
children: [
for (var book in books)
ListTile(
title: Text(book.title),
subtitle: Text(book.author),
onTap: () => onTapped(book),
)
],
),
);
}
}
*/
/*class BookDetailsScreen extends StatelessWidget {
final Book book;
BookDetailsScreen({
#required this.book,
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (book != null) ...[
Text(book.title, style: Theme.of(context).textTheme.headline6),
Text(book.author, style: Theme.of(context).textTheme.subtitle1),
],
],
),
),
);
}
}
*/
class UnknownScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text('404!'),
),
);
}
}
The problem is that I have several screens that has no specific url and I do not want to provide them with routes, I want simply to use Navigator.push()
That is working fine, but when pressing the back button or when I use Navigator.pop() the app closes entirely instead of navigating back to the previous page
How can I use Navigator.pushNamed only for named routes and use Navigator.push for other pages without causing the problem to occur
Thanks in advance