fix: Add missing safearea

This commit is contained in:
Christian Pauly 2020-11-07 10:18:51 +01:00
parent 03c3ffac0f
commit caab868895
1 changed files with 437 additions and 427 deletions

View File

@ -577,476 +577,486 @@ class _ChatState extends State<_Chat> {
width: double.infinity, width: double.infinity,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
Column( SafeArea(
children: <Widget>[ child: Column(
ConnectionStatusHeader(), children: <Widget>[
Expanded( ConnectionStatusHeader(),
child: FutureBuilder<bool>( Expanded(
future: getTimeline(context), child: FutureBuilder<bool>(
builder: (BuildContext context, snapshot) { future: getTimeline(context),
if (!snapshot.hasData) { builder: (BuildContext context, snapshot) {
return Center( if (!snapshot.hasData) {
child: CircularProgressIndicator(), return Center(
); child: CircularProgressIndicator(),
} );
}
if (room.notificationCount != null && if (room.notificationCount != null &&
room.notificationCount > 0 && room.notificationCount > 0 &&
timeline != null && timeline != null &&
timeline.events.isNotEmpty && timeline.events.isNotEmpty &&
Matrix.of(context).webHasFocus) { Matrix.of(context).webHasFocus) {
room.sendReadReceipt(timeline.events.first.eventId); room.sendReadReceipt(timeline.events.first.eventId);
} }
final filteredEvents = getFilteredEvents(); final filteredEvents = getFilteredEvents();
return ListView.builder( return ListView.builder(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: max( horizontal: max(
0, 0,
(MediaQuery.of(context).size.width - (MediaQuery.of(context).size.width -
AdaptivePageLayout.defaultMinWidth * AdaptivePageLayout.defaultMinWidth *
3.5) / 3.5) /
2), 2),
), ),
reverse: true, reverse: true,
itemCount: filteredEvents.length + 2, itemCount: filteredEvents.length + 2,
controller: _scrollController, controller: _scrollController,
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
return i == filteredEvents.length + 1 return i == filteredEvents.length + 1
? _loadingHistory ? _loadingHistory
? Container( ? Container(
height: 50, height: 50,
alignment: Alignment.center, alignment: Alignment.center,
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
) )
: _canLoadMore : _canLoadMore
? FlatButton( ? FlatButton(
child: Text(
L10n.of(context).loadMore,
style: TextStyle(
color: Theme.of(context)
.primaryColor,
fontWeight: FontWeight.bold,
decoration:
TextDecoration.underline,
),
),
onPressed: requestHistory,
)
: Container()
: i == 0
? AnimatedContainer(
height: seenByText.isEmpty ? 0 : 24,
duration: seenByText.isEmpty
? Duration(milliseconds: 0)
: Duration(milliseconds: 300),
alignment:
filteredEvents.first.senderId ==
client.userID
? Alignment.topRight
: Alignment.topLeft,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 4),
decoration: BoxDecoration(
color: Theme.of(context)
.scaffoldBackgroundColor
.withOpacity(0.8),
borderRadius:
BorderRadius.circular(4),
),
child: Text( child: Text(
L10n.of(context).loadMore, seenByText,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Theme.of(context)
.primaryColor, .primaryColor,
fontWeight: FontWeight.bold,
decoration:
TextDecoration.underline,
), ),
), ),
onPressed: requestHistory,
)
: Container()
: i == 0
? AnimatedContainer(
height: seenByText.isEmpty ? 0 : 24,
duration: seenByText.isEmpty
? Duration(milliseconds: 0)
: Duration(milliseconds: 300),
alignment:
filteredEvents.first.senderId ==
client.userID
? Alignment.topRight
: Alignment.topLeft,
child: Container(
padding:
EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
color: Theme.of(context)
.scaffoldBackgroundColor
.withOpacity(0.8),
borderRadius:
BorderRadius.circular(4),
), ),
child: Text( padding: EdgeInsets.only(
seenByText, left: 8,
maxLines: 1, right: 8,
overflow: TextOverflow.ellipsis, bottom: 8,
style: TextStyle( ),
color: )
Theme.of(context).primaryColor, : AutoScrollTag(
key: ValueKey(i - 1),
index: i - 1,
controller: _scrollController,
child: Swipeable(
key: ValueKey(
filteredEvents[i - 1].eventId),
background: Padding(
padding: EdgeInsets.symmetric(
horizontal: 12.0),
child: Center(
child: Icon(Icons.reply),
),
), ),
), direction: SwipeDirection.endToStart,
), onSwipe: (direction) => replyAction(
padding: EdgeInsets.only( replyTo: filteredEvents[i - 1]),
left: 8, child: Message(filteredEvents[i - 1],
right: 8, onAvatarTab: (Event event) =>
bottom: 8, showModalBottomSheet(
), context: context,
) builder: (context) =>
: AutoScrollTag( UserBottomSheet(
key: ValueKey(i - 1), user: event.sender,
index: i - 1, onMention: () =>
controller: _scrollController, sendController.text +=
child: Swipeable( ' ${event.senderId}',
key: ValueKey( ),
filteredEvents[i - 1].eventId),
background: Padding(
padding: EdgeInsets.symmetric(
horizontal: 12.0),
child: Center(
child: Icon(Icons.reply),
),
),
direction: SwipeDirection.endToStart,
onSwipe: (direction) => replyAction(
replyTo: filteredEvents[i - 1]),
child: Message(filteredEvents[i - 1],
onAvatarTab: (Event event) =>
showModalBottomSheet(
context: context,
builder: (context) =>
UserBottomSheet(
user: event.sender,
onMention: () =>
sendController.text +=
' ${event.senderId}',
), ),
), onSelect: (Event event) {
onSelect: (Event event) { if (!event.redacted) {
if (!event.redacted) { if (selectedEvents
if (selectedEvents .contains(event)) {
.contains(event)) { setState(
setState( () => selectedEvents
() => selectedEvents .remove(event),
.remove(event), );
); } else {
} else { setState(
setState( () => selectedEvents
() => selectedEvents .add(event),
.add(event), );
}
selectedEvents.sort(
(a, b) => a.originServerTs
.compareTo(
b.originServerTs),
); );
} }
selectedEvents.sort( },
(a, b) => a.originServerTs scrollToEventId:
.compareTo( (String eventId) =>
b.originServerTs), _scrollToEventId(eventId,
); context: context),
} longPressSelect:
}, selectedEvents.isEmpty,
scrollToEventId: (String eventId) => selected: selectedEvents.contains(
_scrollToEventId(eventId, filteredEvents[i - 1]),
context: context), timeline: timeline,
longPressSelect: nextEvent: i >= 2
selectedEvents.isEmpty, ? filteredEvents[i - 2]
selected: selectedEvents.contains( : null),
filteredEvents[i - 1]), ),
timeline: timeline, );
nextEvent: i >= 2 });
? filteredEvents[i - 2] },
: null),
),
);
});
},
),
),
AnimatedContainer(
duration: Duration(milliseconds: 300),
height: (editEvent == null &&
replyEvent == null &&
selectedEvents.length == 1)
? 56
: 0,
child: Material(
color: Theme.of(context).secondaryHeaderColor,
child: Builder(builder: (context) {
if (!(editEvent == null &&
replyEvent == null &&
selectedEvents.length == 1)) {
return Container();
}
var emojis = List<String>.from(AppEmojis.emojis);
final allReactionEvents = selectedEvents.first
.aggregatedEvents(timeline, RelationshipTypes.Reaction)
?.where((event) =>
event.senderId == event.room.client.userID &&
event.type == 'm.reaction');
allReactionEvents.forEach((event) {
try {
emojis.remove(event.content['m.relates_to']['key']);
} catch (_) {}
});
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: emojis.length,
itemBuilder: (c, i) => InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () {
SimpleDialogs(context).tryRequestWithLoadingDialog(
room.sendReaction(
selectedEvents.first.eventId,
emojis[i],
),
);
setState(() => selectedEvents.clear());
},
child: Container(
width: 56,
height: 56,
alignment: Alignment.center,
child: Text(
emojis[i],
style: TextStyle(fontSize: 30),
),
),
),
);
}),
),
),
AnimatedContainer(
duration: Duration(milliseconds: 300),
height: editEvent != null || replyEvent != null ? 56 : 0,
child: Material(
color: Theme.of(context).secondaryHeaderColor,
child: Row(
children: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () => setState(() {
if (editEvent != null) {
inputText = sendController.text = pendingText;
pendingText = '';
}
replyEvent = null;
editEvent = null;
}),
),
Expanded(
child: replyEvent != null
? ReplyContent(replyEvent, timeline: timeline)
: _EditContent(
editEvent?.getDisplayEvent(timeline)),
),
],
), ),
), ),
), AnimatedContainer(
Divider( duration: Duration(milliseconds: 300),
height: 1, height: (editEvent == null &&
color: Theme.of(context).secondaryHeaderColor, replyEvent == null &&
thickness: 1, selectedEvents.length == 1)
), ? 56
room.canSendDefaultMessages && room.membership == Membership.join : 0,
? Container( child: Material(
decoration: BoxDecoration( color: Theme.of(context).secondaryHeaderColor,
color: Theme.of(context).backgroundColor, child: Builder(builder: (context) {
), if (!(editEvent == null &&
child: Row( replyEvent == null &&
crossAxisAlignment: CrossAxisAlignment.end, selectedEvents.length == 1)) {
mainAxisAlignment: MainAxisAlignment.spaceBetween, return Container();
children: selectMode }
? <Widget>[ var emojis = List<String>.from(AppEmojis.emojis);
Container( final allReactionEvents = selectedEvents.first
height: 56, .aggregatedEvents(
child: FlatButton( timeline, RelationshipTypes.Reaction)
onPressed: () => ?.where((event) =>
forwardEventsAction(context), event.senderId == event.room.client.userID &&
child: Row( event.type == 'm.reaction');
children: <Widget>[
Icon(Icons.keyboard_arrow_left), allReactionEvents.forEach((event) {
Text(L10n.of(context).forward), try {
], emojis.remove(event.content['m.relates_to']['key']);
), } catch (_) {}
), });
), return ListView.builder(
selectedEvents.length == 1 scrollDirection: Axis.horizontal,
? selectedEvents.first itemCount: emojis.length,
.getDisplayEvent(timeline) itemBuilder: (c, i) => InkWell(
.status > borderRadius: BorderRadius.circular(8),
0 onTap: () {
? Container( SimpleDialogs(context).tryRequestWithLoadingDialog(
height: 56, room.sendReaction(
child: FlatButton( selectedEvents.first.eventId,
onPressed: () => replyAction(), emojis[i],
child: Row( ),
children: <Widget>[ );
Text(L10n.of(context).reply), setState(() => selectedEvents.clear());
Icon(Icons },
.keyboard_arrow_right), child: Container(
], width: 56,
), height: 56,
), alignment: Alignment.center,
) child: Text(
: Container( emojis[i],
height: 56, style: TextStyle(fontSize: 30),
child: FlatButton( ),
onPressed: () => ),
sendAgainAction(timeline), ),
child: Row( );
children: <Widget>[ }),
Text(L10n.of(context) ),
.tryToSendAgain), ),
SizedBox(width: 4), AnimatedContainer(
Icon(Icons.send, size: 16), duration: Duration(milliseconds: 300),
], height: editEvent != null || replyEvent != null ? 56 : 0,
), child: Material(
), color: Theme.of(context).secondaryHeaderColor,
) child: Row(
: Container(), children: <Widget>[
] IconButton(
: <Widget>[ icon: Icon(Icons.close),
if (inputText.isEmpty) onPressed: () => setState(() {
if (editEvent != null) {
inputText = sendController.text = pendingText;
pendingText = '';
}
replyEvent = null;
editEvent = null;
}),
),
Expanded(
child: replyEvent != null
? ReplyContent(replyEvent, timeline: timeline)
: _EditContent(
editEvent?.getDisplayEvent(timeline)),
),
],
),
),
),
Divider(
height: 1,
color: Theme.of(context).secondaryHeaderColor,
thickness: 1,
),
room.canSendDefaultMessages &&
room.membership == Membership.join
? Container(
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: selectMode
? <Widget>[
Container( Container(
height: 56, height: 56,
alignment: Alignment.center, child: FlatButton(
child: PopupMenuButton<String>( onPressed: () =>
icon: Icon(Icons.add), forwardEventsAction(context),
onSelected: (String choice) async { child: Row(
if (choice == 'file') { children: <Widget>[
sendFileAction(context); Icon(Icons.keyboard_arrow_left),
} else if (choice == 'image') { Text(L10n.of(context).forward),
sendImageAction(context); ],
} ),
if (choice == 'camera') { ),
openCameraAction(context); ),
} selectedEvents.length == 1
if (choice == 'voice') { ? selectedEvents.first
voiceMessageAction(context); .getDisplayEvent(timeline)
} .status >
}, 0
itemBuilder: (BuildContext context) => ? Container(
<PopupMenuEntry<String>>[ height: 56,
PopupMenuItem<String>( child: FlatButton(
value: 'file', onPressed: () => replyAction(),
child: ListTile( child: Row(
leading: CircleAvatar( children: <Widget>[
backgroundColor: Colors.green, Text(
foregroundColor: Colors.white, L10n.of(context).reply),
child: Icon(Icons.attachment), Icon(Icons
), .keyboard_arrow_right),
title: ],
Text(L10n.of(context).sendFile), ),
contentPadding: EdgeInsets.all(0), ),
), )
), : Container(
PopupMenuItem<String>( height: 56,
value: 'image', child: FlatButton(
child: ListTile( onPressed: () =>
leading: CircleAvatar( sendAgainAction(timeline),
backgroundColor: Colors.blue, child: Row(
foregroundColor: Colors.white, children: <Widget>[
child: Icon(Icons.image), Text(L10n.of(context)
), .tryToSendAgain),
title: Text( SizedBox(width: 4),
L10n.of(context).sendImage), Icon(Icons.send, size: 16),
contentPadding: EdgeInsets.all(0), ],
), ),
), ),
if (PlatformInfos.isMobile) )
: Container(),
]
: <Widget>[
if (inputText.isEmpty)
Container(
height: 56,
alignment: Alignment.center,
child: PopupMenuButton<String>(
icon: Icon(Icons.add),
onSelected: (String choice) async {
if (choice == 'file') {
sendFileAction(context);
} else if (choice == 'image') {
sendImageAction(context);
}
if (choice == 'camera') {
openCameraAction(context);
}
if (choice == 'voice') {
voiceMessageAction(context);
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>( PopupMenuItem<String>(
value: 'camera', value: 'file',
child: ListTile( child: ListTile(
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: Colors.purple, backgroundColor: Colors.green,
foregroundColor: Colors.white, foregroundColor: Colors.white,
child: Icon(Icons.camera_alt), child: Icon(Icons.attachment),
), ),
title: Text( title: Text(
L10n.of(context).openCamera), L10n.of(context).sendFile),
contentPadding: EdgeInsets.all(0), contentPadding: EdgeInsets.all(0),
), ),
), ),
if (PlatformInfos.isMobile)
PopupMenuItem<String>( PopupMenuItem<String>(
value: 'voice', value: 'image',
child: ListTile( child: ListTile(
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: Colors.red, backgroundColor: Colors.blue,
foregroundColor: Colors.white, foregroundColor: Colors.white,
child: Icon(Icons.mic), child: Icon(Icons.image),
), ),
title: Text(L10n.of(context) title: Text(
.voiceMessage), L10n.of(context).sendImage),
contentPadding: EdgeInsets.all(0), contentPadding: EdgeInsets.all(0),
), ),
), ),
], if (PlatformInfos.isMobile)
), PopupMenuItem<String>(
), value: 'camera',
Container( child: ListTile(
height: 56, leading: CircleAvatar(
alignment: Alignment.center, backgroundColor:
child: EncryptionButton(room), Colors.purple,
), foregroundColor: Colors.white,
Expanded( child: Icon(Icons.camera_alt),
child: Padding( ),
padding: const EdgeInsets.symmetric( title: Text(L10n.of(context)
vertical: 4.0), .openCamera),
child: InputBar( contentPadding:
room: room, EdgeInsets.all(0),
minLines: 1, ),
maxLines: kIsWeb ? 1 : 8, ),
autofocus: !PlatformInfos.isMobile, if (PlatformInfos.isMobile)
keyboardType: !PlatformInfos.isMobile PopupMenuItem<String>(
? TextInputType.text value: 'voice',
: TextInputType.multiline, child: ListTile(
onSubmitted: (String text) { leading: CircleAvatar(
send(); backgroundColor: Colors.red,
FocusScope.of(context) foregroundColor: Colors.white,
.requestFocus(inputFocus); child: Icon(Icons.mic),
}, ),
focusNode: inputFocus, title: Text(L10n.of(context)
controller: sendController, .voiceMessage),
decoration: InputDecoration( contentPadding:
hintText: EdgeInsets.all(0),
L10n.of(context).writeAMessage, ),
hintMaxLines: 1, ),
border: InputBorder.none, ],
), ),
onChanged: (String text) {
typingCoolDown?.cancel();
typingCoolDown =
Timer(Duration(seconds: 2), () {
typingCoolDown = null;
currentlyTyping = false;
room.sendTypingInfo(false);
});
typingTimeout ??=
Timer(Duration(seconds: 30), () {
typingTimeout = null;
currentlyTyping = false;
});
if (!currentlyTyping) {
currentlyTyping = true;
room.sendTypingInfo(true,
timeout: Duration(seconds: 30)
.inMilliseconds);
}
// Workaround for a current desktop bug
if (!PlatformInfos.isBetaDesktop) {
setState(() => inputText = text);
}
},
), ),
),
),
if (PlatformInfos.isMobile && inputText.isEmpty)
Container( Container(
height: 56, height: 56,
alignment: Alignment.center, alignment: Alignment.center,
child: IconButton( child: EncryptionButton(room),
icon: Icon(Icons.mic), ),
onPressed: () => Expanded(
voiceMessageAction(context), child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 4.0),
child: InputBar(
room: room,
minLines: 1,
maxLines: kIsWeb ? 1 : 8,
autofocus: !PlatformInfos.isMobile,
keyboardType: !PlatformInfos.isMobile
? TextInputType.text
: TextInputType.multiline,
onSubmitted: (String text) {
send();
FocusScope.of(context)
.requestFocus(inputFocus);
},
focusNode: inputFocus,
controller: sendController,
decoration: InputDecoration(
hintText:
L10n.of(context).writeAMessage,
hintMaxLines: 1,
border: InputBorder.none,
),
onChanged: (String text) {
typingCoolDown?.cancel();
typingCoolDown =
Timer(Duration(seconds: 2), () {
typingCoolDown = null;
currentlyTyping = false;
room.sendTypingInfo(false);
});
typingTimeout ??=
Timer(Duration(seconds: 30), () {
typingTimeout = null;
currentlyTyping = false;
});
if (!currentlyTyping) {
currentlyTyping = true;
room.sendTypingInfo(true,
timeout: Duration(seconds: 30)
.inMilliseconds);
}
// Workaround for a current desktop bug
if (!PlatformInfos.isBetaDesktop) {
setState(() => inputText = text);
}
},
),
), ),
), ),
if (!PlatformInfos.isMobile || if (PlatformInfos.isMobile &&
inputText.isNotEmpty) inputText.isEmpty)
Container( Container(
height: 56, height: 56,
alignment: Alignment.center, alignment: Alignment.center,
child: IconButton( child: IconButton(
icon: Icon(Icons.send), icon: Icon(Icons.mic),
onPressed: () => send(), onPressed: () =>
voiceMessageAction(context),
),
), ),
), if (!PlatformInfos.isMobile ||
], inputText.isNotEmpty)
), Container(
) height: 56,
: Container(), alignment: Alignment.center,
], child: IconButton(
icon: Icon(Icons.send),
onPressed: () => send(),
),
),
],
),
)
: Container(),
],
),
), ),
], ],
), ),