Skip to content

Commit

Permalink
Merge pull request #31 from bakicelebi/PAINTROID-634
Browse files Browse the repository at this point in the history
PAINTROID-634 : Add Eraser tool
  • Loading branch information
juliajulie95 authored Aug 28, 2023
2 parents 5f47f56 + fddbb2a commit 0ed17ab
Show file tree
Hide file tree
Showing 49 changed files with 1,072 additions and 238 deletions.
1 change: 1 addition & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ linter:
prefer_relative_imports: false
prefer_single_quotes: true
avoid_void_async: true
constant_identifier_names: false

analyzer:

Expand Down
3 changes: 2 additions & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>

<queries>
<intent>
Expand Down
1 change: 1 addition & 0 deletions assets/svg/ic_clipboard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions lib/core/graphic_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,22 @@ class GraphicFactory {
PictureRecorder createPictureRecorder() => PictureRecorder();

Canvas createCanvasWithRecorder(PictureRecorder recorder) => Canvas(recorder);

Paint copyPaint(Paint original) {
return Paint()
..blendMode = original.blendMode
..color = original.color
..colorFilter = original.colorFilter
..filterQuality = original.filterQuality
..imageFilter = original.imageFilter
..invertColors = original.invertColors
..isAntiAlias = original.isAntiAlias
..maskFilter = original.maskFilter
..shader = original.shader
..strokeCap = original.strokeCap
..strokeJoin = original.strokeJoin
..strokeMiterLimit = original.strokeMiterLimit
..strokeWidth = original.strokeWidth
..style = original.style;
}
}
9 changes: 9 additions & 0 deletions lib/core/toast_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:toast/toast.dart';

class ToastUtils {
const ToastUtils._();

static void showShortToast({required String message}) {
Toast.show(message, duration: Toast.lengthShort, gravity: Toast.bottom);
}
}
27 changes: 22 additions & 5 deletions lib/io/src/serialization/proto/schema/graphic/paint.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,33 @@ syntax = 'proto3';

message SerializablePaint {
uint32 color = 1;

float strokeWidth = 2;

enum StrokeCap {
ROUND = 0;
BUTT = 1;
SQUARE = 2;
STROKE_CAP_ROUND = 0;
STROKE_CAP_BUTT = 1;
STROKE_CAP_SQUARE = 2;
}
StrokeCap cap = 3;

enum PaintingStyle {
FILL = 0;
STROKE = 1;
PAINTING_STYLE_FILL = 0;
PAINTING_STYLE_STROKE = 1;
}
PaintingStyle style = 4;

enum BlendMode {
BLEND_MODE_SCR_OVER = 0;
BLEND_MODE_CLEAR = 1;
}
BlendMode blendMode = 5;

enum StrokeJoin {
STROKE_JOIN_MITER = 0;
STROKE_JOIN_ROUND = 1;
STROKE_JOIN_BEVEL = 2;
}
StrokeJoin strokeJoin = 6;

}
79 changes: 37 additions & 42 deletions lib/io/src/serialization/serializer/graphic/paint_serializer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@ class PaintSerializer
extends ProtoSerializerWithVersioning<Paint, SerializablePaint> {
final GraphicFactory _graphicFactory;

static const _capMap = {
SerializablePaint_StrokeCap.STROKE_CAP_BUTT: StrokeCap.butt,
SerializablePaint_StrokeCap.STROKE_CAP_ROUND: StrokeCap.round,
SerializablePaint_StrokeCap.STROKE_CAP_SQUARE: StrokeCap.square,
};

static const _styleMap = {
SerializablePaint_PaintingStyle.PAINTING_STYLE_FILL: PaintingStyle.fill,
SerializablePaint_PaintingStyle.PAINTING_STYLE_STROKE: PaintingStyle.stroke,
};

static const _blendModeMap = {
SerializablePaint_BlendMode.BLEND_MODE_SCR_OVER: BlendMode.srcOver,
SerializablePaint_BlendMode.BLEND_MODE_CLEAR: BlendMode.clear,
};

static const _strokeJoinMap = {
SerializablePaint_StrokeJoin.STROKE_JOIN_MITER: StrokeJoin.miter,
SerializablePaint_StrokeJoin.STROKE_JOIN_ROUND: StrokeJoin.round,
SerializablePaint_StrokeJoin.STROKE_JOIN_BEVEL: StrokeJoin.bevel,
};

const PaintSerializer(super.version, this._graphicFactory);

static final provider = Provider.family(
Expand All @@ -20,55 +42,28 @@ class PaintSerializer

@override
Future<Paint> deserializeWithLatestVersion(SerializablePaint data) async {
final paint = _graphicFactory.createPaint()
return _graphicFactory.createPaint()
..color = Color(data.color)
..strokeWidth = data.strokeWidth;
switch (data.cap) {
case SerializablePaint_StrokeCap.BUTT:
paint.strokeCap = StrokeCap.butt;
break;
case SerializablePaint_StrokeCap.ROUND:
paint.strokeCap = StrokeCap.round;
break;
case SerializablePaint_StrokeCap.SQUARE:
paint.strokeCap = StrokeCap.square;
break;
}
switch (data.style) {
case SerializablePaint_PaintingStyle.FILL:
paint.style = PaintingStyle.fill;
break;
case SerializablePaint_PaintingStyle.STROKE:
paint.style = PaintingStyle.stroke;
break;
}
return paint;
..strokeWidth = data.strokeWidth
..strokeCap = _capMap[data.cap] ?? StrokeCap.butt
..style = _styleMap[data.style] ?? PaintingStyle.fill
..blendMode = _blendModeMap[data.blendMode] ?? BlendMode.srcOver
..strokeJoin = _strokeJoinMap[data.strokeJoin] ?? StrokeJoin.miter;
}

@override
Future<SerializablePaint> serializeWithLatestVersion(Paint object) async {
final serializable = SerializablePaint()
..color = object.color.value
..strokeWidth = object.strokeWidth;
switch (object.strokeCap) {
case StrokeCap.butt:
serializable.cap = SerializablePaint_StrokeCap.BUTT;
break;
case StrokeCap.round:
serializable.cap = SerializablePaint_StrokeCap.ROUND;
break;
case StrokeCap.square:
serializable.cap = SerializablePaint_StrokeCap.SQUARE;
break;
}
switch (object.style) {
case PaintingStyle.fill:
serializable.style = SerializablePaint_PaintingStyle.FILL;
break;
case PaintingStyle.stroke:
serializable.style = SerializablePaint_PaintingStyle.STROKE;
break;
}
..strokeWidth = object.strokeWidth
..cap = _capMap.entries.firstWhere((e) => e.value == object.strokeCap).key
..style = _styleMap.entries.firstWhere((e) => e.value == object.style).key
..blendMode = _blendModeMap.entries
.firstWhere((e) => e.value == object.blendMode)
.key
..strokeJoin = _strokeJoinMap.entries
.firstWhere((e) => e.value == object.strokeJoin)
.key;
return serializable;
}
}
35 changes: 29 additions & 6 deletions lib/io/src/service/permission_service.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:io';

import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:paintroid/core/loggable_mixin.dart';
import 'package:permission_handler/permission_handler.dart';
Expand All @@ -20,16 +21,30 @@ class PermissionService with LoggableMixin implements IPermissionService {
static const _unhandledPlatformErrorMsg =
'Permissions for this platform have not been handled';

Future<bool> _isAndroidVersionGreaterOrEqualTo(int version) async {
if (!Platform.isAndroid) return false;
final deviceInfo = DeviceInfoPlugin();
final androidInfo = await deviceInfo.androidInfo;
return androidInfo.version.sdkInt >= version;
}

@override
Future<bool> requestAccessToSharedFileStorage() async {
if (Platform.isIOS || Platform.isAndroid) {
const permission = Permission.storage;
final status = await permission.request();
return _didGrant(status, Permission.storage);
late final Permission permission;
if (Platform.isIOS) {
permission = Permission.photos;
} else if (Platform.isAndroid) {
if (await _isAndroidVersionGreaterOrEqualTo(33)) {
permission = Permission.photos;
} else {
permission = Permission.storage;
}
} else {
logger.severe(_unhandledPlatformErrorMsg);
return false;
}
final status = await permission.request();
return _didGrant(status, permission);
}

@override
Expand All @@ -38,7 +53,11 @@ class PermissionService with LoggableMixin implements IPermissionService {
if (Platform.isIOS) {
permission = Permission.photos;
} else if (Platform.isAndroid) {
permission = Permission.storage;
if (await _isAndroidVersionGreaterOrEqualTo(33)) {
permission = Permission.photos;
} else {
permission = Permission.storage;
}
} else {
logger.severe(_unhandledPlatformErrorMsg);
return false;
Expand All @@ -53,7 +72,11 @@ class PermissionService with LoggableMixin implements IPermissionService {
if (Platform.isIOS) {
permission = Permission.photosAddOnly;
} else if (Platform.isAndroid) {
permission = Permission.storage;
if (await _isAndroidVersionGreaterOrEqualTo(33)) {
permission = Permission.photos;
} else {
permission = Permission.storage;
}
} else {
logger.severe(_unhandledPlatformErrorMsg);
return false;
Expand Down
8 changes: 4 additions & 4 deletions lib/io/src/ui/project_details_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:oxidized/oxidized.dart';
import 'package:paintroid/core/toast_utils.dart';
import 'package:paintroid/data/model/project.dart';
import 'package:paintroid/io/io.dart';
import 'package:paintroid/io/src/ui/generic_dialog.dart';
Expand Down Expand Up @@ -80,7 +80,7 @@ class _ProjectDetailsDialogState extends ConsumerState<ProjectDetailsDialog> {
int _getProjectSize() => fileService.getFile(widget.project.path).when(
ok: (file) => file.lengthSync(),
err: (failure) {
Fluttertoast.showToast(msg: failure.message);
ToastUtils.showShortToast(message: failure.message);
return 0;
},
);
Expand All @@ -95,12 +95,12 @@ class _ProjectDetailsDialogState extends ConsumerState<ProjectDetailsDialog> {
return dimensions;
},
err: (failure) {
Fluttertoast.showToast(msg: failure.message);
ToastUtils.showShortToast(message: failure.message);
return dimensions;
},
),
err: (failure) {
Fluttertoast.showToast(msg: failure.message);
ToastUtils.showShortToast(message: failure.message);
return dimensions;
},
);
Expand Down
8 changes: 4 additions & 4 deletions lib/io/src/ui/save_image_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ class _SaveImageDialogState extends State<SaveImageDialog> {
return AlertDialog(
title: Text(
dialogTitle,
style: ThemeText.largeBoldText,
style: TextThemes.largeBoldText,
),
actions: [_cancelButton, _saveButton],
contentTextStyle: ThemeText.menuItem,
contentTextStyle: TextThemes.menuItem,
content: Form(
key: formKey,
child: Column(
Expand Down Expand Up @@ -145,7 +145,7 @@ class _SaveImageDialogState extends State<SaveImageDialog> {
controller: nameFieldController,
decoration: InputDecoration(
hintText: widget.savingProject ? 'Project name' : 'Image name',
hintStyle: ThemeText.hintTextNormal,
hintStyle: TextThemes.hintTextNormal,
filled: true,
fillColor: lightColorScheme.secondaryContainer,
border: const OutlineInputBorder(
Expand Down Expand Up @@ -176,7 +176,7 @@ class _SaveImageDialogState extends State<SaveImageDialog> {
items: ImageFormat.values.map((fileType) {
return DropdownMenuItem<ImageFormat>(
value: fileType,
child: Text(fileType.extension, style: ThemeText.menuItem),
child: Text(fileType.extension, style: TextThemes.menuItem),
);
}).toList(),
onChanged: (selectedFileType) {
Expand Down
19 changes: 13 additions & 6 deletions lib/tool/src/brush_tool/brush_tool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import 'package:paintroid/command/src/command_manager.dart';
import 'package:paintroid/core/graphic_factory.dart';
import 'package:paintroid/core/path_with_action_history.dart';
import 'package:paintroid/tool/src/tool.dart';
import 'package:paintroid/tool/src/tool_types.dart';

class BrushTool extends Tool with EquatableMixin {
BrushTool({
required super.paint,
required super.commandFactory,
required super.commandManager,
required this.graphicFactory,
required super.type,
});

final GraphicFactory graphicFactory;
Expand All @@ -25,7 +27,9 @@ class BrushTool extends Tool with EquatableMixin {
void onDown(Offset point) {
pathToDraw = graphicFactory.createPathWithActionHistory()
..moveTo(point.dx, point.dy);
final command = commandFactory.createDrawPathCommand(pathToDraw, paint);
Paint savedPaint = graphicFactory.copyPaint(paint);
final command =
commandFactory.createDrawPathCommand(pathToDraw, savedPaint);
commandManager.addGraphicCommand(command);
}

Expand All @@ -49,16 +53,19 @@ class BrushTool extends Tool with EquatableMixin {
@override
List<Object?> get props => [commandManager, commandFactory, graphicFactory];

BrushTool copyWith(
{Paint? paint,
CommandFactory? commandFactory,
CommandManager? commandManager,
GraphicFactory? graphicFactory}) {
BrushTool copyWith({
Paint? paint,
CommandFactory? commandFactory,
CommandManager? commandManager,
GraphicFactory? graphicFactory,
ToolType? type,
}) {
return BrushTool(
paint: paint ?? this.paint,
commandFactory: commandFactory ?? this.commandFactory,
commandManager: commandManager ?? this.commandManager,
graphicFactory: graphicFactory ?? this.graphicFactory,
type: type ?? this.type,
);
}
}
2 changes: 2 additions & 0 deletions lib/tool/src/brush_tool/brush_tool_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:paintroid/command/src/command_manager_provider.dart';
import 'package:paintroid/core/graphic_factory_provider.dart';
import 'package:paintroid/tool/src/brush_tool/brush_tool.dart';
import 'package:paintroid/tool/src/brush_tool/brush_tool_state_provider.dart';
import 'package:paintroid/tool/src/tool_types.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'brush_tool_provider.g.dart';
Expand All @@ -11,6 +12,7 @@ part 'brush_tool_provider.g.dart';
BrushTool brushTool(BrushToolRef ref) {
return BrushTool(
paint: ref.watch(brushToolStateProvider.select((state) => state.paint)),
type: ToolType.BRUSH,
commandManager: ref.watch(commandManagerProvider),
commandFactory: ref.watch(commandFactoryProvider),
graphicFactory: ref.watch(graphicFactoryProvider),
Expand Down
Loading

0 comments on commit 0ed17ab

Please sign in to comment.