Merge branch 'feature/new-ui' of github.com:GetStream/stream-chat-flutter into feat/messg-reply

 Conflicts:
	lib/src/message_list_view.dart
This commit is contained in:
Sahil Kumar
2021-01-01 09:31:41 +05:30
20 changed files with 554 additions and 442 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

-2
View File
@@ -58,8 +58,6 @@
<array> <array>
<string>remote-notification</string> <string>remote-notification</string>
</array> </array>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<true/> <true/>
</dict> </dict>
+1 -2
View File
@@ -350,8 +350,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
return MaterialApp( return MaterialApp(
theme: ThemeData.light(), theme: ThemeData.light(),
darkTheme: ThemeData.dark(), darkTheme: ThemeData.dark(),
//TODO change to system once dark theme is implemented themeMode: ThemeMode.system,
themeMode: ThemeMode.light,
builder: (context, widget) { builder: (context, widget) {
return StreamChat( return StreamChat(
child: widget, child: widget,
+4 -1
View File
@@ -60,7 +60,10 @@ class MyApp extends StatelessWidget {
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: ThemeData.light(), theme: ThemeData.light(),
darkTheme: ThemeData.dark(), darkTheme: ThemeData.dark(),
themeMode: ThemeMode.system, themeMode:
WidgetsBinding.instance.window.platformBrightness == Brightness.light
? ThemeMode.light
: ThemeMode.dark,
onGenerateRoute: AppRoutes.generateRoute, onGenerateRoute: AppRoutes.generateRoute,
initialRoute: initialRoute:
client.state.user == null ? Routes.CHOOSE_USER : Routes.HOME, client.state.user == null ? Routes.CHOOSE_USER : Routes.HOME,
+4 -3
View File
@@ -1,6 +1,6 @@
name: example name: example
description: A new Flutter project. description: A new Flutter project.
version: 1.0.102+105 version: 1.0.106+109
environment: environment:
sdk: ">=2.2.2 <3.0.0" sdk: ">=2.2.2 <3.0.0"
@@ -22,7 +22,7 @@ dev_dependencies:
sdk: flutter sdk: flutter
flutter_driver: flutter_driver:
sdk: flutter sdk: flutter
flutter_launcher_icons: ^0.8.0 flutter_launcher_icons: ^0.8.1
test: any test: any
flutter: flutter:
@@ -34,4 +34,5 @@ flutter:
flutter_icons: flutter_icons:
android: true android: true
ios: true ios: true
image_path: "assets/icon.png" image_path_android: "assets/android_icon.png"
image_path_ios: "assets/ios_icon.png"
+156 -141
View File
@@ -1,127 +1,181 @@
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(
var members = channel.state.members;
var userAsMember =
members.firstWhere((e) => e.user.id == StreamChat.of(context).user.id);
var isOwner = userAsMember.role == 'owner';
return Material(
color: StreamChatTheme.of(context).colorTheme.white,
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0),
),
),
child: ListView(
children: [
SizedBox(
height: 24.0,
),
Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(
vertical: 2.0,
),
child: Center(
child: StreamChannel(
showLoading: false,
channel: channel,
child: ChannelName( child: ChannelName(
textStyle: textStyle: StreamChatTheme.of(context).textTheme.headlineBold,
StreamChatTheme.of(context).channelPreviewTheme.title,
), ),
), ),
), ),
SizedBox(
height: 5.0,
), ),
Padding( Center(
padding: const EdgeInsets.symmetric(
vertical: 2.0,
),
child: Center(
child: ChannelInfo( child: ChannelInfo(
channel: channel, showTypingIndicator: false,
channel: StreamChannel.of(context).channel,
textStyle: textStyle:
StreamChatTheme.of(context).channelPreviewTheme.subtitle, StreamChatTheme.of(context).channelPreviewTheme.subtitle,
), ),
), ),
SizedBox(
height: 17.0,
), ),
Padding( if (channel.isDistinct && channel.memberCount == 2)
padding: const EdgeInsets.symmetric( Column(
vertical: 15.0, children: [
UserAvatar(
user: members
.firstWhere((e) => e.user.id != userAsMember.user.id)
.user,
constraints: BoxConstraints(
maxHeight: 64.0,
maxWidth: 64.0,
), ),
child: Center( borderRadius: BorderRadius.circular(32.0),
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,
), ),
SizedBox(
height: 6.0,
), ),
Text(
members
.firstWhere((e) => e.user.id != userAsMember.user.id)
.user
.name,
style: StreamChatTheme.of(context).textTheme.footnoteBold,
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
Divider(), ],
if (channel.isGroup && !channel.isDistinct)
ListTile(
leading: StreamSvgIcon.userRemove(
size: 24,
color: StreamChatTheme.of(context).colorTheme.grey,
), ),
title: Text( if (!(channel.isDistinct && channel.memberCount == 2))
'Leave Group', Container(
style: TextStyle(fontWeight: FontWeight.bold), 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,
), ),
onTap: () async { borderRadius: BorderRadius.circular(32.0),
final confirm = await showConfirmationDialog( ),
context, SizedBox(
title: 'Leave Group', height: 6.0,
okText: 'LEAVE', ),
question: 'Are you sure you want to leave this group?', Text(
cancelText: 'CANCEL', members[index].user.name,
icon: StreamSvgIcon.userRemove( style: StreamChatTheme.of(context)
color: Colors.red, .textTheme
.footnoteBold,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
), ),
); );
if (confirm == true) {
await channel
.removeMembers([StreamChat.of(context).user.id]);
Navigator.pop(context);
}
}, },
), ),
if ([ ),
'admin', SizedBox(
'owner', height: 24.0,
].contains(channel.state.members ),
.firstWhere((m) => m.userId == channel.client.state.user.id, OptionListTile(
orElse: () => null) leading: StreamSvgIcon.user(
?.role)) color: StreamChatTheme.of(context).colorTheme.grey,
ListTile( ),
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( leading: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed, color: StreamChatTheme.of(context).colorTheme.accentRed,
size: 24,
),
title: Text(
'Delete chat',
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.accentRed,
),
), ),
title: 'Delete Conversation',
titleColor: StreamChatTheme.of(context).colorTheme.accentRed,
onTap: () async { 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( final res = await showConfirmationDialog(
context, context,
title: 'Delete Conversation', title: 'Delete Conversation',
okText: 'DELETE', okText: 'DELETE',
question: question: 'Are you sure you want to delete this conversation?',
'Are you sure you want to delete this conversation?',
cancelText: 'CANCEL', cancelText: 'CANCEL',
icon: StreamSvgIcon.delete( icon: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed, color: StreamChatTheme.of(context).colorTheme.accentRed,
@@ -129,65 +183,26 @@ class ChannelBottomSheet extends StatelessWidget {
); );
var channel = StreamChannel.of(context).channel; var channel = StreamChannel.of(context).channel;
if (res == true) { if (res == true) {
await channel.delete().then((value) { await channel.delete();
Navigator.pop(context); Navigator.pop(context);
});
} }
},
),
],
),
),
);
} }
Widget _buildMembers( void _showLeaveDialog() async {
BuildContext context, final res = await showConfirmationDialog(
AsyncSnapshot<List<Member>> snapshot, context,
) { title: 'Leave conversation',
if (snapshot.data.isEmpty) { okText: 'LEAVE',
return SizedBox(); question: 'Are you sure you want to leave this conversation?',
} cancelText: 'CANCEL',
icon: StreamSvgIcon.userRemove(
return Container( color: StreamChatTheme.of(context).colorTheme.accentRed,
height: 83,
child: ListView(
padding: EdgeInsets.only(
left: (MediaQuery.of(context).size.width / 2) - 48,
),
scrollDirection: Axis.horizontal,
children: snapshot.data.map((m) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
),
child: Column(
children: <Widget>[
UserAvatar(
showOnlineStatus: true,
user: m.user,
borderRadius: BorderRadius.circular(32),
constraints: BoxConstraints.tight(
Size.square(64),
),
),
Padding(
padding: const EdgeInsets.only(top: 5.0),
child: Text(
m.user.name?.split(' ')?.elementAt(0),
style: StreamChatTheme.of(context)
.channelPreviewTheme
.title
.copyWith(
fontSize: 12,
),
),
),
],
),
);
}).toList(),
), ),
); );
var channel = StreamChannel.of(context).channel;
if (res == true) {
await channel.removeMembers([StreamChat.of(context).user.id]);
Navigator.pop(context);
}
} }
} }
+26 -1
View File
@@ -380,6 +380,10 @@ class _ChannelListViewState extends State<ChannelListView>
width: 40, width: 40,
), ),
), ),
contentPadding: const EdgeInsets.only(
left: 8,
right: 8,
),
title: Align( title: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Container( child: Container(
@@ -394,6 +398,7 @@ class _ChannelListViewState extends State<ChannelListView>
), ),
), ),
subtitle: Row( subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
@@ -567,7 +572,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,
); );
}, },
+17 -8
View File
@@ -130,11 +130,14 @@ class ChannelPreview extends StatelessWidget {
.lastMessageAt .lastMessageAt
.fontSize, .fontSize,
isMessageRead: channel.state.read isMessageRead: channel.state.read
.where((element) => element.lastRead ?.where((element) =>
element.user.id !=
channel.client.state.user.id)
?.where((element) => element.lastRead
.isAfter(channel .isAfter(channel
.state.lastMessage.createdAt)) .state.lastMessage.createdAt))
.length == ?.isNotEmpty ==
((channel.memberCount ?? 0) - 1), true,
), ),
); );
} }
@@ -162,12 +165,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,
+3 -7
View File
@@ -1,6 +1,4 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:carousel_slider/carousel_options.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/full_screen_media.dart'; import 'package:stream_chat_flutter/src/full_screen_media.dart';
@@ -81,12 +79,10 @@ class ImageGroup extends StatelessWidget {
color: Colors.black38, color: Colors.black38,
child: Center( child: Center(
child: Text( child: Text(
'${images.length - 4} +', '+ ${images.length - 4}',
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context) color: Colors.white,
.colorTheme fontSize: 26,
.white,
fontSize: 28,
), ),
), ),
), ),
+13 -26
View File
@@ -186,10 +186,10 @@ class _MessageListViewState extends State<MessageListView> {
bool _showScrollToBottom = false; bool _showScrollToBottom = false;
ItemPositionsListener _itemPositionListener; ItemPositionsListener _itemPositionListener;
int _messageListLength; int _messageListLength;
StreamChannelState streamChannel;
int get _initialIndex { int get _initialIndex {
if (widget.initialScrollIndex != null) return widget.initialScrollIndex; if (widget.initialScrollIndex != null) return widget.initialScrollIndex;
final streamChannel = StreamChannel.of(context);
if (streamChannel.initialMessageId != null) { if (streamChannel.initialMessageId != null) {
final messages = streamChannel.channel.state.messages; final messages = streamChannel.channel.state.messages;
final totalMessages = messages.length; final totalMessages = messages.length;
@@ -209,11 +209,10 @@ class _MessageListViewState extends State<MessageListView> {
} }
bool _isInitialMessage(String id) { bool _isInitialMessage(String id) {
final streamChannel = StreamChannel.of(context);
return streamChannel.initialMessageId == id; return streamChannel.initialMessageId == id;
} }
bool get _upToDate => StreamChannel.of(context).channel.state.isUpToDate; bool get _upToDate => streamChannel.channel.state.isUpToDate;
bool _topPaginationActive = false; bool _topPaginationActive = false;
bool _bottomPaginationActive = false; bool _bottomPaginationActive = false;
@@ -229,22 +228,13 @@ class _MessageListViewState extends State<MessageListView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final streamChannel = StreamChannel.of(context);
final messagesStream = widget.parentMessage != null final messagesStream = widget.parentMessage != null
? streamChannel.channel.state.threadsStream ? streamChannel.channel.state.threadsStream
.where((threads) => threads.containsKey(widget.parentMessage.id)) .where((threads) => threads.containsKey(widget.parentMessage.id))
.map((threads) => threads[widget.parentMessage.id]) .map((threads) => threads[widget.parentMessage.id])
: streamChannel.channel.state?.messagesStream; : streamChannel.channel.state?.messagesStream;
return WillPopScope( return StreamBuilder<List<Message>>(
onWillPop: () async {
if (!_upToDate) {
await streamChannel.reloadChannel();
}
return true;
},
child: StreamBuilder<List<Message>>(
stream: messagesStream?.map((messages) => messages stream: messagesStream?.map((messages) => messages
?.where((e) => ?.where((e) =>
!e.isDeleted || !e.isDeleted ||
@@ -441,8 +431,7 @@ class _MessageListViewState extends State<MessageListView> {
children: <Widget>[ children: <Widget>[
messageWidget, messageWidget,
Padding( Padding(
padding: padding: const EdgeInsets.symmetric(vertical: 12.0),
const EdgeInsets.symmetric(vertical: 12.0),
child: widget.dateDividerBuilder != null child: widget.dateDividerBuilder != null
? widget.dateDividerBuilder( ? widget.dateDividerBuilder(
nextMessage.createdAt.toLocal()) nextMessage.createdAt.toLocal())
@@ -491,8 +480,7 @@ class _MessageListViewState extends State<MessageListView> {
), ),
], ],
); );
}), });
);
} }
Future<void> _paginateData( Future<void> _paginateData(
@@ -512,7 +500,6 @@ class _MessageListViewState extends State<MessageListView> {
} }
Widget _buildScrollToBottom() { Widget _buildScrollToBottom() {
final streamChannel = StreamChannel.of(context);
return StreamBuilder<Tuple2<bool, int>>( return StreamBuilder<Tuple2<bool, int>>(
stream: Rx.combineLatest2( stream: Rx.combineLatest2(
streamChannel.channel.state.isUpToDateStream, streamChannel.channel.state.isUpToDateStream,
@@ -758,7 +745,7 @@ class _MessageListViewState extends State<MessageListView> {
final isNextUser = final isNextUser =
index - 2 >= 0 && message.user.id == messages[index - 2]?.user?.id; index - 2 >= 0 && message.user.id == messages[index - 2]?.user?.id;
final channel = StreamChannel.of(context).channel; final channel = streamChannel.channel;
final readList = channel.state?.read final readList = channel.state?.read
?.where((element) => element.user.id != userId) ?.where((element) => element.user.id != userId)
?.where((read) => ?.where((read) =>
@@ -864,7 +851,7 @@ class _MessageListViewState extends State<MessageListView> {
_itemPositionListener = _itemPositionListener =
widget.itemPositionListener ?? ItemPositionsListener.create(); widget.itemPositionListener ?? ItemPositionsListener.create();
final streamChannel = StreamChannel.of(context); streamChannel = StreamChannel.of(context);
initialIndex = _initialIndex; initialIndex = _initialIndex;
initialAlignment = _initialAlignment; initialAlignment = _initialAlignment;
@@ -907,16 +894,13 @@ class _MessageListViewState extends State<MessageListView> {
context, context,
MaterialPageRoute(builder: (_) { MaterialPageRoute(builder: (_) {
return StreamBuilder<Message>( return StreamBuilder<Message>(
stream: StreamChannel.of(context) stream: streamChannel.channel.state.messagesStream.map(
.channel (messages) =>
.state
.messagesStream
.map((messages) =>
messages.firstWhere((m) => m.id == message.id)), messages.firstWhere((m) => m.id == message.id)),
initialData: message, initialData: message,
builder: (_, snapshot) { builder: (_, snapshot) {
return StreamChannel( return StreamChannel(
channel: StreamChannel.of(context).channel, channel: streamChannel.channel,
child: widget.threadBuilder(context, snapshot.data), child: widget.threadBuilder(context, snapshot.data),
); );
}); });
@@ -928,6 +912,9 @@ class _MessageListViewState extends State<MessageListView> {
@override @override
void dispose() { void dispose() {
if (!_upToDate) {
streamChannel.reloadChannel();
}
_messageNewListener?.cancel(); _messageNewListener?.cancel();
super.dispose(); super.dispose();
} }
+1
View File
@@ -855,6 +855,7 @@ class _MessageWidgetState extends State<MessageWidget> {
if (isMessageRead) { if (isMessageRead) {
child = Row( child = Row(
children: [ children: [
if (StreamChannel.of(context).channel.memberCount > 2)
Text( Text(
widget.readList.length.toString(), widget.readList.length.toString(),
style: style.copyWith( style: style.copyWith(
+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';