Merge remote-tracking branch 'origin/develop' into cds-328
# Conflicts: # packages/stream_chat/CHANGELOG.md
@@ -1,9 +1,14 @@
|
||||
## Upcoming
|
||||
|
||||
✅ Added
|
||||
✅ Added
|
||||
|
||||
- Added `MessageListView.paginationLimit`
|
||||
- Allow the various ListView widgets to be themed via ThemeData classes
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- Fix floating date divider not having a fixed size
|
||||
|
||||
## 2.0.0
|
||||
|
||||
🛑️ Breaking Changes from `1.5.4`
|
||||
|
||||
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
compileSdkVersion 30
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
@@ -41,7 +41,7 @@ android {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.example.example"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
targetSdkVersion 30
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.50'
|
||||
ext.kotlin_version = '1.5.20'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.6.2'
|
||||
classpath 'com.android.tools.build:gradle:4.2.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
|
||||
|
||||
@@ -231,13 +231,21 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
);
|
||||
}
|
||||
|
||||
return ColoredBox(
|
||||
color: ChannelListViewTheme.of(context).backgroundColor!,
|
||||
child: LazyLoadScrollView(
|
||||
onEndOfPage: () => _channelListController.paginateData!(),
|
||||
child: child,
|
||||
),
|
||||
child = LazyLoadScrollView(
|
||||
onEndOfPage: () => _channelListController.paginateData!(),
|
||||
child: child,
|
||||
);
|
||||
|
||||
final backgroundColor = ChannelListViewTheme.of(context).backgroundColor;
|
||||
|
||||
if (backgroundColor != null) {
|
||||
return ColoredBox(
|
||||
color: backgroundColor,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
Widget _buildListView(BuildContext context, List<Channel> channels) {
|
||||
|
||||
@@ -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)],
|
||||
];
|
||||
@@ -166,11 +166,23 @@ class MessageListView extends StatefulWidget {
|
||||
this.showFloatingDateDivider = true,
|
||||
this.threadSeparatorBuilder,
|
||||
this.messageListController,
|
||||
this.reverse = true,
|
||||
this.paginationLimit = 20,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Function used to build a custom message widget
|
||||
final MessageBuilder? messageBuilder;
|
||||
|
||||
/// Whether the view scrolls in the reading direction.
|
||||
///
|
||||
/// Defaults to true.
|
||||
///
|
||||
/// See [ScrollView.reverse].
|
||||
final bool reverse;
|
||||
|
||||
/// Limit used during pagination
|
||||
final int paginationLimit;
|
||||
|
||||
/// Function used to build a custom system message widget
|
||||
final SystemMessageBuilder? systemMessageBuilder;
|
||||
|
||||
@@ -328,6 +340,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => MessageListCore(
|
||||
paginationLimit: widget.paginationLimit,
|
||||
messageFilter: widget.messageFilter,
|
||||
loadingBuilder: widget.loadingBuilder ??
|
||||
(context) => const Center(
|
||||
@@ -386,210 +399,217 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
1 // parent message
|
||||
;
|
||||
|
||||
return ColoredBox(
|
||||
color: MessageListViewTheme.of(context).backgroundColor!,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
ConnectionStatusBuilder(
|
||||
statusBuilder: (context, status) {
|
||||
var statusString = '';
|
||||
var showStatus = true;
|
||||
switch (status) {
|
||||
case ConnectionStatus.connected:
|
||||
statusString = 'Connected';
|
||||
showStatus = false;
|
||||
break;
|
||||
case ConnectionStatus.connecting:
|
||||
statusString = 'Reconnecting...';
|
||||
break;
|
||||
case ConnectionStatus.disconnected:
|
||||
statusString = 'Disconnected';
|
||||
break;
|
||||
}
|
||||
final child = Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
ConnectionStatusBuilder(
|
||||
statusBuilder: (context, status) {
|
||||
var statusString = '';
|
||||
var showStatus = true;
|
||||
switch (status) {
|
||||
case ConnectionStatus.connected:
|
||||
statusString = 'Connected';
|
||||
showStatus = false;
|
||||
break;
|
||||
case ConnectionStatus.connecting:
|
||||
statusString = 'Reconnecting...';
|
||||
break;
|
||||
case ConnectionStatus.disconnected:
|
||||
statusString = 'Disconnected';
|
||||
break;
|
||||
}
|
||||
|
||||
return InfoTile(
|
||||
showMessage: widget.showConnectionStateTile && showStatus,
|
||||
tileAnchor: Alignment.topCenter,
|
||||
childAnchor: Alignment.topCenter,
|
||||
message: statusString,
|
||||
child: LazyLoadScrollView(
|
||||
onPageScrollStart: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
return InfoTile(
|
||||
showMessage: widget.showConnectionStateTile && showStatus,
|
||||
tileAnchor: Alignment.topCenter,
|
||||
childAnchor: Alignment.topCenter,
|
||||
message: statusString,
|
||||
child: LazyLoadScrollView(
|
||||
onPageScrollStart: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
onStartOfPage: () async {
|
||||
_inBetweenList = false;
|
||||
if (!_upToDate) {
|
||||
_topPaginationActive = false;
|
||||
_bottomPaginationActive = true;
|
||||
return _paginateData(
|
||||
streamChannel,
|
||||
QueryDirection.bottom,
|
||||
);
|
||||
}
|
||||
},
|
||||
onEndOfPage: () async {
|
||||
_inBetweenList = false;
|
||||
_topPaginationActive = true;
|
||||
_bottomPaginationActive = false;
|
||||
return _paginateData(
|
||||
streamChannel,
|
||||
QueryDirection.top,
|
||||
);
|
||||
},
|
||||
onInBetweenOfPage: () {
|
||||
_inBetweenList = true;
|
||||
},
|
||||
child: ScrollablePositionedList.separated(
|
||||
key: ValueKey(initialIndex! + initialAlignment!),
|
||||
itemPositionsListener: _itemPositionListener,
|
||||
initialScrollIndex: initialIndex ?? 0,
|
||||
initialAlignment: initialAlignment ?? 0,
|
||||
physics: widget.scrollPhysics,
|
||||
itemScrollController: _scrollController,
|
||||
reverse: widget.reverse,
|
||||
addAutomaticKeepAlives: false,
|
||||
itemCount: itemCount,
|
||||
|
||||
// Item Count -> 8 (1 parent, 2 header+footer, 2 top+bottom, 3 messages)
|
||||
// eg: |Type| rev(|Index(item)|) rev(|Index(separator)|) |Index(item)| |Index(separator)|
|
||||
// ParentMessage -> 7 (count-1)
|
||||
// Separator(ThreadSeparator) -> 6 (count-2)
|
||||
// Header -> 6 (count-2)
|
||||
// Separator(Header -> 8??T -> 0||52) -> 5 (count-3)
|
||||
// TopLoader -> 5 (count-3)
|
||||
// Separator(0) -> 4 (count-4)
|
||||
// Message -> 4 (count-4)
|
||||
// Separator(2||8) -> 3 (count-5)
|
||||
// Message -> 3 (count-5)
|
||||
// Separator(2||8) -> 2 (count-6)
|
||||
// Message -> 2 (count-6)
|
||||
// Separator(0) -> 1 (count-7)
|
||||
// BottomLoader -> 1 (count-7)
|
||||
// Separator(Footer -> 8??30) -> 0 (count-8)
|
||||
// Footer -> 0 (count-8)
|
||||
|
||||
separatorBuilder: (context, i) {
|
||||
if (i == itemCount - 2) {
|
||||
if (widget.parentMessage == null) {
|
||||
return const Offstage();
|
||||
}
|
||||
return _buildThreadSeparator();
|
||||
}
|
||||
if (i == itemCount - 3) {
|
||||
if (widget.headerBuilder == null) {
|
||||
if (_isThreadConversation) return const Offstage();
|
||||
return const SizedBox(height: 52);
|
||||
}
|
||||
return const SizedBox(height: 8);
|
||||
}
|
||||
if (i == 0) {
|
||||
if (widget.footerBuilder == null) {
|
||||
return const SizedBox(height: 30);
|
||||
}
|
||||
return const SizedBox(height: 8);
|
||||
}
|
||||
|
||||
if (i == 1 || i == itemCount - 4) return const Offstage();
|
||||
|
||||
final message = messages[i - 1];
|
||||
final nextMessage = messages[i - 2];
|
||||
if (!Jiffy(message.createdAt.toLocal()).isSame(
|
||||
nextMessage.createdAt.toLocal(),
|
||||
Units.DAY,
|
||||
)) {
|
||||
final divider = widget.dateDividerBuilder != null
|
||||
? widget.dateDividerBuilder!(
|
||||
nextMessage.createdAt.toLocal(),
|
||||
)
|
||||
: DateDivider(
|
||||
dateTime: nextMessage.createdAt.toLocal(),
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: divider,
|
||||
);
|
||||
}
|
||||
final timeDiff =
|
||||
Jiffy(nextMessage.createdAt.toLocal()).diff(
|
||||
message.createdAt.toLocal(),
|
||||
Units.MINUTE,
|
||||
);
|
||||
|
||||
final isNextUserSame =
|
||||
message.user!.id == nextMessage.user?.id;
|
||||
final isThread = message.replyCount! > 0;
|
||||
final isDeleted = message.isDeleted;
|
||||
if (timeDiff >= 1 ||
|
||||
!isNextUserSame ||
|
||||
isThread ||
|
||||
isDeleted) {
|
||||
return const SizedBox(height: 8);
|
||||
}
|
||||
return const SizedBox(height: 2);
|
||||
},
|
||||
onStartOfPage: () async {
|
||||
_inBetweenList = false;
|
||||
if (!_upToDate) {
|
||||
_topPaginationActive = false;
|
||||
_bottomPaginationActive = true;
|
||||
return _paginateData(
|
||||
streamChannel,
|
||||
itemBuilder: (context, i) {
|
||||
if (i == itemCount - 1) {
|
||||
if (widget.parentMessage == null) {
|
||||
return const Offstage();
|
||||
}
|
||||
return buildParentMessage(widget.parentMessage!);
|
||||
}
|
||||
|
||||
if (i == itemCount - 2) {
|
||||
return widget.headerBuilder?.call(context) ??
|
||||
const Offstage();
|
||||
}
|
||||
|
||||
if (i == itemCount - 3) {
|
||||
return _buildLoadingIndicator(
|
||||
streamChannel!,
|
||||
QueryDirection.top,
|
||||
);
|
||||
}
|
||||
|
||||
if (i == 1) {
|
||||
return _buildLoadingIndicator(
|
||||
streamChannel!,
|
||||
QueryDirection.bottom,
|
||||
);
|
||||
}
|
||||
},
|
||||
onEndOfPage: () async {
|
||||
_inBetweenList = false;
|
||||
_topPaginationActive = true;
|
||||
_bottomPaginationActive = false;
|
||||
return _paginateData(
|
||||
streamChannel,
|
||||
QueryDirection.top,
|
||||
);
|
||||
},
|
||||
onInBetweenOfPage: () {
|
||||
_inBetweenList = true;
|
||||
},
|
||||
child: ScrollablePositionedList.separated(
|
||||
key: ValueKey(initialIndex! + initialAlignment!),
|
||||
itemPositionsListener: _itemPositionListener,
|
||||
initialScrollIndex: initialIndex ?? 0,
|
||||
initialAlignment: initialAlignment ?? 0,
|
||||
physics: widget.scrollPhysics,
|
||||
itemScrollController: _scrollController,
|
||||
reverse: true,
|
||||
addAutomaticKeepAlives: false,
|
||||
itemCount: itemCount,
|
||||
|
||||
// Item Count -> 8 (1 parent, 2 header+footer, 2 top+bottom, 3 messages)
|
||||
// eg: |Type| rev(|Index(item)|) rev(|Index(separator)|) |Index(item)| |Index(separator)|
|
||||
// ParentMessage -> 7 (count-1)
|
||||
// Separator(ThreadSeparator) -> 6 (count-2)
|
||||
// Header -> 6 (count-2)
|
||||
// Separator(Header -> 8??T -> 0||52) -> 5 (count-3)
|
||||
// TopLoader -> 5 (count-3)
|
||||
// Separator(0) -> 4 (count-4)
|
||||
// Message -> 4 (count-4)
|
||||
// Separator(2||8) -> 3 (count-5)
|
||||
// Message -> 3 (count-5)
|
||||
// Separator(2||8) -> 2 (count-6)
|
||||
// Message -> 2 (count-6)
|
||||
// Separator(0) -> 1 (count-7)
|
||||
// BottomLoader -> 1 (count-7)
|
||||
// Separator(Footer -> 8??30) -> 0 (count-8)
|
||||
// Footer -> 0 (count-8)
|
||||
if (i == 0) {
|
||||
return widget.footerBuilder?.call(context) ??
|
||||
const Offstage();
|
||||
}
|
||||
|
||||
separatorBuilder: (context, i) {
|
||||
if (i == itemCount - 2) {
|
||||
if (widget.parentMessage == null) {
|
||||
return const Offstage();
|
||||
}
|
||||
return _buildThreadSeparator();
|
||||
}
|
||||
if (i == itemCount - 3) {
|
||||
if (widget.headerBuilder == null) {
|
||||
if (_isThreadConversation) return const Offstage();
|
||||
return const SizedBox(height: 52);
|
||||
}
|
||||
return const SizedBox(height: 8);
|
||||
}
|
||||
if (i == 0) {
|
||||
if (widget.footerBuilder == null) {
|
||||
return const SizedBox(height: 30);
|
||||
}
|
||||
return const SizedBox(height: 8);
|
||||
}
|
||||
const bottomMessageIndex = 2; // 1 -> loader // 0 -> footer
|
||||
|
||||
if (i == 1 || i == itemCount - 4) return const Offstage();
|
||||
final message = messages[i - 2];
|
||||
Widget messageWidget;
|
||||
|
||||
final message = messages[i - 1];
|
||||
final nextMessage = messages[i - 2];
|
||||
if (!Jiffy(message.createdAt.toLocal()).isSame(
|
||||
nextMessage.createdAt.toLocal(),
|
||||
Units.DAY,
|
||||
)) {
|
||||
final divider = widget.dateDividerBuilder != null
|
||||
? widget.dateDividerBuilder!(
|
||||
nextMessage.createdAt.toLocal(),
|
||||
)
|
||||
: DateDivider(
|
||||
dateTime: nextMessage.createdAt.toLocal(),
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: divider,
|
||||
);
|
||||
}
|
||||
final timeDiff =
|
||||
Jiffy(nextMessage.createdAt.toLocal()).diff(
|
||||
message.createdAt.toLocal(),
|
||||
Units.MINUTE,
|
||||
if (i == bottomMessageIndex) {
|
||||
messageWidget = _buildBottomMessage(
|
||||
context,
|
||||
message,
|
||||
messages,
|
||||
streamChannel!,
|
||||
i - 2,
|
||||
);
|
||||
|
||||
final isNextUserSame =
|
||||
message.user!.id == nextMessage.user?.id;
|
||||
final isThread = message.replyCount! > 0;
|
||||
final isDeleted = message.isDeleted;
|
||||
if (timeDiff >= 1 ||
|
||||
!isNextUserSame ||
|
||||
isThread ||
|
||||
isDeleted) {
|
||||
return const SizedBox(height: 8);
|
||||
}
|
||||
return const SizedBox(height: 2);
|
||||
},
|
||||
itemBuilder: (context, i) {
|
||||
if (i == itemCount - 1) {
|
||||
if (widget.parentMessage == null) {
|
||||
return const Offstage();
|
||||
}
|
||||
return buildParentMessage(widget.parentMessage!);
|
||||
}
|
||||
|
||||
if (i == itemCount - 2) {
|
||||
return widget.headerBuilder?.call(context) ??
|
||||
const Offstage();
|
||||
}
|
||||
|
||||
if (i == itemCount - 3) {
|
||||
return _buildLoadingIndicator(
|
||||
streamChannel!,
|
||||
QueryDirection.top,
|
||||
);
|
||||
}
|
||||
|
||||
if (i == 1) {
|
||||
return _buildLoadingIndicator(
|
||||
streamChannel!,
|
||||
QueryDirection.bottom,
|
||||
);
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
return widget.footerBuilder?.call(context) ??
|
||||
const Offstage();
|
||||
}
|
||||
|
||||
const bottomMessageIndex =
|
||||
2; // 1 -> loader // 0 -> footer
|
||||
|
||||
final message = messages[i - 2];
|
||||
Widget messageWidget;
|
||||
|
||||
if (i == bottomMessageIndex) {
|
||||
messageWidget = _buildBottomMessage(
|
||||
context,
|
||||
message,
|
||||
messages,
|
||||
streamChannel!,
|
||||
i - 2,
|
||||
);
|
||||
} else {
|
||||
messageWidget = buildMessage(message, messages, i - 2);
|
||||
}
|
||||
return messageWidget;
|
||||
},
|
||||
),
|
||||
} else {
|
||||
messageWidget = buildMessage(message, messages, i - 2);
|
||||
}
|
||||
return messageWidget;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (widget.showScrollToBottom) _buildScrollToBottom(),
|
||||
if (widget.showFloatingDateDivider)
|
||||
_buildFloatingDateDivider(itemCount),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (widget.showScrollToBottom) _buildScrollToBottom(),
|
||||
if (widget.showFloatingDateDivider)
|
||||
_buildFloatingDateDivider(itemCount),
|
||||
],
|
||||
);
|
||||
|
||||
final backgroundColor = MessageListViewTheme.of(context).backgroundColor;
|
||||
|
||||
if (backgroundColor != null) {
|
||||
return ColoredBox(
|
||||
color: backgroundColor,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
Widget _buildThreadSeparator() {
|
||||
@@ -614,7 +634,10 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
}
|
||||
|
||||
Positioned _buildFloatingDateDivider(int itemCount) => Positioned(
|
||||
top: 20,
|
||||
top: widget.reverse ? 20 : null,
|
||||
bottom: widget.reverse ? null : 20,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: BetterStreamBuilder<Iterable<ItemPosition>>(
|
||||
initialData: _itemPositionListener.itemPositions.value,
|
||||
stream: _itemPositionStream,
|
||||
@@ -646,7 +669,9 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
);
|
||||
|
||||
Future<void> _paginateData(
|
||||
StreamChannelState? channel, QueryDirection direction) =>
|
||||
StreamChannelState? channel,
|
||||
QueryDirection direction,
|
||||
) =>
|
||||
_messageListController.paginateData!(direction: direction);
|
||||
|
||||
int? _getTopElementIndex(Iterable<ItemPosition> values) {
|
||||
@@ -706,9 +731,13 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
);
|
||||
}
|
||||
},
|
||||
child: StreamSvgIcon.down(
|
||||
color: _streamTheme.colorTheme.textHighEmphasis,
|
||||
),
|
||||
child: widget.reverse
|
||||
? StreamSvgIcon.down(
|
||||
color: _streamTheme.colorTheme.textHighEmphasis,
|
||||
)
|
||||
: StreamSvgIcon.up(
|
||||
color: _streamTheme.colorTheme.textHighEmphasis,
|
||||
),
|
||||
),
|
||||
if (showUnreadCount)
|
||||
Positioned(
|
||||
|
||||
@@ -144,61 +144,72 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
||||
widget.messageSearchListController ?? _defaultController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ColoredBox(
|
||||
color: MessageSearchListViewTheme.of(context).backgroundColor!,
|
||||
child: MessageSearchListCore(
|
||||
filters: widget.filters,
|
||||
sortOptions: widget.sortOptions,
|
||||
messageQuery: widget.messageQuery,
|
||||
paginationParams: widget.paginationParams,
|
||||
messageFilters: widget.messageFilters,
|
||||
messageSearchListController: _messageSearchListController,
|
||||
emptyBuilder: widget.emptyBuilder ??
|
||||
(context) => LayoutBuilder(
|
||||
builder: (context, viewportConstraints) =>
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: const Center(
|
||||
child: Text('There are no messages currently'),
|
||||
),
|
||||
),
|
||||
Widget build(BuildContext context) {
|
||||
final messageSearchListCore = MessageSearchListCore(
|
||||
filters: widget.filters,
|
||||
sortOptions: widget.sortOptions,
|
||||
messageQuery: widget.messageQuery,
|
||||
paginationParams: widget.paginationParams,
|
||||
messageFilters: widget.messageFilters,
|
||||
messageSearchListController: _messageSearchListController,
|
||||
emptyBuilder: widget.emptyBuilder ??
|
||||
(context) => LayoutBuilder(
|
||||
builder: (context, viewportConstraints) =>
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: const Center(
|
||||
child: Text('There are no messages currently'),
|
||||
),
|
||||
),
|
||||
errorBuilder: widget.errorBuilder ??
|
||||
(BuildContext context, dynamic error) {
|
||||
if (error is Error) {
|
||||
print(error.stackTrace);
|
||||
}
|
||||
return InfoTile(
|
||||
showMessage: widget.showErrorTile,
|
||||
tileAnchor: Alignment.topCenter,
|
||||
childAnchor: Alignment.topCenter,
|
||||
message: 'An error occurred.',
|
||||
child: Container(),
|
||||
);
|
||||
},
|
||||
loadingBuilder: widget.loadingBuilder ??
|
||||
(context) => LayoutBuilder(
|
||||
builder: (context, viewportConstraints) =>
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
errorBuilder: widget.errorBuilder ??
|
||||
(BuildContext context, dynamic error) {
|
||||
if (error is Error) {
|
||||
print(error.stackTrace);
|
||||
}
|
||||
return InfoTile(
|
||||
showMessage: widget.showErrorTile,
|
||||
tileAnchor: Alignment.topCenter,
|
||||
childAnchor: Alignment.topCenter,
|
||||
message: 'An error occurred.',
|
||||
child: Container(),
|
||||
);
|
||||
},
|
||||
loadingBuilder: widget.loadingBuilder ??
|
||||
(context) => LayoutBuilder(
|
||||
builder: (context, viewportConstraints) =>
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
childBuilder: widget.childBuilder ?? _buildListView,
|
||||
),
|
||||
),
|
||||
),
|
||||
childBuilder: widget.childBuilder ?? _buildListView,
|
||||
);
|
||||
|
||||
final backgroundColor =
|
||||
MessageSearchListViewTheme.of(context).backgroundColor;
|
||||
|
||||
if (backgroundColor != null) {
|
||||
return ColoredBox(
|
||||
color: backgroundColor,
|
||||
child: messageSearchListCore,
|
||||
);
|
||||
}
|
||||
|
||||
return messageSearchListCore;
|
||||
}
|
||||
|
||||
Widget _separatorBuilder(BuildContext context, int index) => Container(
|
||||
height: 1,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -37,6 +37,18 @@ class StreamSvgIcon extends StatelessWidget {
|
||||
height: size,
|
||||
);
|
||||
|
||||
/// [StreamSvgIcon] type
|
||||
factory StreamSvgIcon.up({
|
||||
double? size,
|
||||
Color? color,
|
||||
}) =>
|
||||
StreamSvgIcon(
|
||||
assetName: 'Icon_up.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
|
||||
/// [StreamSvgIcon] type
|
||||
factory StreamSvgIcon.attach({
|
||||
double? size,
|
||||
|
||||
@@ -160,38 +160,48 @@ class _UserListViewState extends State<UserListView>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final child = ColoredBox(
|
||||
color: UserListViewTheme.of(context).backgroundColor!,
|
||||
child: UserListCore(
|
||||
errorBuilder: widget.errorBuilder ??
|
||||
(BuildContext context, Object err) => _buildError(err),
|
||||
emptyBuilder: widget.emptyBuilder ?? (context) => _buildEmpty(),
|
||||
loadingBuilder: widget.loadingBuilder ??
|
||||
(context) => LayoutBuilder(
|
||||
builder: (context, viewportConstraints) =>
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
final userListCore = UserListCore(
|
||||
errorBuilder: widget.errorBuilder ??
|
||||
(BuildContext context, Object err) => _buildError(err),
|
||||
emptyBuilder: widget.emptyBuilder ?? (context) => _buildEmpty(),
|
||||
loadingBuilder: widget.loadingBuilder ??
|
||||
(context) => LayoutBuilder(
|
||||
builder: (context, viewportConstraints) =>
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
listBuilder:
|
||||
widget.listBuilder ?? (context, list) => _buildListView(list),
|
||||
pagination: widget.pagination,
|
||||
sort: widget.sort,
|
||||
filter: widget.filter,
|
||||
presence: widget.presence,
|
||||
groupAlphabetically: widget.groupAlphabetically,
|
||||
userListController: _userListController,
|
||||
),
|
||||
),
|
||||
listBuilder:
|
||||
widget.listBuilder ?? (context, list) => _buildListView(list),
|
||||
pagination: widget.pagination,
|
||||
sort: widget.sort,
|
||||
filter: widget.filter,
|
||||
presence: widget.presence,
|
||||
groupAlphabetically: widget.groupAlphabetically,
|
||||
userListController: _userListController,
|
||||
);
|
||||
|
||||
final backgroundColor = UserListViewTheme.of(context).backgroundColor;
|
||||
|
||||
Widget child;
|
||||
|
||||
if (backgroundColor != null) {
|
||||
child = ColoredBox(
|
||||
color: backgroundColor,
|
||||
child: userListCore,
|
||||
);
|
||||
} else {
|
||||
child = userListCore;
|
||||
}
|
||||
|
||||
if (!widget.pullToRefresh) {
|
||||
return child;
|
||||
} else {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path transform="translate(24, 24) rotate(-180)" d="M5.30605 8.30597C5.11052 8.50222 5.00073 8.76795 5.00073 9.04498C5.00073 9.322 5.11052 9.58774 5.30605 9.78398L11.2061 15.697C11.4321 15.923 11.7351 16.024 12.0291 16C12.17 16.0032 12.3102 15.9778 12.441 15.9252C12.5718 15.8726 12.6906 15.7939 12.7901 15.694L18.6941 9.78398C18.8898 9.58786 18.9998 9.32208 18.9998 9.04498C18.9998 8.76787 18.8898 8.50209 18.6941 8.30597C18.5972 8.20898 18.4821 8.13203 18.3555 8.07952C18.2289 8.02702 18.0931 8 17.9561 8C17.819 8 17.6832 8.02702 17.5566 8.07952C17.43 8.13203 17.3149 8.20898 17.2181 8.30597L11.9981 13.533L6.78005 8.30597C6.68327 8.20901 6.56831 8.13208 6.44176 8.07959C6.31521 8.0271 6.17956 8.00008 6.04255 8.00008C5.90555 8.00008 5.76989 8.0271 5.64334 8.07959C5.51679 8.13208 5.40184 8.20901 5.30505 8.30597H5.30605Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 943 B |
@@ -30,8 +30,8 @@ dependencies:
|
||||
lottie: ^1.0.1
|
||||
meta: ^1.3.0
|
||||
path_provider: ^2.0.1
|
||||
photo_manager: ^1.1.6
|
||||
photo_view: ^0.11.1
|
||||
photo_manager: ^1.2.6+1
|
||||
photo_view: ^0.12.0
|
||||
rxdart: ^0.27.0
|
||||
scrollable_positioned_list: ^0.2.0-nullsafety.0
|
||||
share_plus: ^2.0.3
|
||||
|
||||
@@ -1,9 +1,35 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:golden_toolkit/golden_toolkit.dart';
|
||||
|
||||
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
|
||||
await loadAppFonts();
|
||||
goldenFileComparator =
|
||||
CustomGoldenFileComparator(Uri.parse('test/src/goldens'));
|
||||
return testMain();
|
||||
}
|
||||
|
||||
class CustomGoldenFileComparator extends LocalFileComparator {
|
||||
CustomGoldenFileComparator(Uri testFile) : super(testFile);
|
||||
|
||||
@override
|
||||
Future<bool> compare(Uint8List imageBytes, Uri golden) async {
|
||||
final result = await GoldenFileComparator.compareLists(
|
||||
imageBytes,
|
||||
await getGoldenBytes(golden),
|
||||
);
|
||||
|
||||
if (!result.passed && result.diffPercent > 0.05) {
|
||||
final error = await generateFailureOutput(result, golden, basedir);
|
||||
throw FlutterError(error);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> update(Uri golden, Uint8List imageBytes) =>
|
||||
super.update(golden, imageBytes);
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
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 {
|
||||
|
||||