Skip to content

Commit

Permalink
PAINTROID-634 : Add Eraser tool
Browse files Browse the repository at this point in the history
  • Loading branch information
bakicelebi committed Aug 26, 2023
2 parents 609fe81 + c9cc266 commit df2ff74
Show file tree
Hide file tree
Showing 69 changed files with 1,391 additions and 698 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ jobs:
- uses: actions/checkout@v3
- name: Install Protoc
uses: arduino/setup-protoc@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- uses: subosito/[email protected]
with:
flutter-version: '3.10.5'
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ coverage
*.mocks.dart
*.pb*.dart
*.g.dart
*.freezed.dart

# Web related
lib/generated_plugin_registrant.dart
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
Empty file modified generate_protos.sh
100644 → 100755
Empty file.
3 changes: 0 additions & 3 deletions lib/command/src/command_factory.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import 'dart:ui';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:paintroid/command/src/implementation/command/graphic/draw_path_command.dart';
import 'package:paintroid/core/path_with_action_history.dart';

class CommandFactory {
const CommandFactory();

static final provider = Provider((ref) => const CommandFactory());

DrawPathCommand createDrawPathCommand(
PathWithActionHistory path, Paint paint) =>
DrawPathCommand(path, paint);
Expand Down
9 changes: 9 additions & 0 deletions lib/command/src/command_factory_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:paintroid/command/src/command_factory.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'command_factory_provider.g.dart';

@Riverpod(keepAlive: true)
CommandFactory commandFactory(CommandFactoryRef ref) {
return const CommandFactory();
}
6 changes: 0 additions & 6 deletions lib/command/src/command_manager.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import 'dart:ui';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:paintroid/command/src/command.dart';
import 'package:paintroid/command/src/graphic_command.dart';
import 'package:paintroid/command/src/implementation/manager/sync_command_manager.dart';

abstract class CommandManager {
static final provider = Provider<CommandManager>(
(ref) => SyncCommandManager(commands: []),
);

Iterable<Command> get history;

int get count;
Expand Down
10 changes: 10 additions & 0 deletions lib/command/src/command_manager_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'package:paintroid/command/src/command_manager.dart';
import 'package:paintroid/command/src/implementation/manager/sync_command_manager.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'command_manager_provider.g.dart';

@Riverpod(keepAlive: true)
CommandManager commandManager(CommandManagerRef ref) {
return SyncCommandManager(commands: []);
}
21 changes: 18 additions & 3 deletions lib/core/graphic_factory.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import 'dart:ui';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:paintroid/core/path_with_action_history.dart';

class GraphicFactory {
const GraphicFactory();

static final provider = Provider((ref) => const GraphicFactory());

Paint createPaint() => Paint();

PathWithActionHistory createPathWithActionHistory() =>
Expand All @@ -16,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/graphic_factory_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:paintroid/core/graphic_factory.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'graphic_factory_provider.g.dart';

@Riverpod(keepAlive: true)
GraphicFactory graphicFactory(GraphicFactoryRef ref) {
return const GraphicFactory();
}
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;

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:paintroid/command/command.dart'
show CommandFactory, DrawPathCommand;
import 'package:paintroid/command/src/command_factory_provider.dart';
import 'package:paintroid/io/serialization.dart';

class DrawPathCommandSerializer extends ProtoSerializerWithVersioning<
Expand All @@ -21,7 +22,7 @@ class DrawPathCommandSerializer extends ProtoSerializerWithVersioning<
ver,
ref.watch(PathSerializer.provider(ver)),
ref.watch(PaintSerializer.provider(ver)),
ref.watch(CommandFactory.provider)),
ref.watch(commandFactoryProvider)),
);

@override
Expand Down
82 changes: 39 additions & 43 deletions lib/io/src/serialization/serializer/graphic/paint_serializer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,68 @@ import 'dart:ui';

import 'package:flutter_riverpod/flutter_riverpod.dart' show Provider;
import 'package:paintroid/core/graphic_factory.dart';
import 'package:paintroid/core/graphic_factory_provider.dart';
import 'package:paintroid/io/serialization.dart';

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(
(ref, int ver) => PaintSerializer(ver, ref.watch(GraphicFactory.provider)),
(ref, int ver) => PaintSerializer(ver, ref.watch(graphicFactoryProvider)),
);

@override
final fromBytesToSerializable = SerializablePaint.fromBuffer;

@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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:ui';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:paintroid/core/graphic_factory.dart';
import 'package:paintroid/core/graphic_factory_provider.dart';
import 'package:paintroid/core/loggable_mixin.dart';
import 'package:paintroid/core/path_with_action_history.dart';
import 'package:paintroid/io/serialization.dart';
Expand All @@ -13,7 +14,7 @@ class PathSerializer extends ProtoSerializerWithVersioning<
PathSerializer(super.version, this._graphicFactory);

static final provider = Provider.family(
(ref, int ver) => PathSerializer(ver, ref.watch(GraphicFactory.provider)),
(ref, int ver) => PathSerializer(ver, ref.watch(graphicFactoryProvider)),
);

@override
Expand Down
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
Loading

0 comments on commit df2ff74

Please sign in to comment.