Merge pull request #560 from GetStream/replace_random_png

feat(ui): Replace random png
This commit is contained in:
Salvatore Giordano
2021-07-26 09:33:19 +02:00
committed by GitHub
11 changed files with 391 additions and 31 deletions
@@ -0,0 +1,258 @@
import 'dart:math';
import 'dart:ui';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
/// Fallback user avatar with a polygon gradient overlayed with text
class GradientAvatar extends StatefulWidget {
/// Constructor for [GradientAvatar]
const GradientAvatar({
Key? key,
required this.name,
required this.userId,
}) : super(key: key);
/// Name of user to shorten and display
final String name;
/// ID of user to be used for key
final String userId;
@override
_GradientAvatarState createState() => _GradientAvatarState();
}
class _GradientAvatarState extends State<GradientAvatar> {
@override
Widget build(BuildContext context) => Center(
child: RepaintBoundary(
child: CustomPaint(
painter: DemoPainter(
widget.userId,
getShortenedName(widget.name),
DefaultTextStyle.of(context).style.fontFamily ?? 'Roboto',
),
child: const SizedBox.expand(),
),
),
);
String getShortenedName(String name) {
var parts = name.split(' ')..removeWhere((e) => e == '');
if (parts.length > 2) {
parts = parts.take(2).toList();
}
var result = '';
for (var i = 0; i < parts.length; i++) {
result = result + parts[i][0].toUpperCase();
}
return result;
}
}
/// Painter for bg polygon gradient
class DemoPainter extends CustomPainter {
/// Constructor for [DemoPainter]
DemoPainter(
this.userId,
this.username,
this.fontFamily,
);
/// Init grid row count
static const int rowCount = 5;
/// Init grid column count
static const int columnCount = 5;
/// User ID used for key
String userId;
/// User name to display
String username;
/// Font family to use
String fontFamily;
@override
void paint(Canvas canvas, Size size) {
final rowUnit = size.width / columnCount;
final columnUnit = size.height / rowCount;
final rand = Random(userId.length);
final squares = <Offset4>[];
final points = <Offset>{};
final gradient = colorGradients[rand.nextInt(colorGradients.length)];
for (var i = 0; i < rowCount; i++) {
for (var j = 0; j < columnCount; j++) {
final off1 = Offset(rowUnit * j, columnUnit * i);
final off2 = Offset(rowUnit * (j + 1), columnUnit * i);
final off3 = Offset(rowUnit * (j + 1), columnUnit * (i + 1));
final off4 = Offset(rowUnit * j, columnUnit * (i + 1));
points.addAll([off1, off2, off3, off4]);
final pointsList = points.toList();
final p1 = pointsList.indexOf(off1);
final p2 = pointsList.indexOf(off2);
final p3 = pointsList.indexOf(off3);
final p4 = pointsList.indexOf(off4);
squares.add(
Offset4(p1, p2, p3, p4, i, j, rowCount, columnCount, gradient));
}
}
final list = transformPoints(points, size);
squares.forEach((e) => e.draw(canvas, list));
final smallerSide = size.width > size.height ? size.width : size.height;
final textSize = smallerSide / 3;
final dxShift = (username.length == 2 ? 1.45 : 0.9) * textSize / 2;
final dyShift = (username.length == 2 ? 1.0 : 1.65) * textSize / 2;
final fontSize = username.length == 2 ? textSize : textSize * 1.5;
TextPainter(
text: TextSpan(
text: username,
style: TextStyle(
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: FontWeight.w500,
color: Colors.white.withOpacity(0.7),
),
),
textAlign: TextAlign.center,
textDirection: TextDirection.ltr)
..layout(maxWidth: size.width)
..paint(
canvas,
Offset(
(size.width / 2) - dxShift,
(size.height / 2) - dyShift,
),
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
/// Transforms initial grid into a polygon grid
List<Offset> transformPoints(Set<Offset> points, Size size) {
final transformedList = <Offset>[];
final orgList = points.toList();
final rand = Random(userId.length);
for (var i = 0; i < points.length; i++) {
final orgDx = orgList[i].dx;
final orgDy = orgList[i].dy;
if (orgDx == 0 ||
orgDy == 0 ||
orgDx == size.width ||
orgDy == size.height) {
transformedList.add(Offset(orgDx, orgDy));
continue;
}
final sign1 = rand.nextInt(2) == 1 ? 1 : -1;
final sign2 = rand.nextInt(2) == 1 ? 1 : -1;
final dx = 0.6 * sign1 * rand.nextInt(size.width ~/ columnCount);
final dy = 0.6 * sign2 * rand.nextInt(size.height ~/ rowCount);
transformedList.add(Offset(orgDx + dx, orgDy + dy));
}
return transformedList;
}
}
/// Class for storing and drawing four points of a polygon
class Offset4 {
/// Constructor for [Offset4]
Offset4(
this.p1,
this.p2,
this.p3,
this.p4,
this.row,
this.column,
this.rowSize,
this.colSize,
this.gradient,
);
/// Point 1
int p1;
/// Point 2
int p2;
/// Point 3
int p3;
/// Point 4
int p4;
/// Position of polygon on grid
int row;
/// Position of polygon on grid
int column;
/// Max row size
int rowSize;
/// Max col size
int colSize;
/// Gradient to be applied to polygon
List<Color> gradient;
/// Draw the polygon on canvas
void draw(Canvas canvas, List<Offset> points) {
final paint = Paint()
..color = Color.fromARGB(255, Random().nextInt(255),
Random().nextInt(255), Random().nextInt(255))
..shader = ui.Gradient.linear(
points[p1],
points[p3],
gradient,
);
final backgroundPath = Path()
..moveTo(points[p1].dx, points[p1].dy)
..lineTo(points[p2].dx, points[p2].dy)
..lineTo(points[p3].dx, points[p3].dy)
..lineTo(points[p4].dx, points[p4].dy)
..lineTo(points[p1].dx, points[p1].dy)
..close();
canvas.drawPath(backgroundPath, paint);
}
}
/// Gradient list for polygons
const colorGradients = [
[Color(0xffffafbd), Color(0xffffc3a0)],
[Color(0xff2193b0), Color(0xff6dd5ed)],
[Color(0xffcc2b5e), Color(0xff753a88)],
[Color(0xffee9ca7), Color(0xffffdde1)],
[Color(0xff42275a), Color(0xff734b6d)],
[Color(0xffde6262), Color(0xffffb88c)],
[Color(0xff56ab2f), Color(0xffa8e063)],
[Color(0xff614385), Color(0xff516395)],
[Color(0xffeacda3), Color(0xffd6ae7b)],
[Color(0xff02aab0), Color(0xff00cdac)],
];
@@ -1,12 +1,11 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/channel_header.dart';
import 'package:stream_chat_flutter/src/channel_preview.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/gradient_avatar.dart';
import 'package:stream_chat_flutter/src/message_input.dart';
import 'package:stream_chat_flutter/src/reaction_icon.dart';
import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -310,10 +309,9 @@ class StreamChatThemeData {
colorTheme: colorTheme,
primaryIconTheme: iconTheme,
defaultUserImage: (context, user) => Center(
child: CachedNetworkImage(
filterQuality: FilterQuality.high,
imageUrl: getRandomPicUrl(user),
fit: BoxFit.cover,
child: GradientAvatar(
name: user.name,
userId: user.id,
),
),
channelPreviewTheme: channelPreviewTheme,
@@ -14,6 +14,7 @@ export 'src/deleted_message.dart';
export 'src/full_screen_media.dart';
export 'src/gallery_footer.dart';
export 'src/gallery_header.dart';
export 'src/gradient_avatar.dart';
export 'src/info_tile.dart';
export 'src/mention_tile.dart';
export 'src/message_action.dart';
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:mocktail/mocktail.dart';
import 'package:stream_chat_flutter/src/gradient_avatar.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'control test',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(
MaterialApp(
home: StreamChat(
client: client,
child: const Scaffold(
body: Center(
child: SizedBox(
width: 100,
height: 100,
child: GradientAvatar(name: 'demo user', userId: 'demo123'),
),
),
),
),
),
);
expect(find.byType(GradientAvatar), findsOneWidget);
},
);
testGoldens(
'golden test for the name "demo user"',
(WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
width: 100,
height: 100,
child: GradientAvatar(name: 'demo user', userId: 'demo123'),
),
),
),
),
);
await screenMatchesGolden(tester, 'gradient_avatar_0');
},
);
testGoldens(
'golden test for the name "demo"',
(WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
width: 100,
height: 100,
child: GradientAvatar(name: 'demo', userId: 'demo1'),
),
),
),
),
);
await screenMatchesGolden(tester, 'gradient_avatar_1');
},
);
testGoldens(
'control special character test',
(WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
width: 100,
height: 100,
child: GradientAvatar(
name: 'd123@/d de:\$as',
userId: 'demo123',
),
),
),
),
),
);
await screenMatchesGolden(tester, 'gradient_avatar_2');
},
);
testGoldens(
'control special character test 2',
(WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
width: 100,
height: 100,
child: GradientAvatar(name: '123@/d \$as', userId: 'demo123'),
),
),
),
),
);
await screenMatchesGolden(tester, 'gradient_avatar_3');
},
);
}
@@ -80,7 +80,7 @@ void main() {
});
const messageText = '''
a message.
a message.
with multiple lines
and a list:
- a. okasd
@@ -6,32 +6,8 @@ import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
import 'simple_frame.dart';
void main() {
testGoldens(
'it should show no reactions',
(WidgetTester tester) async {
await tester.pumpWidgetBuilder(
SimpleFrame(
child: StreamChatTheme(
data: StreamChatThemeData(),
child: const SizedBox(
child: ReactionBubble(
reactions: [],
borderColor: Colors.black,
backgroundColor: Colors.white,
maskColor: Colors.white,
),
),
),
),
surfaceSize: const Size(100, 100),
);
await screenMatchesGolden(tester, 'reaction_bubble_0');
},
);
testGoldens(
'it should show a like - light theme',
(WidgetTester tester) async {