Merge remote-tracking branch 'origin/develop' into cds-328

# Conflicts:
#	packages/stream_chat/CHANGELOG.md
This commit is contained in:
xsahil03x
2021-07-26 14:15:48 +05:30
38 changed files with 890 additions and 352 deletions
@@ -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 {