Merge pull request #188 from GetStream/feature/home-screen-fixes

Feature/home screen fixes
This commit is contained in:
Salvatore Giordano
2020-12-31 16:14:50 +01:00
committed by GitHub
6 changed files with 298 additions and 179 deletions
+187 -172
View File
@@ -1,193 +1,208 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import '../stream_chat_flutter.dart';
import 'channel_info.dart'; import 'channel_info.dart';
import 'channel_name.dart'; import 'option_list_tile.dart';
import 'stream_chat.dart';
import 'stream_chat_theme.dart';
import 'user_avatar.dart';
class ChannelBottomSheet extends StatelessWidget { class ChannelBottomSheet extends StatefulWidget {
const ChannelBottomSheet({ VoidCallback onViewInfoTap;
Key key,
}) : super(key: key); ChannelBottomSheet({this.onViewInfoTap});
@override
_ChannelBottomSheetState createState() => _ChannelBottomSheetState();
}
class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final channel = StreamChannel.of(context).channel; var channel = StreamChannel.of(context).channel;
return SafeArea(
child: Padding( var members = channel.state.members;
padding: const EdgeInsets.all(8.0),
child: Column( var userAsMember =
mainAxisSize: MainAxisSize.min, members.firstWhere((e) => e.user.id == StreamChat.of(context).user.id);
crossAxisAlignment: CrossAxisAlignment.stretch, var isOwner = userAsMember.role == 'owner';
children: <Widget>[
Padding( return Material(
padding: const EdgeInsets.symmetric( color: StreamChatTheme.of(context).colorTheme.white,
vertical: 2.0, clipBehavior: Clip.antiAlias,
), shape: RoundedRectangleBorder(
child: Center( borderRadius: BorderRadius.only(
child: StreamChannel( topLeft: Radius.circular(16.0),
showLoading: false, topRight: Radius.circular(16.0),
channel: channel,
child: ChannelName(
textStyle:
StreamChatTheme.of(context).channelPreviewTheme.title,
),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 2.0,
),
child: Center(
child: ChannelInfo(
channel: channel,
textStyle:
StreamChatTheme.of(context).channelPreviewTheme.subtitle,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 15.0,
),
child: Center(
child: StreamBuilder<List<Member>>(
stream: channel.state.membersStream.map((event) => event
.where((m) => m.userId != StreamChat.of(context).user.id)
.toList()),
initialData: channel.state.members
.where((m) => m.userId != StreamChat.of(context).user.id)
.toList(),
builder: _buildMembers,
),
),
),
Divider(),
if (channel.isGroup && !channel.isDistinct)
ListTile(
leading: StreamSvgIcon.userRemove(
size: 24,
color: StreamChatTheme.of(context).colorTheme.grey,
),
title: Text(
'Leave Group',
style: TextStyle(fontWeight: FontWeight.bold),
),
onTap: () async {
final confirm = await showConfirmationDialog(
context,
title: 'Leave Group',
okText: 'LEAVE',
question: 'Are you sure you want to leave this group?',
cancelText: 'CANCEL',
icon: StreamSvgIcon.userRemove(
color: Colors.red,
),
);
if (confirm == true) {
await channel
.removeMembers([StreamChat.of(context).user.id]);
Navigator.pop(context);
}
},
),
if ([
'admin',
'owner',
].contains(channel.state.members
.firstWhere((m) => m.userId == channel.client.state.user.id,
orElse: () => null)
?.role))
ListTile(
leading: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed,
size: 24,
),
title: Text(
'Delete chat',
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.accentRed,
),
),
onTap: () async {
final res = await showConfirmationDialog(
context,
title: 'Delete Conversation',
okText: 'DELETE',
question:
'Are you sure you want to delete this conversation?',
cancelText: 'CANCEL',
icon: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed,
),
);
var channel = StreamChannel.of(context).channel;
if (res == true) {
await channel.delete().then((value) {
Navigator.pop(context);
});
}
},
),
],
), ),
), ),
);
}
Widget _buildMembers(
BuildContext context,
AsyncSnapshot<List<Member>> snapshot,
) {
if (snapshot.data.isEmpty) {
return SizedBox();
}
return Container(
height: 83,
child: ListView( child: ListView(
padding: EdgeInsets.only( children: [
left: (MediaQuery.of(context).size.width / 2) - 48, SizedBox(
), height: 24.0,
scrollDirection: Axis.horizontal, ),
children: snapshot.data.map((m) { Center(
return Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(horizontal: 16.0),
horizontal: 8.0, child: ChannelName(
textStyle: StreamChatTheme.of(context).textTheme.headlineBold,
),
), ),
child: Column( ),
children: <Widget>[ SizedBox(
height: 5.0,
),
Center(
child: ChannelInfo(
showTypingIndicator: false,
channel: StreamChannel.of(context).channel,
textStyle:
StreamChatTheme.of(context).channelPreviewTheme.subtitle,
),
),
SizedBox(
height: 17.0,
),
if (channel.isDistinct && channel.memberCount == 2)
Column(
children: [
UserAvatar( UserAvatar(
showOnlineStatus: true, user: members
user: m.user, .firstWhere((e) => e.user.id != userAsMember.user.id)
borderRadius: BorderRadius.circular(32), .user,
constraints: BoxConstraints.tight( constraints: BoxConstraints(
Size.square(64), maxHeight: 64.0,
maxWidth: 64.0,
), ),
borderRadius: BorderRadius.circular(32.0),
), ),
Padding( SizedBox(
padding: const EdgeInsets.only(top: 5.0), height: 6.0,
child: Text( ),
m.user.name?.split(' ')?.elementAt(0), Text(
style: StreamChatTheme.of(context) members
.channelPreviewTheme .firstWhere((e) => e.user.id != userAsMember.user.id)
.title .user
.copyWith( .name,
fontSize: 12, style: StreamChatTheme.of(context).textTheme.footnoteBold,
), maxLines: 1,
), overflow: TextOverflow.ellipsis,
), ),
], ],
), ),
); if (!(channel.isDistinct && channel.memberCount == 2))
}).toList(), Container(
height: 94.0,
alignment: Alignment.center,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: members.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
children: [
UserAvatar(
user: members[index].user,
constraints: BoxConstraints(
maxHeight: 64.0,
maxWidth: 64.0,
),
borderRadius: BorderRadius.circular(32.0),
),
SizedBox(
height: 6.0,
),
Text(
members[index].user.name,
style: StreamChatTheme.of(context)
.textTheme
.footnoteBold,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
);
},
),
),
SizedBox(
height: 24.0,
),
OptionListTile(
leading: StreamSvgIcon.user(
color: StreamChatTheme.of(context).colorTheme.grey,
),
title: 'View Info',
onTap: widget.onViewInfoTap,
),
if (!channel.isDistinct)
OptionListTile(
leading: StreamSvgIcon.userRemove(
color: StreamChatTheme.of(context).colorTheme.grey,
),
title: 'Leave Group',
onTap: () async {
_showLeaveDialog();
},
),
if (isOwner)
OptionListTile(
leading: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed,
),
title: 'Delete Conversation',
titleColor: StreamChatTheme.of(context).colorTheme.accentRed,
onTap: () async {
_showDeleteDialog();
},
),
OptionListTile(
leading: StreamSvgIcon.close_small(
color: StreamChatTheme.of(context).colorTheme.grey,
),
title: 'Cancel',
onTap: () {
Navigator.pop(context);
},
),
],
), ),
); );
} }
void _showDeleteDialog() async {
final res = await showConfirmationDialog(
context,
title: 'Delete Conversation',
okText: 'DELETE',
question: 'Are you sure you want to delete this conversation?',
cancelText: 'CANCEL',
icon: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed,
),
);
var channel = StreamChannel.of(context).channel;
if (res == true) {
await channel.delete();
Navigator.pop(context);
}
}
void _showLeaveDialog() async {
final res = await showConfirmationDialog(
context,
title: 'Leave conversation',
okText: 'LEAVE',
question: 'Are you sure you want to leave this conversation?',
cancelText: 'CANCEL',
icon: StreamSvgIcon.userRemove(
color: StreamChatTheme.of(context).colorTheme.accentRed,
),
);
var channel = StreamChannel.of(context).channel;
if (res == true) {
await channel.removeMembers([StreamChat.of(context).user.id]);
Navigator.pop(context);
}
}
} }
+21 -1
View File
@@ -567,7 +567,27 @@ class _ChannelListViewState extends State<ChannelListView>
context: context, context: context,
builder: (context) { builder: (context) {
return StreamChannel( return StreamChannel(
child: ChannelBottomSheet(), child: ChannelBottomSheet(
onViewInfoTap: () {
if (channel.memberCount == 2 &&
channel.isDistinct) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: ChatInfoScreen(
user:
channel.state.members.first.user,
),
),
),
);
}
// TODO: Add group screen
},
),
channel: channel, channel: channel,
); );
}, },
+11 -5
View File
@@ -162,12 +162,18 @@ class ChannelPreview extends StatelessWidget {
String stringDate; String stringDate;
final now = DateTime.now(); final now = DateTime.now();
if (now.year != lastMessageAt.year || var startOfDay = DateTime(now.year, now.month, now.day);
now.month != lastMessageAt.month ||
now.day != lastMessageAt.day) { if (lastMessageAt.millisecondsSinceEpoch >=
stringDate = Jiffy(lastMessageAt.toLocal()).format('dd/MM/yyyy'); startOfDay.millisecondsSinceEpoch) {
} else {
stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm'); stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm');
} else if (lastMessageAt.millisecondsSinceEpoch >=
startOfDay.subtract(Duration(days: 1)).millisecondsSinceEpoch) {
stringDate = 'Yesterday';
} else if (startOfDay.difference(lastMessageAt).inDays < 7) {
stringDate = Jiffy(lastMessageAt.toLocal()).EEEE;
} else {
stringDate = Jiffy(lastMessageAt.toLocal()).format('dd/MM/yyyy');
} }
return Text( return Text(
+2 -1
View File
@@ -19,6 +19,7 @@ class ChannelUnreadIndicator extends StatelessWidget {
if (!snapshot.hasData || snapshot.data == 0) { if (!snapshot.hasData || snapshot.data == 0) {
return SizedBox(); return SizedBox();
} }
return Material( return Material(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
color: StreamChatTheme.of(context) color: StreamChatTheme.of(context)
@@ -33,7 +34,7 @@ class ChannelUnreadIndicator extends StatelessWidget {
), ),
child: Center( child: Center(
child: Text( child: Text(
'${snapshot.data}', '${snapshot.data > 99 ? '99+' : snapshot.data}',
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: Colors.white, color: Colors.white,
+76
View File
@@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
class OptionListTile extends StatelessWidget {
final String title;
final StreamSvgIcon leading;
final Widget trailing;
final VoidCallback onTap;
final Color titleColor;
OptionListTile({
this.title,
this.leading,
this.trailing,
this.onTap,
this.titleColor,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
height: 1.0,
),
Material(
color: StreamChatTheme.of(context).colorTheme.white,
child: Container(
height: 63.0,
child: InkWell(
onTap: onTap,
child: Row(
children: [
if (leading != null)
Expanded(
child: Center(child: leading),
),
if (leading == null)
SizedBox(
width: 16.0,
),
Expanded(
flex: 4,
child: Text(
title,
style: titleColor == null
? StreamChatTheme.of(context).textTheme.bodyBold
: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: titleColor,
),
),
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Align(
alignment: Alignment.centerRight,
child: trailing ?? Container(),
),
),
),
],
),
),
),
),
],
);
}
}
+1
View File
@@ -43,3 +43,4 @@ export 'src/message_search_bloc.dart';
export 'src/message_search_item.dart'; export 'src/message_search_item.dart';
export 'src/message_search_list_view.dart'; export 'src/message_search_list_view.dart';
export 'src/unread_indicator.dart'; export 'src/unread_indicator.dart';
export 'src/chat_info_screen.dart';