...
 
Commits (10)
......@@ -34,7 +34,6 @@ class ChatSettingsBloc extends Bloc<ChatSettingsEvent, ChatSettingsState> {
ChatSettingsBloc(this._matrix, this._room);
@override
// TODO: implement initialState
ChatSettingsState get initialState => ChatSettingsUninitialized();
@override
......@@ -42,10 +41,12 @@ class ChatSettingsBloc extends Bloc<ChatSettingsEvent, ChatSettingsState> {
if (event is FetchMembers) {
final me = await _room.members[_matrix.user.id];
final members = List.of(
var members = List.of(
await _room.members.get(upTo: !event.all ? 6 : _room.members.count),
);
members = members.where((u) => u.state.membership is Joined).toList();
members.remove(me);
members.insert(0, me);
......
......@@ -51,13 +51,6 @@ class ChatSettingsPage extends StatefulWidget {
class _ChatSettingsPageState extends State<ChatSettingsPage> {
Room get room => widget.room;
bool _previewMembers;
@override
void initState() {
super.initState();
_previewMembers = true;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
......@@ -106,9 +99,9 @@ class _ChatSettingsPageState extends State<ChatSettingsPage> {
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate.fixed([
_buildDescription(),
if (!room.isDirect) _Description(),
SizedBox(height: 16),
_buildMembers(),
if (!room.isDirect) _MemberList(room: room)
]),
)
],
......@@ -116,12 +109,15 @@ class _ChatSettingsPageState extends State<ChatSettingsPage> {
),
);
}
}
Widget _buildDescription() {
if (room.isDirect) {
return Container(height: 0);
}
class _Description extends StatelessWidget {
final String description;
const _Description({Key key, this.description}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
......@@ -142,9 +138,9 @@ class _ChatSettingsPageState extends State<ChatSettingsPage> {
),
SizedBox(height: 4),
Text(
room.topic ?? l(context).noDescriptionSet,
description ?? l(context).noDescriptionSet,
style: TextStyle(
fontStyle: room.topic == null
fontStyle: description == null
? FontStyle.italic
: FontStyle.normal,
),
......@@ -157,12 +153,27 @@ class _ChatSettingsPageState extends State<ChatSettingsPage> {
],
);
}
}
class _MemberList extends StatefulWidget {
final Room room;
const _MemberList({Key key, @required this.room}) : super(key: key);
@override
State<StatefulWidget> createState() => _MemberListState();
}
Widget _buildMembers() {
if (room.isDirect) {
return Container(height: 0);
}
class _MemberListState extends State<_MemberList> {
bool _previewMembers;
@override
void initState() {
super.initState();
_previewMembers = true;
}
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
......@@ -174,7 +185,7 @@ class _ChatSettingsPageState extends State<ChatSettingsPage> {
Padding(
padding: EdgeInsets.only(left: 16, top: 16),
child: Text(
l(context).xParticipants(room.members.count),
l(context).xParticipants(widget.room.members.count),
style: TextStyle(
color: redOnBackground(context),
fontSize: 16,
......@@ -191,7 +202,8 @@ class _ChatSettingsPageState extends State<ChatSettingsPage> {
}
final isLoading = state is MembersLoading;
final allShown = members.length == room.members.count;
final allShown =
members.length == widget.room.members.count;
return MediaQuery.removePadding(
context: context,
......@@ -207,10 +219,15 @@ class _ChatSettingsPageState extends State<ChatSettingsPage> {
itemBuilder: (BuildContext context, int index) {
// Item after all members
if (index == members.length) {
return _buildShowMoreItem(
context,
members.length,
isLoading,
return _ShowMoreItem(
room: widget.room,
shownMembersCount: members.length,
isLoading: isLoading,
onTap: () => setState(() {
BlocProvider.of<ChatSettingsBloc>(context)
.add(FetchMembers(all: true));
_previewMembers = false;
}),
);
}
......@@ -222,6 +239,7 @@ class _ChatSettingsPageState extends State<ChatSettingsPage> {
);
},
),
SizedBox(height: 12)
],
),
),
......@@ -229,16 +247,29 @@ class _ChatSettingsPageState extends State<ChatSettingsPage> {
],
);
}
}
class _ShowMoreItem extends StatelessWidget {
final Room room;
final int shownMembersCount;
final bool isLoading;
final VoidCallback onTap;
const _ShowMoreItem({
Key key,
@required this.room,
@required this.shownMembersCount,
@required this.isLoading,
@required this.onTap,
}) : super(key: key);
Widget _buildShowMoreItem(BuildContext context, int count, bool isWaiting) {
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(Icons.keyboard_arrow_down, size: 32),
title: Text(l(context).xMore(room.members.count - count)),
subtitle: isWaiting ? LinearProgressIndicator() : null,
onTap: () => setState(() {
BlocProvider.of<ChatSettingsBloc>(context).add(FetchMembers(all: true));
_previewMembers = false;
}),
title: Text(l(context).xMore(room.members.count - shownMembersCount)),
subtitle: isLoading ? LinearProgressIndicator() : null,
onTap: onTap,
);
}
}
......@@ -49,7 +49,8 @@ class ImageContent extends StatelessWidget {
final height = (event.content.info?.height ??
0 / (event.content.info?.width ?? 0 / _width))
.clamp(_minHeight, _maxHeight);
.clamp(_minHeight, _maxHeight)
.toDouble();
return Container(
width: _width,
......@@ -70,7 +71,7 @@ class ImageContent extends StatelessWidget {
),
),
if (MessageInfo.necessary(context)) _MessageInfo(),
if (bubble.message.isMine && bubble.isStartOfGroup) _Sender(),
if (Sender.necessary(context)) _Sender(),
Positioned.fill(
child: Clickable(
extraMaterial: true,
......
......@@ -29,20 +29,21 @@ import '../../message.dart';
///
/// Must have a [MessageBubble] ancestor.
class TextContent extends StatelessWidget {
static const _replyMargin = 8.0;
static const _replyLeftPadding = 12.0;
@override
Widget build(BuildContext context) {
final bubble = MessageBubble.of(context);
final needsBorder =
bubble.isReply && bubble.message.inReplyTo?.isMine == true;
!bubble.message.room.isDirect && bubble.reply?.isMine == false ||
bubble.message.isMine && bubble.reply?.isMine == true;
return Clickable(
child: CustomPaint(
painter: needsBorder
? _ReplyBorderPainter(
color: bubble.message.isMine
color: bubble.message.isMine && bubble.reply?.isMine == true
? Colors.white
: bubble.message.event.sender.getColor(context),
borderRadius: bubble.borderRadius,
......@@ -52,12 +53,8 @@ class TextContent extends StatelessWidget {
padding: EdgeInsets.all(8).copyWith(
left: needsBorder ? _replyLeftPadding : null,
),
child: Wrap(
runAlignment:
bubble.message.isMine ? WrapAlignment.end : WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.end,
spacing: 4,
runSpacing: 4,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (Sender.necessary(context))
Padding(
......@@ -67,20 +64,30 @@ class TextContent extends StatelessWidget {
// Only build the replied-to message if this itself
// is not a replied-to message (to prevent very long
// reply chains)
if (bubble.message.inReplyTo != null && bubble.isReply)
Padding(
padding: EdgeInsets.only(
top: !bubble.message.isMine ? 4 : 0,
bottom: _replyMargin,
),
child: Container() // TODO: REPLY
),
_Content(),
if (MessageInfo.necessary(context))
Padding(
padding: EdgeInsets.only(top: 4),
child: MessageInfo(),
if (bubble.message.inReplyTo != null) ...[
SizedBox(height: 4),
MessageBubble.withContent(
message: bubble.message.inReplyTo,
reply: bubble.message,
),
SizedBox(height: 8)
],
Wrap(
runAlignment: bubble.message.isMine
? WrapAlignment.end
: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.end,
spacing: 4,
runSpacing: 4,
children: <Widget>[
_Content(),
if (MessageInfo.necessary(context))
Padding(
padding: EdgeInsets.only(top: 4),
child: MessageInfo(),
),
],
),
],
),
),
......
......@@ -38,7 +38,9 @@ class MessageBubble extends StatelessWidget {
final bool isStartOfGroup;
final bool isEndOfGroup;
final bool isReply;
/// If this is not null, this bubble is rendered inside another
/// bubble, because it's replied to by [reply].
final ChatMessage reply;
final BorderRadius borderRadius;
......@@ -59,7 +61,7 @@ class MessageBubble extends StatelessWidget {
@required this.nextMessage,
@required this.isStartOfGroup,
@required this.isEndOfGroup,
this.isReply = false,
this.reply,
@required this.borderRadius,
@required this.child,
});
......@@ -68,11 +70,11 @@ class MessageBubble extends StatelessWidget {
ChatMessage message,
ChatMessage previousMessage,
ChatMessage nextMessage,
bool isReply = false,
ChatMessage reply,
Widget child,
}) {
final isStartOfGroup = _isStartOfGroup(message, previousMessage, isReply);
final isEndOfGroup = _isEndofGroup(message, nextMessage, isReply);
final isStartOfGroup = _isStartOfGroup(message, previousMessage, reply);
final isEndOfGroup = _isEndofGroup(message, nextMessage, reply);
return MessageBubble._(
message: message,
......@@ -80,7 +82,7 @@ class MessageBubble extends StatelessWidget {
nextMessage: nextMessage,
isStartOfGroup: isStartOfGroup,
isEndOfGroup: isEndOfGroup,
isReply: isReply,
reply: reply,
borderRadius: _borderRadius(message, isEndOfGroup, isStartOfGroup),
child: child,
);
......@@ -89,8 +91,9 @@ class MessageBubble extends StatelessWidget {
/// Create a [MessageBubble] with the correct [child] for the given [message].
factory MessageBubble.withContent({
@required ChatMessage message,
@required ChatMessage previousMessage,
@required ChatMessage nextMessage,
ChatMessage previousMessage,
ChatMessage nextMessage,
ChatMessage reply,
}) {
final event = message.event;
......@@ -108,6 +111,7 @@ class MessageBubble extends StatelessWidget {
message: message,
previousMessage: previousMessage,
nextMessage: nextMessage,
reply: reply,
child: content,
);
}
......@@ -115,9 +119,9 @@ class MessageBubble extends StatelessWidget {
static bool _isStartOfGroup(
ChatMessage message,
ChatMessage previousMessage,
bool isReply,
ChatMessage reply,
) {
if (isReply == true) {
if (reply != null) {
return false;
}
......@@ -149,9 +153,9 @@ class MessageBubble extends StatelessWidget {
static bool _isEndofGroup(
ChatMessage message,
ChatMessage nextMessage,
bool isReply,
ChatMessage reply,
) {
if (isReply == true) {
if (reply != null) {
return false;
}
......@@ -225,41 +229,47 @@ class MessageBubble extends StatelessWidget {
final border = RoundedRectangleBorder(borderRadius: borderRadius);
return Align(
alignment: message.isMine ? Alignment.centerRight : Alignment.centerLeft,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment:
message.isMine ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
Flexible(
child: Padding(
padding: EdgeInsets.only(
left: message.isMine ? _oppositePadding : 0,
right: !message.isMine ? _oppositePadding : 0,
top: previousMessage == null ? _paddingBetween : 0,
bottom:
isEndOfGroup ? _paddingBetween : _paddingBetweenSameGroup,
),
child: Material(
color: color,
elevation: 1,
shape: border,
child: DefaultTextStyle(
style: Theme.of(context).textTheme.body1.apply(
fontSizeFactor: 1.1,
color: message.isMine ? Colors.white : null,
),
child: Provider<MessageBubble>.value(
value: this,
child: child,
return Column(
crossAxisAlignment:
message.isMine ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment:
message.isMine ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
Flexible(
child: Padding(
padding: reply == null
? EdgeInsets.only(
left: message.isMine ? _oppositePadding : 0,
right: !message.isMine ? _oppositePadding : 0,
top: previousMessage == null ? _paddingBetween : 0,
bottom: isEndOfGroup
? _paddingBetween
: _paddingBetweenSameGroup,
)
: EdgeInsets.zero,
child: Material(
color: color,
elevation: 1,
shape: border,
child: DefaultTextStyle(
style: Theme.of(context).textTheme.body1.apply(
fontSizeFactor: 1.1,
color: message.isMine ? Colors.white : null,
),
child: Provider<MessageBubble>.value(
value: this,
child: child,
),
),
),
),
),
),
],
),
],
),
],
);
}
......@@ -327,14 +337,13 @@ class MessageInfo extends StatelessWidget {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
if (MessageState.necessary(bubble.message)) ...[
Center(
child: MessageState(
message: bubble.message,
color: Colors.white,
size: 14,
),
MessageState(
message: bubble.message,
color: Colors.white,
size: 14,
),
SizedBox(width: 4),
],
......@@ -365,9 +374,9 @@ class Sender extends StatelessWidget {
static bool necessary(BuildContext context) {
final bubble = MessageBubble.of(context);
return (bubble.isStartOfGroup ||
(bubble.isReply != null && !bubble.message.isMine)) &&
!bubble.message.room.isDirect;
return !bubble.message.isMine &&
!bubble.message.room.isDirect &&
(bubble.isStartOfGroup || bubble.reply != null);
}
@override
......
......@@ -37,6 +37,7 @@ class CreationContent extends StatelessWidget {
return RichText(
text: TextSpan(
style: DefaultTextStyle.of(context).style,
children: l(context).createdThisGroup(
TextSpan(
text: message.room.creator.displayName,
......
......@@ -37,6 +37,7 @@ class TopicChangeContent extends StatelessWidget {
return RichText(
text: TextSpan(
style: DefaultTextStyle.of(context).style,
children: l(context).changedDescriptionTapToView(
TextSpan(
text: message.event.sender.displayName,
......
......@@ -37,6 +37,7 @@ class UpgradeContent extends StatelessWidget {
return RichText(
text: TextSpan(
style: DefaultTextStyle.of(context).style,
children: l(context).upgradedThisGroup(
TextSpan(
text: message.event.sender.displayName,
......