diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b28689..c648dcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,4 +8,7 @@ * Update test coverage ## 1.1.2 -* Update logo in readme \ No newline at end of file +* Update logo in readme + +## 1.1.3 +* Update Thicken Test \ No newline at end of file diff --git a/README.md b/README.md index 770576b..eec184c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Add this line to your pubspec.yaml. ```yaml dependencies: - thicken: ^1.1.2 + thicken: ^1.1.3 ``` ## Usage @@ -50,6 +50,7 @@ Thicken( | Property | Purpose | | ------------------ | ----------------------------------------------------------------------------------------------------------------- | +| pixelRatio | The sharpness quality of the stroke. | | thickness | The amount of thickness applied. _**(It is not recommended to set thickness greater than 1.0)**_ | | child | The child widget that will be thickened as multiple layers. | diff --git a/lib/thicken.dart b/lib/thicken.dart index 0e41ef3..2795a15 100644 --- a/lib/thicken.dart +++ b/lib/thicken.dart @@ -20,6 +20,7 @@ library thicken; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; /// A widget that creates a thick visual effect by stacking multiple @@ -50,22 +51,19 @@ import 'package:flutter/widgets.dart'; /// _**(It is not recommended to set thickness greater than 1.0)**_ /// /// [child] : The widget that will be "thickened" by drawing multiple layers. -class Thicken extends StatelessWidget { +class Thicken extends StatefulWidget { /// Creates a [Thicken] widget. /// /// The [thickness] parameter defines how many layers of the child widget /// will be drawn, as the [pixelRatio] parameter is used to define the sharpness of the strokes, /// and the [child] is the widget that will be thickened. - Thicken({ + const Thicken({ super.key, - this.pixelRatio = 1.0, + this.pixelRatio = 2.0, required this.thickness, required this.child, }); - /// The key used to convert the [child] widget to an image. - late final _key = GlobalKey(debugLabel: 'thicken.$key'); - /// The amount of thickness applied. The thickness controls how many layers /// of the [child] widget are created. A higher [thickness] value creates more /// layers and larger translations between them. @@ -89,58 +87,89 @@ class Thicken extends StatelessWidget { return 3 + (range * 2); } + @override + State createState() => _ThickenState(); +} + +class _ThickenState extends State { @override Widget build(BuildContext context) { - return FutureBuilder( - future: () async { - try { - // Wait until after the frame is built - await Future.delayed(Duration.zero); - - final boundary = _key.currentContext?.findRenderObject(); - if (boundary is RenderRepaintBoundary) { - final image = await boundary.toImage(pixelRatio: pixelRatio); - final byte = await image.toByteData( - format: ui.ImageByteFormat.png, - ); - - return byte?.buffer.asUint8List(); - } - - return null; - } catch (e) { - return null; - } - }(), - builder: (builder, snapshot) { - return Stack( - children: [ - ...List.generate( - layers * layers, - (index) { - final indexX = index % layers; - final indexY = index ~/ layers; - final offsetX = (1 - (2 * indexX / layers)) * thickness; - final offsetY = (1 - (2 * indexY / layers)) * thickness; - - if (index + indexY == 0) return const SizedBox(); - return Positioned.fill( - child: Transform.translate( + if (widget.thickness == 0.0) return child; + return Stack( + children: [ + if (image != null) + ...List.generate( + widget.layers * widget.layers, + (index) { + final indexX = index % widget.layers; + final indexY = index ~/ widget.layers; + final offsetX = + (1 - (2 * indexX / widget.layers)) * widget.thickness; + final offsetY = + (1 - (2 * indexY / widget.layers)) * widget.thickness; + + if (index + indexY == 0) return const SizedBox(); + return Positioned.fill( + child: Transform.translate( offset: Offset(offsetX, offsetY), - child: snapshot.data != null - ? Image.memory(snapshot.data!) - : child, - ), - ); - }, - ), - RepaintBoundary( - key: _key, - child: child, - ), - ], - ); - }, + child: Image.memory(image!)), + ); + }, + ), + RepaintBoundary( + key: _key, + child: child, + ), + ], ); } + + /// The child widget that will be thickened with multiple layers. + late final Widget child = widget.child; + + /// The key used to convert the [child] widget to an image. + late final GlobalKey _key; + + /// The generated image of the [child] widget. + Uint8List? image; + + /// Converts the [child] widget to an image. + /// + /// Returns a [Future] that completes with the [Uint8List] of the image. + Future toBitmap() async { + try { + // Wait until after the frame is built + await Future.delayed(Duration.zero); + + final boundary = _key.currentContext?.findRenderObject(); + if (boundary is RenderRepaintBoundary) { + final image = await boundary.toImage(pixelRatio: widget.pixelRatio); + final byte = await image.toByteData( + format: ui.ImageByteFormat.png, + ); + + return byte?.buffer.asUint8List(); + } + + return null; + } catch (e) { + return null; + } + } + + @override + void initState() { + super.initState(); + _key = GlobalKey(); + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + try { + final image = await toBitmap(); + setState(() => this.image = image); + } catch (_) { + // do nothing + } + }); + } + } } diff --git a/pubspec.yaml b/pubspec.yaml index daba076..738eace 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: thicken description: A widget that creates a thick visual effect by stacking multiple layers of a given child widget with slight translations based on the thickness value. -version: 1.1.2 +version: 1.1.3 homepage: https://inidia.app repository: https://github.com/Nialixus/thicken.git issue_tracker: https://github.com/Nialixus/thicken/issues @@ -11,7 +11,7 @@ screenshots: path: logo.png topics: - utility - - animation + - widget environment: sdk: '>=3.4.4 <4.0.0' diff --git a/test/thicken_test.dart b/test/thicken_test.dart index 0650978..f1cadeb 100644 --- a/test/thicken_test.dart +++ b/test/thicken_test.dart @@ -3,54 +3,134 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:thicken/thicken.dart'; void main() { - testWidgets('Thicken Widget Test', (WidgetTester tester) async { - debugPrint("✅ Test is started !"); + TestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Thicken Widget Initial Test', (WidgetTester tester) async { + debugPrint("\x1B[33m[1] Initial Test is started ... \x1B[32m✔️"); await tester.pumpWidget( - MaterialApp( + const MaterialApp( home: Thicken( thickness: 2.5, - child: const Text( + child: Text( 'Thickened Text', style: TextStyle(fontSize: 24), ), ), ), ); + await tester.pumpAndSettle(); + await tester.pump(); + // Verify that the text is displayed expect( find.text('Thickened Text'), findsWidgets, reason: 'Verify that the text is displayed', ); - debugPrint("✅ Thicken child widget found !"); + debugPrint("\x1B[33m[2] Verify that the text is displayed ... \x1B[32m✔️"); - // // Get the Thicken widget + // Verify that the 'Thicken' widget exists final thickenWidget = tester.widget(find.byType(Thicken)); expect( find.byWidget(thickenWidget), findsOne, - reason: 'Verify that the widget exists', + reason: "Verify that the 'Thicken' widget exists", ); - debugPrint("✅ Thicken widget found !"); + debugPrint( + "\x1B[33m[3] Verify that the 'Thicken' widget exists ... \x1B[32m✔️"); - // Calculate the total number of layers in the stack + // Verify that the 'Stack' widget exists final stack = tester.widget(find.byType(Stack)); expect( find.byWidget(stack), findsOne, - reason: 'Verify that the stack widget exists', + reason: "Verify that the 'Stack' widget exists", + ); + debugPrint( + "\x1B[33m[4] Verify that the 'Stack' widget inside 'Thicken' exists ... \x1B[32m✔️", ); - debugPrint("✅ ${stack.children.length} Stack children found !"); - await tester.pumpAndSettle(); // Calculate the expected number of layers - final expectedLayers = 3 + (thickenWidget.thickness.floor() * 2); + // Verify that the child of stack widget is only one expect( stack.children.length, - expectedLayers * expectedLayers + 1, - reason: 'Verify that the expected number of layers is rendered', + 1, + reason: 'Verify that the child of stack widget is only one', ); - debugPrint("✅ $expectedLayers Layers found as expected !"); - debugPrint("✅ Test is finished !"); + debugPrint( + "\x1B[33m[5] Verify that the child of stack widget is only ${stack.children.length} found ... \x1B[32m✔️", + ); + debugPrint("\x1B[33m[5] Initial test is finished ... \x1B[32m✔️"); + }); + + testWidgets('Thicken Widget Final Test', (tester) async { + debugPrint("\x1B[33m[6] Final test is started ... \x1B[32m✔️"); + await tester.runAsync(() async { + await tester.pumpWidget( + const MaterialApp( + home: Thicken( + thickness: 2.5, + child: Text( + 'Thickened Text', + style: TextStyle(fontSize: 24), + ), + ), + ), + ); + await Future.delayed(const Duration(seconds: 1)); + await tester.pumpAndSettle(); + await tester.pump(); + + // Verify that the text is displayed + expect( + find.text('Thickened Text'), + findsWidgets, + reason: 'Verify that the text is displayed', + ); + debugPrint( + "\x1B[33m[7] Verify that the text is displayed ... \x1B[32m✔️"); + + // Verify that the 'Thicken' widget exists + final thickenWidget = tester.widget(find.byType(Thicken)); + expect( + find.byWidget(thickenWidget), + findsOne, + reason: "Verify that the 'Thicken' widget exists", + ); + debugPrint( + "\x1B[33m[8] Verify that the 'Thicken' widget exists ... \x1B[32m✔️"); + + // Verify that the 'Stack' widget exists + final stack = tester.widget(find.byType(Stack)); + expect( + find.byWidget(stack), + findsOne, + reason: "Verify that the 'Stack' widget exists", + ); + debugPrint( + "\x1B[33m[9] Verify that the 'Stack' widget inside 'Thicken' exists ... \x1B[32m✔️", + ); + final calculated = 3 + (thickenWidget.thickness.floor() * 2); + final layers = calculated * calculated + 1; + expect( + stack.children.length, + layers, + reason: 'Verify that the child of stack widget is only one', + ); + debugPrint( + "\x1B[33m[10] Verify that the child of stack widget is equal to $layers layers. ... \x1B[32m✔️", + ); + + // Verify that the 'Image.memory' widget exists + expect( + tester.allWidgets.any((child) => child is Image), + isTrue, + reason: "Verify that the 'Image.memory' widget exists", + ); + debugPrint( + "\x1B[33m[11] Verify that the 'Image.memory' widget inside 'Thicken' exists ... \x1B[32m✔️", + ); + debugPrint("\x1B[33m[12] Final test is finished ... \x1B[32m✔️"); + }); }); }