From 57d02dccd7bc77a22f14b7bd1eda5671eb0fc830 Mon Sep 17 00:00:00 2001 From: mohammad mobasher Date: Sat, 16 Oct 2021 17:39:09 +0330 Subject: [PATCH 1/3] use cached_network_image for show avatar --- example/pubspec.lock | 190 +++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 6 +- 2 files changed, 193 insertions(+), 3 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 52702e8..c7db3bd 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -15,6 +15,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + cached_network_image: + dependency: transitive + description: + name: cached_network_image + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" characters: dependency: transitive description: @@ -43,6 +64,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" cupertino_icons: dependency: "direct main" description: @@ -57,11 +85,39 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_blurhash: + dependency: transitive + description: + name: flutter_blurhash + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.2" flutter_test: dependency: "direct dev" description: flutter @@ -74,6 +130,20 @@ packages: relative: true source: path version: "1.0.0" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.3" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" matcher: dependency: transitive description: @@ -88,6 +158,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + octo_image: + dependency: transitive + description: + name: octo_image + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0+1" path: dependency: transitive description: @@ -95,6 +172,76 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + path_provider: + dependency: transitive + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.1" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.3" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.27.2" sky_engine: dependency: transitive description: flutter @@ -107,6 +254,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + sqflite: + dependency: transitive + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0+4" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1+1" stack_trace: dependency: transitive description: @@ -128,6 +289,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" term_glyph: dependency: transitive description: @@ -149,6 +317,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.5" vector_math: dependency: transitive description: @@ -156,5 +331,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" sdks: dart: ">=2.12.0 <3.0.0" + flutter: ">=2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index eac1695..09d443c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,17 +4,17 @@ version: 1.0.0 homepage: https://github.com/Akora-IngDKB/flutter_text_drawable.git environment: - sdk: '>=2.12.0 <3.0.0' + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter + cached_network_image: ^3.1.0 dev_dependencies: - test: + test: flutter_test: sdk: flutter - flutter: uses-material-design: true From 8f3cef0f11cbbf0c31e20beb3aa88b12ea4b539a Mon Sep 17 00:00:00 2001 From: mohammad mobasher Date: Sat, 16 Oct 2021 17:40:18 +0330 Subject: [PATCH 2/3] create new widget for loading image for avatar insted of test --- lib/flutter_text_drawable.dart | 1 + lib/src/avatar_drawable_widget.dart | 221 +++++++++++++++++++++++++ test/flutter_avatar_drawable_test.dart | 40 +++++ 3 files changed, 262 insertions(+) create mode 100644 lib/src/avatar_drawable_widget.dart create mode 100644 test/flutter_avatar_drawable_test.dart diff --git a/lib/flutter_text_drawable.dart b/lib/flutter_text_drawable.dart index f80084c..db4f792 100644 --- a/lib/flutter_text_drawable.dart +++ b/lib/flutter_text_drawable.dart @@ -1,4 +1,5 @@ library flutter_text_drawable; export 'package:flutter_text_drawable/src/text_drawable_list_tile.dart'; +export 'package:flutter_text_drawable/src/avatar_drawable_widget.dart'; export 'package:flutter_text_drawable/src/text_drawable_widget.dart'; diff --git a/lib/src/avatar_drawable_widget.dart b/lib/src/avatar_drawable_widget.dart new file mode 100644 index 0000000..e691a72 --- /dev/null +++ b/lib/src/avatar_drawable_widget.dart @@ -0,0 +1,221 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_text_drawable/src/color_generator.dart'; +import 'package:flutter_text_drawable/src/contrast_helper.dart'; +import 'package:cached_network_image/cached_network_image.dart'; + +// ignore: must_be_immutable +class AvatarDrawable extends StatefulWidget { + /// The text supplied. Only first character will be displayed. + final String urlAvatar; + + /// Height of the [TextDrawable] widget. + final double height; + + /// Width of the [TextDrawable] widget. + final double width; + + /// `TextStyle` for the `text` to be displayed. + final TextStyle? textStyle; + + /// Background color to for the widget. + /// If not specified, a random color will be generated. + final Color? backgroundColor; + + /// Shape of the widget. + /// Defaults to `BoxShape.circle`. + final BoxShape boxShape; + + /// Border radius of the widget. + /// Only specify this if `boxShape == BoxShape.circle`. + final BorderRadiusGeometry? borderRadius; + + /// Specify duration of animation when flipping between text and checked icon. + /// Defaults to current theme animation duration. + final Duration duration; + + /// Set to `true` when you want the widget to recognize taps. + /// Typical selection behaviour found in the Gmail app. + final bool isTappable; + + /// Callback received when widget is tapped. + /// It emits its current selected status. + final Function(bool)? onTap; + final Function(bool)? onLongPress; + + bool isSelected; + + /// Creates a customizable [TextDrawable] widget. + AvatarDrawable({ + Key? key, + required this.urlAvatar, + this.height = 48, + this.width = 48, + this.textStyle, + this.backgroundColor, + this.boxShape = BoxShape.circle, + this.borderRadius, + this.duration = const Duration(milliseconds: 500), + this.isTappable = false, + this.isSelected = false, + this.onTap, + this.onLongPress, + }) : super(key: key) { + assert( + boxShape == BoxShape.rectangle || borderRadius == null, + "Set boxShape = BoxShape.rectangle when borderRadius is specified", + ); + assert( + onTap == null || isTappable, + "isTappable must be true to receive onTapped callback", + ); + assert( + onLongPress == null || isTappable, + "isTappable must be true to receive onLongPress callback", + ); + } + + @override + _AvatarDrawableState createState() => _AvatarDrawableState(); +} + +class _AvatarDrawableState extends State { + Color? backgroundColor; + + @override + void initState() { + backgroundColor = + widget.backgroundColor ?? ColorGenerator().getRandomColor(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + double contrast = ContrastHelper.contrast([ + backgroundColor!.red, + backgroundColor!.green, + backgroundColor!.blue, + ], [ + 255, + 255, + 255 + ] /** white text */); + + return GestureDetector( + onTap: () { + if (widget.isTappable) { + setState(() { + widget.isSelected = !widget.isSelected; + }); + + if (widget.isTappable && widget.onTap != null) + widget.onTap!(widget.isSelected); + } + }, + onLongPress: () { + if (widget.isTappable) { + setState(() { + widget.isSelected = !widget.isSelected; + }); + + if (widget.isTappable && widget.onLongPress != null) + widget.onLongPress!(widget.isSelected); + } + }, + child: _Flippy( + duration: widget.duration, + isSelected: widget.isSelected, + front: _getSide(contrast), + back: _getSide(contrast), + ), + ); + } + + Widget _getSide(double contrast) { + return Container( + alignment: Alignment.center, + height: widget.height, + width: widget.width, + decoration: BoxDecoration( + color: !widget.isSelected ? backgroundColor : Colors.lightBlue, + shape: widget.boxShape, + borderRadius: widget.borderRadius, + ), + child: widget.isSelected + ? Icon( + Icons.check, + color: Colors.white, + ) + : ClipRRect( + //decoration: BoxDecoration( + borderRadius: BorderRadius.circular(40) + //borderRadius: BorderRadius.all(Radius.circular(13.0)) + // ) + , + child: CachedNetworkImage( + fit: BoxFit.cover, + height: widget.height, + width: widget.width, + imageUrl: widget.urlAvatar, + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => Icon(Icons.error), + ), + ), + ); + } +} + +class _Flippy extends StatelessWidget { + final bool isSelected; + final Widget front; + final Widget back; + final Duration? duration; + + const _Flippy({ + this.isSelected = false, + this.duration, + required this.front, + required this.back, + }); + + @override + Widget build(BuildContext context) { + return TweenAnimationBuilder( + duration: duration!, + curve: Curves.easeOut, + tween: Tween(begin: 0, end: isSelected ? 180 : 0), + builder: (context, dynamic value, child) { + var content = value >= 90 ? back : front; + return _RotationY( + rotationY: value, + child: _RotationY( + rotationY: value >= 90 ? 180 : 0, + child: content, + ), + ); + }, + ); + } +} + +class _RotationY extends StatelessWidget { + static const double degrees2Radians = pi / 180; + + final Widget child; + final double rotationY; + + const _RotationY({Key? key, required this.child, this.rotationY = 0}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Transform( + alignment: FractionalOffset.center, + transform: Matrix4.identity() + ..setEntry(3, 2, 0.001) + ..rotateY(rotationY * degrees2Radians), + child: child, + ); + } +} diff --git a/test/flutter_avatar_drawable_test.dart b/test/flutter_avatar_drawable_test.dart new file mode 100644 index 0000000..c813d31 --- /dev/null +++ b/test/flutter_avatar_drawable_test.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:flutter_text_drawable/flutter_text_drawable.dart'; +import 'package:flutter_text_drawable/src/avatar_drawable_widget.dart'; + +void main() { + testWidgets("Text Drawable Widget Test", (WidgetTester tester) async { + final String url = + "https://www.google.com/url?sa=i&url=https%3A%2F%2Fpixabay.com%2Fde%2Fphotos%2Fmann-person-portr%25C3%25A4t-gesicht-388104%2F&psig=AOvVaw0Hxm_n1SGg6LbJwNTrWTTS&ust=1634479447178000&source=images&cd=vfe&ved=0CAsQjRxqFwoTCKCx7MmMz_MCFQAAAAAdAAAAABAD"; + // Build the TextDrawable widget from a MaterialApp root + // To prevent text directionality exception + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: AvatarDrawable( + urlAvatar: url, + isTappable: true, + onLongPress: (bool selected) {}, + onTap: (bool selected) {}, + ), + ), + ), + ); + + // Expect to find the first character in the Text supplied but not an Icon + // expect(find.text(text[0]), findsOneWidget); + expect(find.byIcon(Icons.check), findsNothing); + + // Tap on the TextDrawable widget + await tester.tap(find.byType(TextDrawable)); + + // Wait for widget to be rebuilt + await tester.pumpAndSettle(); + + // Expect to find an Icon instead of a Text + expect(find.byIcon(Icons.check), findsOneWidget); + // expect(find.text(text[0]), findsNothing); + }); +} From bc1100b046a7f839ef06f071af1113e3ec4494e7 Mon Sep 17 00:00:00 2001 From: mohammad mobasher Date: Sat, 16 Oct 2021 17:41:43 +0330 Subject: [PATCH 3/3] remove test style from avatar_drawable_widget --- lib/src/avatar_drawable_widget.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/src/avatar_drawable_widget.dart b/lib/src/avatar_drawable_widget.dart index e691a72..4789af0 100644 --- a/lib/src/avatar_drawable_widget.dart +++ b/lib/src/avatar_drawable_widget.dart @@ -16,9 +16,6 @@ class AvatarDrawable extends StatefulWidget { /// Width of the [TextDrawable] widget. final double width; - /// `TextStyle` for the `text` to be displayed. - final TextStyle? textStyle; - /// Background color to for the widget. /// If not specified, a random color will be generated. final Color? backgroundColor; @@ -52,7 +49,6 @@ class AvatarDrawable extends StatefulWidget { required this.urlAvatar, this.height = 48, this.width = 48, - this.textStyle, this.backgroundColor, this.boxShape = BoxShape.circle, this.borderRadius,