Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions lib/ui/pages/landing_page/landing_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class _LandingPageState extends ConsumerState<LandingPage> {
late ProjectDatabase database;
late IFileService fileService;
late IImageService imageService;
bool _hasShownDatabaseErrorToast = false;

Future<List<Project>> _getProjects() async {
return database.projectDAO.getProjects();
Expand Down Expand Up @@ -84,9 +85,18 @@ class _LandingPageState extends ConsumerState<LandingPage> {

final db = ref.watch(ProjectDatabase.provider);
db.when(
data: (value) => database = value,
error: (err, stacktrace) =>
ToastUtils.showShortToast(message: 'Error: $err'),
data: (value) {
database = value;
_hasShownDatabaseErrorToast = false;
},
error: (err, stacktrace) {
if (_hasShownDatabaseErrorToast) return;
_hasShownDatabaseErrorToast = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
ToastUtils.showShortToast(message: 'Error: $err');
});
},
loading: () {},
);
final ioHandler = ref.watch(IOHandler.provider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,29 @@ class BottomNavBar extends ConsumerWidget {
NavigationDestination(
label: localizations.color,
icon: InkWell(
child: Container(
height: 24.0,
width: 24.0,
decoration: BoxDecoration(
color: currentPaint.color,
border: Border.all(
color: PaintroidTheme.of(context).onSurfaceColor,
width: 1.4,
child: Stack(
children: [
Container(
height: 24.0,
width: 24.0,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: PaintroidTheme.of(context).onSurfaceColor,
width: 1.4,
),
borderRadius: BorderRadius.circular(2.0),
),
),
borderRadius: BorderRadius.circular(2.0),
),
Container(
height: 24.0,
width: 24.0,
decoration: BoxDecoration(
color: currentPaint.color,
borderRadius: BorderRadius.circular(2.0),
),
),
],
),
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class _DrawingCanvasState extends ConsumerState<DrawingCanvas> {
final _transformationController = TransformationController();
var _pointersOnScreen = 0;
var _isZooming = false;
var _interactionWasZooming = false;
var _interactionHasPaintInput = false;
Offset _lastPointerUpPosition = Offset.zero;

void _resetCanvasScale({bool fitToScreen = false}) =>
Expand All @@ -49,14 +51,18 @@ class _DrawingCanvasState extends ConsumerState<DrawingCanvas> {
_pointersOnScreen++;
if (_pointersOnScreen >= 2) {
_isZooming = true;
_interactionWasZooming = true;
_interactionHasPaintInput = false;
_toolBoxStateNotifier.didSwitchToZooming();
}
}

void _onPointerUp(PointerUpEvent event) {
_pointersOnScreen--;
_lastPointerUpPosition = event.position;
if (_isZooming && _pointersOnScreen == 0) _isZooming = false;
if (_pointersOnScreen == 0) {
if (_isZooming) _isZooming = false;
}
}

Offset _globalToCanvas(Offset global) {
Expand All @@ -68,6 +74,7 @@ class _DrawingCanvasState extends ConsumerState<DrawingCanvas> {
void _onInteractionStart(ScaleStartDetails details) {
if (!_isZooming) {
if (details.pointerCount == 1) {
_interactionHasPaintInput = true;
_toolBoxStateNotifier.didTapDown(_globalToCanvas(details.focalPoint));
}
}
Expand All @@ -83,6 +90,16 @@ class _DrawingCanvasState extends ConsumerState<DrawingCanvas> {
}

void _onInteractionEnd(ScaleEndDetails details) {
if (_interactionWasZooming) {
_interactionWasZooming = false;
_interactionHasPaintInput = false;
return;
}

if (!_interactionHasPaintInput) {
return;
}

if (!_isZooming) {
_toolBoxStateNotifier.didTapUp(_globalToCanvas(_lastPointerUpPosition));
ref.read(canvasPainterProvider.notifier).repaint();
Expand All @@ -96,6 +113,8 @@ class _DrawingCanvasState extends ConsumerState<DrawingCanvas> {
break;
}
}

_interactionHasPaintInput = false;
}

@override
Expand Down
48 changes: 48 additions & 0 deletions test/integration/brush_tool_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,52 @@ void main() {
expect(color.toValue(), Colors.red.toValue());
});
}

if (testID == -1 || testID == 5) {
testWidgets('[BRUSH_TOOL]: opacity should stay stable after zoom',
(WidgetTester tester) async {
UIInteraction.initialize(tester);
await tester.pumpWidget(sut);
await UIInteraction.createNewImage();
UIInteraction.setColor(const Color.fromARGB(96, 0, 0, 255));
await UIInteraction.selectTool(ToolData.BRUSH.name);

await UIInteraction.tapAt(CanvasPosition.center);

final colorBeforeZoom = await UIInteraction.getPixelColor(
CanvasPosition.centerX,
CanvasPosition.centerY,
);
final alphaBeforeZoom = colorBeforeZoom.a;
expect(alphaBeforeZoom, greaterThan(0));
expect(alphaBeforeZoom, lessThan(255));

final ivFinder = find.byType(InteractiveViewer);
expect(ivFinder, findsOneWidget,
reason: 'InteractiveViewer should be present');

final interactiveViewer = tester.widget<InteractiveViewer>(ivFinder);
final transformationController =
interactiveViewer.transformationController!;

transformationController.value = Matrix4.diagonal3Values(2.0, 2.0, 1.0);
await tester.pumpAndSettle();

transformationController.value = Matrix4.identity();
await tester.pumpAndSettle();

final colorAfterZoom = await UIInteraction.getPixelColor(
CanvasPosition.centerX,
CanvasPosition.centerY,
);
final alphaAfterZoom = colorAfterZoom.a;

expect(
(alphaAfterZoom - alphaBeforeZoom).abs(),
lessThanOrEqualTo(2),
reason:
'Alpha channel should stay stable after zoom/repaint for existing strokes',
);
});
}
}
18 changes: 9 additions & 9 deletions test/utils/bottom_nav_bar_interactions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,15 @@ class BottomNavBarInteractions {
Future<BottomNavBarInteractions> checkActiveColor(Color color) async {
final thirdNavDestination = find.byType(NavigationDestination).at(2);
final activeColor = find.descendant(
of: thirdNavDestination,
matching: find.byWidgetPredicate((Widget widget) =>
widget is InkWell &&
widget.child is Container &&
(widget.child as Container).decoration is BoxDecoration &&
((widget.child as Container).decoration as BoxDecoration)
.color
?.toValue() ==
color.toValue()));
of: thirdNavDestination,
matching: find.byWidgetPredicate(
(Widget widget) =>
widget is Container &&
widget.decoration is BoxDecoration &&
(widget.decoration as BoxDecoration).color?.toValue() ==
color.toValue(),
),
);

expect(activeColor, findsOneWidget);
return this;
Expand Down
27 changes: 27 additions & 0 deletions test/widget/workspace_page/bottom_control_navigation_bar_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:paintroid/core/localization/app_localizations.dart';
import 'package:paintroid/core/providers/state/paint_provider.dart';
import 'package:paintroid/core/tools/tool_data.dart';
import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/tool_options/widgets/stroke_cap_chips.dart';
import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/tool_options/widgets/stroke_width_slider.dart';
Expand Down Expand Up @@ -184,5 +185,31 @@ void main() {
(bottomNavBarInteractions) =>
bottomNavBarInteractions.checkActiveColor(blueColor));
});

testWidgets('Test if semi-transparent color swatch has white background',
(WidgetTester tester) async {
const transparentBlue = Color.fromARGB(96, 0, 115, 204);

await tester.pumpWidget(sut);

final container =
ProviderScope.containerOf(tester.element(find.byType(WorkspacePage)));
container.read(paintProvider.notifier).updateColor(transparentBlue);
await tester.pumpAndSettle();

final thirdNavDestination = find.byType(NavigationDestination).at(2);

final whiteBackgroundFinder = find.descendant(
of: thirdNavDestination,
matching: find.byWidgetPredicate(
(Widget widget) =>
widget is Container &&
widget.decoration is BoxDecoration &&
(widget.decoration as BoxDecoration).color == Colors.white,
),
);

expect(whiteBackgroundFinder, findsOneWidget);
});
});
}