Verified Commit a058a049 authored by Wilko Manger's avatar Wilko Manger

Set wantKeepAlive to true on chat widgets

parent 7f5fb49e
......@@ -134,7 +134,4 @@ abstract class Bubble extends Item {
return null;
}
}
@override
Widget build(BuildContext context);
}
......@@ -29,16 +29,22 @@ class DateHeader extends Item {
DateHeader(this.item);
@override
State<StatefulWidget> createState() => DateHeaderState();
}
class DateHeaderState extends ItemState<DateHeader> {
@override
Widget build(BuildContext context) {
super.build(context);
return Center(
child: Padding(
padding: EdgeInsets.only(
top: marginTop(),
bottom: betweenMargin,
bottom: DateHeader.betweenMargin,
),
child: Text(
formatAsDate(context, item.date).toUpperCase(),
formatAsDate(context, widget.item.date).toUpperCase(),
style: Theme.of(context).textTheme.display1.copyWith(
fontSize: 16,
),
......@@ -48,6 +54,7 @@ class DateHeader extends Item {
}
@override
double marginTop() =>
previousItem == null ? betweenMargin * 2 : betweenMargin;
double marginTop() => widget.previousItem == null
? DateHeader.betweenMargin * 2
: DateHeader.betweenMargin;
}
......@@ -53,17 +53,22 @@ class ImageBubble extends MessageBubble {
reply: reply,
);
@override
State<StatefulWidget> createState() => ImageBubbleState();
}
class ImageBubbleState extends MessageBubbleState<ImageBubble> {
void _onTap(BuildContext context) {
Navigator.pushNamed(context, Routes.image, arguments: event);
Navigator.pushNamed(context, Routes.image, arguments: widget.event);
}
Widget buildContent(BuildContext context) =>
Text(event.content.body ?? '', style: textStyle(context));
Text(widget.event.content.body ?? '', style: textStyle(context));
@override
Widget buildTime(BuildContext context, {Color color}) {
var alignment, borderRadius, content;
if (isMine) {
if (widget.isMine) {
alignment = Alignment.bottomRight;
borderRadius = this.borderRadius();
......@@ -103,7 +108,7 @@ class ImageBubble extends MessageBubble {
@override
Widget buildSender(BuildContext context, {Color color}) {
if (!isMine && isStartOfGroup) {
if (!widget.isMine && isStartOfGroup) {
return Align(
alignment: Alignment.topLeft,
child: Padding(
......@@ -126,8 +131,8 @@ class ImageBubble extends MessageBubble {
}
Widget _build(BuildContext context) => Container(
width: width,
height: height,
width: ImageBubble.width,
height: widget.height,
decoration: BoxDecoration(borderRadius: borderRadius()),
child: Stack(
children: <Widget>[
......@@ -135,9 +140,9 @@ class ImageBubble extends MessageBubble {
child: ClipRRect(
borderRadius: borderRadius(),
child: Hero(
tag: event.id,
tag: widget.event.id,
child: Image(
image: MatrixImage(event.content.url),
image: MatrixImage(widget.event.content.url),
fit: BoxFit.cover,
),
),
......
......@@ -18,7 +18,7 @@
import 'package:flutter/material.dart';
import 'package:pattle/src/ui/main/models/chat_item.dart';
abstract class Item extends StatelessWidget {
abstract class Item extends StatefulWidget {
final ChatItem item;
final ChatItem previousItem;
......@@ -33,13 +33,16 @@ abstract class Item extends StatelessWidget {
@required this.previousItem,
@required this.nextItem,
});
}
abstract class ItemState<T extends Item> extends State<T>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context);
final bool wantKeepAlive = true;
@protected
double marginBottom() => betweenMargin;
double marginBottom() => Item.betweenMargin;
@protected
double marginTop() => previousItem == null ? betweenMargin : 0;
double marginTop() => widget.previousItem == null ? Item.betweenMargin : 0;
}
......@@ -50,6 +50,11 @@ class LoadingBubble extends MessageBubble {
event = item.event;
}
@override
State<StatefulWidget> createState() => LoadingBubbleState();
}
class LoadingBubbleState extends MessageBubbleState<LoadingBubble> {
@override
Color mineColor() => Colors.grey[350];
......
......@@ -27,11 +27,11 @@ import 'bubble.dart';
import 'item.dart';
abstract class MessageBubble extends Bubble {
static const _groupTimeLimit = const Duration(minutes: 3);
static const groupTimeLimit = const Duration(minutes: 3);
// Styling
static const _betweenGroupMargin = 4.0;
static const _oppositeMargin = 64.0;
static const betweenGroupMargin = 4.0;
static const oppositeMargin = 64.0;
final RoomEvent reply;
final bool isRepliedTo;
......@@ -49,10 +49,14 @@ abstract class MessageBubble extends Bubble {
nextItem: nextItem,
isMine: isMine,
);
}
abstract class MessageBubbleState<T extends MessageBubble>
extends ItemState<T> {
@override
Widget build(BuildContext context) {
if (isMine) {
super.build(context);
if (widget.isMine) {
return _buildMine(context);
} else {
return _buildTheirs(context);
......@@ -69,7 +73,7 @@ abstract class MessageBubble extends Bubble {
style = style.copyWith(
color: color,
);
} else if (isMine) {
} else if (widget.isMine) {
style = style.copyWith(
color: Colors.white,
);
......@@ -80,7 +84,7 @@ abstract class MessageBubble extends Bubble {
TextStyle senderTextStyle(BuildContext context, {Color color}) {
if (color == null) {
color = colorOf(event.sender);
color = colorOf(widget.event.sender);
}
return textStyle(context, color: color).copyWith(
......@@ -93,7 +97,7 @@ abstract class MessageBubble extends Bubble {
if (isEndOfGroup) {
final style = textStyle(context, color: color);
return Text(
formatAsTime(event.time),
formatAsTime(widget.event.time),
style: style.copyWith(
// size 12 14+1-3
fontSize: style.fontSize - 3,
......@@ -109,9 +113,10 @@ abstract class MessageBubble extends Bubble {
@protected
Widget buildSender(BuildContext context, {Color color}) {
if ((isStartOfGroup || (isRepliedTo && !isMine)) && !event.room.isDirect) {
if ((isStartOfGroup || (widget.isRepliedTo && !widget.isMine)) &&
!widget.event.room.isDirect) {
return Text(
displayNameOf(event.sender, context),
displayNameOf(widget.event.sender, context),
style: senderTextStyle(context, color: color),
);
} else {
......@@ -122,24 +127,25 @@ abstract class MessageBubble extends Bubble {
bool _isStartOfGroup;
@protected
bool get isStartOfGroup {
if (isRepliedTo) {
if (widget.isRepliedTo) {
_isStartOfGroup = false;
return _isStartOfGroup;
}
if (_isStartOfGroup == null) {
if (previousItem is! ChatEvent ||
(previousItem is ChatEvent &&
(previousItem as ChatEvent).event is StateEvent)) {
if (widget.previousItem is! ChatEvent ||
(widget.previousItem is ChatEvent &&
(widget.previousItem as ChatEvent).event is StateEvent)) {
_isStartOfGroup = true;
return _isStartOfGroup;
}
final previousEvent = (previousItem as ChatEvent).event;
final previousEvent = (widget.previousItem as ChatEvent).event;
final previousHasSameSender = previousEvent != null &&
displayNameOf(previousEvent.sender) == displayNameOf(event.sender) &&
previousEvent.sender == event.sender;
displayNameOf(previousEvent.sender) ==
displayNameOf(widget.event.sender) &&
previousEvent.sender == widget.event.sender;
if (!previousHasSameSender) {
_isStartOfGroup = true;
......@@ -147,7 +153,9 @@ abstract class MessageBubble extends Bubble {
}
// Difference between time is greater than 3 min
final limit = event.time.subtract(_groupTimeLimit).millisecondsSinceEpoch;
final limit = widget.event.time
.subtract(MessageBubble.groupTimeLimit)
.millisecondsSinceEpoch;
if (previousEvent.time.millisecondsSinceEpoch <= limit) {
_isStartOfGroup = true;
......@@ -164,24 +172,25 @@ abstract class MessageBubble extends Bubble {
bool _isEndOfGroup;
@protected
bool get isEndOfGroup {
if (isRepliedTo) {
if (widget.isRepliedTo) {
_isEndOfGroup = false;
return _isEndOfGroup;
}
if (_isEndOfGroup == null) {
if (nextItem is! ChatEvent ||
(nextItem is ChatEvent &&
(nextItem as ChatEvent).event is StateEvent)) {
if (widget.nextItem is! ChatEvent ||
(widget.nextItem is ChatEvent &&
(widget.nextItem as ChatEvent).event is StateEvent)) {
_isEndOfGroup = true;
return _isEndOfGroup;
}
final nextEvent = (nextItem as ChatEvent).event;
final nextEvent = (widget.nextItem as ChatEvent).event;
final nextHasSameSender = nextEvent != null &&
displayNameOf(nextEvent.sender) == displayNameOf(event.sender) &&
nextEvent.sender == event.sender;
displayNameOf(nextEvent.sender) ==
displayNameOf(widget.event.sender) &&
nextEvent.sender == widget.event.sender;
if (!nextHasSameSender) {
_isEndOfGroup = true;
......@@ -189,7 +198,9 @@ abstract class MessageBubble extends Bubble {
}
// Difference between time is greater than 3 min
final limit = event.time.add(_groupTimeLimit).millisecondsSinceEpoch;
final limit = widget.event.time
.add(MessageBubble.groupTimeLimit)
.millisecondsSinceEpoch;
if (nextEvent.time.millisecondsSinceEpoch >= limit) {
_isEndOfGroup = true;
......@@ -209,14 +220,14 @@ abstract class MessageBubble extends Bubble {
if (isEndOfGroup) {
return Item.betweenMargin;
} else {
return _betweenGroupMargin;
return MessageBubble.betweenGroupMargin;
}
}
@protected
@override
double marginTop() {
if (previousItem == null) {
if (widget.previousItem == null) {
return Item.betweenMargin;
} else {
return 0;
......@@ -227,7 +238,7 @@ abstract class MessageBubble extends Bubble {
BorderRadius borderRadius() {
var radius = const BorderRadius.all(Bubble.radiusForBorder);
if (isMine) {
if (widget.isMine) {
if (isEndOfGroup) {
radius = BorderRadius.only(
topLeft: Bubble.radiusForBorder,
......@@ -255,7 +266,9 @@ abstract class MessageBubble extends Bubble {
@protected
Widget buildSentState(BuildContext context) => Icon(
event.sentState != SentState.sent ? Icons.access_time : Icons.check,
widget.event.sentState != SentState.sent
? Icons.access_time
: Icons.check,
color: Colors.white,
size: 14,
);
......@@ -290,9 +303,9 @@ abstract class MessageBubble extends Bubble {
children: [
Flexible(
child: Padding(
padding: !isRepliedTo
padding: !widget.isRepliedTo
? EdgeInsets.only(
left: _oppositeMargin,
left: MessageBubble.oppositeMargin,
right: Item.sideMargin,
bottom: marginBottom(),
top: marginTop(),
......@@ -325,10 +338,10 @@ abstract class MessageBubble extends Bubble {
children: [
Flexible(
child: Padding(
padding: !isRepliedTo
padding: !widget.isRepliedTo
? EdgeInsets.only(
left: Item.sideMargin,
right: _oppositeMargin,
right: MessageBubble.oppositeMargin,
bottom: marginBottom(),
top: marginTop(),
)
......
......@@ -44,10 +44,15 @@ class RedactedBubble extends MessageBubble {
isMine: isMine,
);
@override
State<StatefulWidget> createState() => RedactedBubbleState();
}
class RedactedBubbleState extends MessageBubbleState<RedactedBubble> {
@protected
Widget buildContent(BuildContext context) => Redacted(
event: event,
color: isMine ? Colors.grey[300] : Colors.grey[700],
event: widget.event,
color: widget.isMine ? Colors.grey[300] : Colors.grey[700],
textStyle: textStyle(context),
);
......
......@@ -42,10 +42,15 @@ class CreationBubble extends StateBubble {
isMine: isMine,
);
@override
State<StatefulWidget> createState() => CreationBubbleState();
}
class CreationBubbleState extends StateBubbleState<CreationBubble> {
@protected
Widget buildContent(BuildContext context) {
return FutureOrBuilder<User>(
futureOr: event.room.creator,
futureOr: widget.event.room.creator,
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
return super.buildContent(context);
},
......@@ -57,7 +62,7 @@ class CreationBubble extends StateBubble {
List<TextSpan> buildContentSpans(BuildContext context) =>
l(context).createdThisGroup(
TextSpan(
text: displayNameOf(event.room.creator),
text: displayNameOf(widget.event.room.creator),
style: defaultEmphasisTextStyle,
),
);
......
......@@ -40,8 +40,13 @@ class MemberBubble extends StateBubble {
isMine: isMine,
);
@override
State<StatefulWidget> createState() => MemberBubbleState();
}
class MemberBubbleState extends StateBubbleState<MemberBubble> {
@override
@protected
List<TextSpan> buildContentSpans(BuildContext context) =>
spanFor(context, event, style: defaultEmphasisTextStyle);
spanFor(context, widget.event, style: defaultEmphasisTextStyle);
}
......@@ -41,7 +41,9 @@ abstract class StateBubble extends Bubble {
isMine: isMine);
final void Function(BuildContext) onTap = (context) {};
}
abstract class StateBubbleState<T extends StateBubble> extends ItemState<T> {
TextStyle defaultTextStyle(BuildContext context) =>
Theme.of(context).textTheme.body1;
......@@ -54,15 +56,17 @@ abstract class StateBubble extends Bubble {
@protected
Widget buildContent(BuildContext context) {
return RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: defaultTextStyle(context),
children: buildContentSpans(context),
));
textAlign: TextAlign.center,
text: TextSpan(
style: defaultTextStyle(context),
children: buildContentSpans(context),
),
);
}
@override
Widget build(BuildContext context) {
super.build(context);
// size 12 14-2
var timeTextStyle = Theme.of(context).textTheme.body1;
timeTextStyle = timeTextStyle.copyWith(
......@@ -83,12 +87,12 @@ abstract class StateBubble extends Bubble {
child: Material(
elevation: 1,
color: LightColors.red[100],
borderRadius: borderRadius,
borderRadius: StateBubble.borderRadius,
child: PlatformInkWell(
customBorder: RoundedRectangleBorder(
borderRadius: borderRadius,
borderRadius: StateBubble.borderRadius,
),
onTap: () => onTap(context),
onTap: () => widget.onTap(context),
child: Padding(
padding: Bubble.padding,
child: Column(
......@@ -97,7 +101,7 @@ abstract class StateBubble extends Bubble {
buildContent(context),
SizedBox(height: 4),
Text(
formatAsTime(event.time),
formatAsTime(widget.event.time),
style: timeTextStyle,
),
],
......
......@@ -50,12 +50,17 @@ class TopicBubble extends StateBubble {
);
};
@override
State<StatefulWidget> createState() => TopicBubbleState();
}
class TopicBubbleState extends StateBubbleState<TopicBubble> {
@protected
@override
List<TextSpan> buildContentSpans(BuildContext context) =>
l(context).changedDescriptionTapToView(
TextSpan(
text: displayNameOf(event.sender),
text: displayNameOf(widget.event.sender),
style: defaultEmphasisTextStyle,
),
);
......
......@@ -41,12 +41,17 @@ class UpgradeBubble extends StateBubble {
isMine: isMine,
);
@override
State<StatefulWidget> createState() => UpgradeBubbleState();
}
class UpgradeBubbleState extends StateBubbleState<UpgradeBubble> {
@protected
@override
List<TextSpan> buildContentSpans(BuildContext context) =>
l(context).upgradedThisGroup(
TextSpan(
text: displayNameOf(event.sender),
text: displayNameOf(widget.event.sender),
style: defaultEmphasisTextStyle,
),
);
......
......@@ -29,8 +29,8 @@ import 'bubble.dart';
import 'message_bubble.dart';
class TextBubble extends MessageBubble {
static const _replyMargin = 8.0;
static const _replyLeftPadding = 12.0;
static const replyMargin = 8.0;
static const replyLeftPadding = 12.0;
@override
final TextMessageEvent event;
......@@ -52,8 +52,13 @@ class TextBubble extends MessageBubble {
reply: reply,
);
@override
State<StatefulWidget> createState() => TextBubbleState();
}
class TextBubbleState extends MessageBubbleState<TextBubble> {
TextStyle _linkStyle(BuildContext context) {
if (isMine) {
if (widget.isMine) {
return textStyle(context).copyWith(
decoration: TextDecoration.underline,
);
......@@ -66,26 +71,27 @@ class TextBubble extends MessageBubble {
}
Widget _buildRepliedTo(BuildContext context) {
if (event.content.inReplyToId != null) {
final repliedTo = event.room.timeline[event.content.inReplyToId];
if (widget.event.content.inReplyToId != null) {
final repliedTo =
widget.event.room.timeline[widget.event.content.inReplyToId];
return FutureOrBuilder<RoomEvent>(
futureOr: repliedTo,
builder: (BuildContext context, AsyncSnapshot<RoomEvent> snapshot) {
final repliedTo = snapshot.data;
if (repliedTo != null && repliedTo is TextMessageEvent) {
return !isRepliedTo
return !widget.isRepliedTo
? Padding(
padding: EdgeInsets.only(
top: !isMine ? 4 : 0,
bottom: _replyMargin,
top: !widget.isMine ? 4 : 0,
bottom: TextBubble.replyMargin,
),
// Only build the replied-to message if this itself
// is not a replied-to message (to prevent very long
// reply chains)
child: Bubble.asReply(
reply: event,
reply: widget.event,
replyTo: repliedTo,
isMine: repliedTo.sender == me,
isMine: repliedTo.sender == widget.me,
),
)
: Container();
......@@ -102,7 +108,7 @@ class TextBubble extends MessageBubble {
@protected
Widget buildContent(BuildContext context) {
final html = Html(
data: event.content.formattedBody ?? '',
data: widget.event.content.formattedBody ?? '',
useRichText: true,
defaultTextStyle: textStyle(context),
fillWidth: false,
......@@ -114,7 +120,7 @@ class TextBubble extends MessageBubble {
},
);
if (event is! EmoteMessageEvent) {
if (widget.event is! EmoteMessageEvent) {
return html;
} else {
return Row(
......@@ -123,10 +129,10 @@ class TextBubble extends MessageBubble {
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
displayNameOf(event.sender, context) + ' ',
displayNameOf(widget.event.sender, context) + ' ',
style: senderTextStyle(
context,
color: isMine ? Colors.white : null,
color: widget.isMine ? Colors.white : null,
),
),
Flexible(
......@@ -139,7 +145,7 @@ class TextBubble extends MessageBubble {
@protected
Widget buildMine(BuildContext context) {
final needsBorder = isRepliedTo && reply.sender == me;
final needsBorder = widget.isRepliedTo && widget.reply.sender == widget.me;
return PlatformInkWell(
onTap: () {},
......@@ -148,7 +154,7 @@ class TextBubble extends MessageBubble {
painter: needsBorder ? ReplyBorderPainter(color: Colors.white) : null,
child: Padding(
padding: Bubble.padding.copyWith(
left: needsBorder ? _replyLeftPadding : null,
left: needsBorder ? TextBubble.replyLeftPadding : null,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
......@@ -166,10 +172,10 @@ class TextBubble extends MessageBubble {
@protected
Widget buildTheirs(BuildContext context) {
final needsBorder = isRepliedTo && reply.sender != me;
final needsBorder = widget.isRepliedTo && widget.reply.sender != widget.me;
// Don't show sender above emotes
final sender = event is! EmoteMessageEvent
final sender = widget.event is! EmoteMessageEvent
? buildSender(context)
: Container(width: 0);
......@@ -179,12 +185,12 @@ class TextBubble extends MessageBubble {
child: CustomPaint(
painter: needsBorder
? ReplyBorderPainter(
color: colorOf(event.sender),
color: colorOf(widget.event.sender),
)
: null,
child: Padding(
padding: Bubble.padding.copyWith(
left: needsBorder ? _replyLeftPadding : null,
left: needsBorder ? TextBubble.replyLeftPadding : null,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
......@@ -213,11 +219,12 @@ class ReplyBorderPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
canvas.drawRRect(
RRect.fromRectAndCorners(
Rect.fromPoints(Offset(0, 0), Offset(width, size.height)),
topLeft: Bubble.radiusForBorder,
bottomLeft: Bubble.radiusForBorder),
Paint()..color = color);
RRect.fromRectAndCorners(
Rect.fromPoints(Offset(0, 0), Offset(width, size.height)),
topLeft: Bubble.radiusForBorder,
bottomLeft: Bubble.radiusForBorder),
Paint()..color = color,
);
}
@override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment