-
Notifications
You must be signed in to change notification settings - Fork 66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PAINTROID-737: Add Pipette Tool #78
Open
bhav-khurana
wants to merge
47
commits into
Catrobat:develop
Choose a base branch
from
bhav-khurana:PAINTROID-737
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
da5297f
Add Color Picker
bhav-khurana 5aaaaaa
Merge branch 'Catrobat:develop' into color_picker
bhav-khurana a6cafb0
Implement color picker
bhav-khurana 372b63a
Merge branch 'color_picker' of https://github.com/bhav-khurana/Paintr…
bhav-khurana 29f0187
Fix issues
bhav-khurana 432fbf8
Merge with develop
bhav-khurana 492d8c1
Fix issues
bhav-khurana 98fe1e7
Merge branch 'develop' into color_picker
bhav-khurana eca8dcd
Resolve pubspec errors
bhav-khurana 0a14b44
address requested changes
bhav-khurana de3ce87
Merge branch 'develop' into color_picker
bhav-khurana c90bdd6
fix minor issues
bhav-khurana 34f9ba2
Merge branch 'color_picker' of https://github.com/bhav-khurana/Paintr…
bhav-khurana 6d2e803
refactoring and minor fixes
bhav-khurana c05f08f
use checkerboardimg from component_library
bhav-khurana 4596426
fix minor issues
bhav-khurana 0c15732
revert app_localizations file
bhav-khurana f7526f5
add dynamic color change, separate colorpicker package
bhav-khurana 37eac01
use riverpod generator
bhav-khurana 10c8e56
fix lint errors
bhav-khurana e9d9f76
add tests
bhav-khurana 1272ceb
fix riverpod issues, remove redundancies
bhav-khurana 34235dc
remove comments, capitalize text
bhav-khurana 7a430c2
convert stateful to stateless, rename widgets
bhav-khurana 8a347b9
address changes, remove slider position state provider
bhav-khurana 749274a
fix minor issues
bhav-khurana e8a6e03
debug test error
bhav-khurana 06972ec
Revert "debug test error"
bhav-khurana f17b9fd
debug test error
bhav-khurana 9b1ba2e
fix test error
bhav-khurana 48bd9de
resolve conflicts
bhav-khurana 34a6c9f
Merge branch 'develop' into color_picker
bhav-khurana c47fef8
sort imports, remove duplicates
bhav-khurana 096ac43
fix lint errors
bhav-khurana fb23944
use paintroid theme colors
bhav-khurana 2aabe54
fix minor issues
bhav-khurana f0d97d5
fix lint errors
bhav-khurana 22c993d
fix background color
bhav-khurana e3353b5
fix test error, minor refactoring
bhav-khurana 5bab29a
add pipette tool
bhav-khurana 5550010
Merge remote-tracking branch 'origin/develop' into PAINTROID-737
bhav-khurana 5215c12
fix dependencies
bhav-khurana 3e64b1f
fix test error
bhav-khurana 3677ed4
update makefile to run pub get in packages
bhav-khurana d6d8c92
add basic color match test
bhav-khurana 05aa422
Merge remote-tracking branch 'origin/develop' into PAINTROID-737
bhav-khurana 6fb279e
fix errors
bhav-khurana File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import 'dart:async'; | ||
import 'dart:typed_data'; | ||
import 'dart:ui' as ui; | ||
|
||
import 'package:colorpicker/src/components/top_bar.dart'; | ||
import 'package:colorpicker/src/state/color_picker_state_provider.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
|
||
class PipetteToolPage extends ConsumerStatefulWidget { | ||
const PipetteToolPage({ | ||
super.key, | ||
required this.snapshot, | ||
}); | ||
|
||
final ui.Image? snapshot; | ||
|
||
@override | ||
ConsumerState<ConsumerStatefulWidget> createState() => | ||
_PipetteToolPageState(); | ||
} | ||
|
||
class _PipetteToolPageState extends ConsumerState<PipetteToolPage> { | ||
GlobalKey imageKey = GlobalKey(); | ||
ui.Image? displayImage; | ||
|
||
Future<void> _loadImage() async { | ||
final ByteData? bytedata = | ||
await widget.snapshot!.toByteData(format: ui.ImageByteFormat.png); | ||
if (bytedata == null) { | ||
return Future.error('An error occurred while loading the snapshot'); | ||
} | ||
final Uint8List headedIntList = Uint8List.view(bytedata.buffer); | ||
ImageProvider? image = MemoryImage(headedIntList); | ||
final ImageStream imageStream = image.resolve(const ImageConfiguration()); | ||
final Completer<ui.Image> completer = Completer<ui.Image>(); | ||
|
||
void imageListener(ImageInfo info, bool synchronousCall) { | ||
completer.complete(info.image); | ||
imageStream.removeListener(ImageStreamListener(imageListener)); | ||
} | ||
|
||
imageStream.addListener(ImageStreamListener(imageListener)); | ||
displayImage = await completer.future; | ||
|
||
setState(() {}); | ||
} | ||
|
||
Future<void> _updateColor(TapUpDetails details) async { | ||
RenderBox box = imageKey.currentContext!.findRenderObject() as RenderBox; | ||
Offset localPosition = box.globalToLocal(details.globalPosition); | ||
|
||
double xRatio = localPosition.dx / box.size.width; | ||
double yRatio = localPosition.dy / box.size.height; | ||
|
||
int x = (xRatio * displayImage!.width).toInt(); | ||
int y = (yRatio * displayImage!.height).toInt(); | ||
|
||
ByteData? byteData = | ||
await displayImage!.toByteData(format: ui.ImageByteFormat.rawRgba); | ||
if (byteData == null) return; | ||
|
||
int offset = (y * displayImage!.width + x) * 4; | ||
|
||
int red = byteData.getUint8(offset); | ||
int green = byteData.getUint8(offset + 1); | ||
int blue = byteData.getUint8(offset + 2); | ||
int alpha = byteData.getUint8(offset + 3); | ||
|
||
Color color = Color.fromARGB(alpha, red, green, blue); | ||
|
||
final colorData = ref.read(colorPickerStateProvider.notifier); | ||
colorData.updateColor(color.withOpacity(1)); | ||
colorData.updateOpacity(color.opacity); | ||
} | ||
|
||
@override | ||
void initState() { | ||
super.initState(); | ||
_loadImage(); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final screenSize = MediaQuery.of(context).size; | ||
final colorData = ref.watch(colorPickerStateProvider); | ||
return Scaffold( | ||
backgroundColor: Colors.white, | ||
appBar: PreferredSize( | ||
preferredSize: const Size.fromHeight(60.0), | ||
child: TopBar( | ||
color: colorData.currentColor != null | ||
? colorData.currentColor!.withOpacity(colorData.currentOpacity) | ||
: Colors.transparent), | ||
), | ||
body: Center( | ||
child: Stack( | ||
children: [ | ||
Positioned.fill( | ||
child: Image.asset( | ||
'packages/colorpicker/assets/img/checkerboard.png', | ||
repeat: ImageRepeat.repeat, | ||
cacheHeight: 16, | ||
cacheWidth: 16, | ||
filterQuality: FilterQuality.none, | ||
), | ||
), | ||
if (displayImage != null) | ||
GestureDetector( | ||
onTapUp: _updateColor, | ||
child: SizedBox( | ||
height: screenSize.height, | ||
width: screenSize.width, | ||
child: CustomPaint( | ||
key: imageKey, | ||
painter: ImagePainter(displayImage!), | ||
), | ||
), | ||
), | ||
], | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
class ImagePainter extends CustomPainter { | ||
final ui.Image image; | ||
|
||
ImagePainter(this.image); | ||
|
||
@override | ||
void paint(Canvas canvas, Size size) { | ||
final Rect srcRect = | ||
Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()); | ||
final Rect dstRect = Rect.fromLTWH(0, 0, size.width, size.height); | ||
canvas.drawImageRect(image, srcRect, dstRect, Paint()); | ||
} | ||
|
||
@override | ||
bool shouldRepaint(covariant CustomPainter oldDelegate) { | ||
return false; | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
packages/colorpicker/lib/src/components/pipette_tool_button.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import 'package:flutter/material.dart'; | ||
|
||
class PipetteToolButton extends StatelessWidget { | ||
const PipetteToolButton({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Container( | ||
height: 50.0, | ||
width: 148.0, | ||
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 4.0), | ||
decoration: BoxDecoration( | ||
color: const Color.fromARGB(255, 204, 204, 204), | ||
borderRadius: const BorderRadius.all(Radius.circular(6.0)), | ||
boxShadow: [ | ||
BoxShadow( | ||
color: Colors.black.withOpacity(0.2), | ||
offset: const Offset(0, 1), | ||
blurRadius: 1.0, | ||
), | ||
], | ||
), | ||
child: const Row( | ||
mainAxisAlignment: MainAxisAlignment.center, | ||
children: [ | ||
Spacer(), | ||
Icon( | ||
Icons.auto_fix_normal, | ||
color: Colors.black, | ||
size: 20, | ||
), | ||
Spacer(), | ||
Text( | ||
'PIPETTE', | ||
style: TextStyle( | ||
color: Colors.black, | ||
fontWeight: FontWeight.w500, | ||
), | ||
), | ||
Spacer(), | ||
], | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
|
||
class TopBar extends ConsumerWidget { | ||
const TopBar({ | ||
super.key, | ||
required this.color, | ||
}); | ||
|
||
final Color color; | ||
|
||
@override | ||
Widget build(BuildContext context, WidgetRef ref) { | ||
return AppBar( | ||
title: Container( | ||
height: 36.0, | ||
width: 36.0, | ||
decoration: BoxDecoration( | ||
color: color, | ||
borderRadius: BorderRadius.circular(2.0), | ||
border: Border.all( | ||
color: Colors.white, | ||
width: 0.4, | ||
), | ||
), | ||
), | ||
centerTitle: true, | ||
actions: [ | ||
IconButton( | ||
icon: const Icon(Icons.check), | ||
onPressed: () { | ||
Navigator.pop(context); | ||
}, | ||
), | ||
const SizedBox(width: 10.0), | ||
], | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import 'dart:ui' as ui; | ||
import 'package:colorpicker/pages/pipette_tool_page.dart'; | ||
import 'package:colorpicker/src/state/color_picker_state_data.dart'; | ||
import 'package:colorpicker/src/state/color_picker_state_provider.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
|
||
// Helper function to create a sample image for testing | ||
Future<ui.Image> createTestImage() async { | ||
final pictureRecorder = ui.PictureRecorder(); | ||
final canvas = Canvas(pictureRecorder); | ||
final paint = Paint()..color = const Color(0xFFFF0000); | ||
canvas.drawRect(const Rect.fromLTWH(0, 0, 100, 100), paint); | ||
final picture = pictureRecorder.endRecording(); | ||
return picture.toImage(100, 100); | ||
} | ||
|
||
void main() { | ||
testWidgets('PipetteToolPage displays image and updates color on tap', | ||
(WidgetTester tester) async { | ||
// Create a sample image for testing | ||
final image = await createTestImage(); | ||
|
||
// Override the provider for testing | ||
final container = ProviderContainer(overrides: [ | ||
colorPickerStateProvider.overrideWith( | ||
() => ColorPickerState() | ||
..state = const ColorPickerStateData( | ||
currentColor: Colors.red, currentOpacity: 1.0), | ||
), | ||
]); | ||
|
||
// Build the widget | ||
await tester.pumpWidget( | ||
UncontrolledProviderScope( | ||
container: container, | ||
child: MaterialApp( | ||
home: PipetteToolPage(snapshot: image), | ||
), | ||
), | ||
); | ||
|
||
// Wait for the image to load | ||
await tester.pumpAndSettle(); | ||
|
||
// Verify that the image is displayed | ||
expect(find.byType(CustomPaint), findsOneWidget); | ||
|
||
// Tap on the image to update the color | ||
await tester.tap(find.byType(CustomPaint)); | ||
await tester.pumpAndSettle(); | ||
|
||
// Verify that the color update was called | ||
final colorState = container.read(colorPickerStateProvider); | ||
expect(colorState.currentColor, isNotNull); | ||
}); | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opening a new page for pipette tool is overkill. As mentioned in my previous comment the kotlin version of Pocket Paint doesnt open a new page. You can approach this tool the easiet way:
This simplyfies the user experience and matches the original implementation of the tool and removes alot of unnessary code.