Merge pull request #53 from GetStream/chore/update-samples

chore: update dependencies and readme
This commit is contained in:
Salvatore Giordano
2021-10-12 14:49:10 +02:00
committed by GitHub
33 changed files with 195 additions and 146 deletions
+7 -5
View File
@@ -25,16 +25,18 @@ With Stream's chat components, developers quickly add chat to their app for a va
## Repo Overview 😎 ## Repo Overview 😎
This repo contains projects and samples developed by the team and Stream community. Projects are broke up into directories under the `packages` folder. This repo contains projects and samples developed by the team and Stream community. Projects are broke up into directories under the `packages` folder.
Each project contains a README with build and execution instructions. Each project contains a README with build and execution instructions.
## **Projects 🚀** ## **Maintained Projects 🚀**
- [Stream Chat v1](https://github.com/GetStream/flutter-samples/tree/main/packages/stream_chat_v1): a sample app implemented using Stream Chat and Flutter. It is a fully fledged messaging app built using a combination of our pre-made widgets and custom Flutter widgets. - [Stream Chat v1](https://github.com/GetStream/flutter-samples/tree/main/packages/stream_chat_v1): a sample app implemented using Stream Chat and Flutter. It is a fully fledged messaging app built using a combination of our pre-made widgets and custom Flutter widgets.
- [Chatter YouTube Series](https://github.com/HayesGordon/chatter): an ongoing, beginner friendly, YouTube video series showing how to use Stream's Flutter packages.
- [Stream Chatty](https://github.com/GetStream/flutter-samples/tree/main/packages/chatty) Stream Chatty is a sample chat app made in Flutter using Stream Chat, Firebase, and flutter_bloc. It has full light and dark mode support, real-time chat, and full authentication using Firebase auth.
- [iMessage clone](https://github.com/GetStream/flutter-samples/tree/main/packages/imessage): an iMessage clone implemented using Flutter and the `stream_chat_flutter_core` package. - [iMessage clone](https://github.com/GetStream/flutter-samples/tree/main/packages/imessage): an iMessage clone implemented using Flutter and the `stream_chat_flutter_core` package.
## **Historic Projects 📕**
*These projects are not actively maintained. They were developed using older versions of our packages and Flutter.*
- [Stream Chatty](https://github.com/GetStream/flutter-samples/tree/main/packages/chatty) Stream Chatty is a sample chat app made in Flutter using Stream Chat, Firebase, and flutter_bloc. It has full light and dark mode support, real-time chat, and full authentication using Firebase auth.
## Requirements 🛠 ## Requirements 🛠
Before running this project please ensure Flutter is installed and configured on your machine. If you're new to Flutter, please checkout the [official guide](https://flutter.dev/docs/get-started/install) with installation instructions for your OS. Before running this project please ensure Flutter is installed and configured on your machine. If you're new to Flutter, please checkout the [official guide](https://flutter.dev/docs/get-started/install) with installation instructions for your OS.
+4
View File
@@ -1,5 +1,9 @@
# Stream Chatty # Stream Chatty
⚠️ **Attention**: This example is no longer maintained. It uses older versions of our packages and was tested on an older version of Flutter.
---
Stream Chatty is a sample chat app made in Flutter using [Stream Chat](https://getstream.io/chat/sdk/flutter/), [Firebase](https://firebase.google.com/), and [flutter_bloc](https://bloclibrary.dev/#/). It has full light and dark mode support, real-time chat, and full authentication using Firebase auth. Stream Chatty is a sample chat app made in Flutter using [Stream Chat](https://getstream.io/chat/sdk/flutter/), [Firebase](https://firebase.google.com/), and [flutter_bloc](https://bloclibrary.dev/#/). It has full light and dark mode support, real-time chat, and full authentication using Firebase auth.
Stream Chatty was created by [Diego Velasquez](http://www.twitter.com/diegoveloper) as part of a Youtube series. A step by step guide to building Stream Chatty from scratch can be found here: Stream Chatty was created by [Diego Velasquez](http://www.twitter.com/diegoveloper) as part of a Youtube series. A step by step guide to building Stream Chatty from scratch can be found here:
@@ -6,7 +6,7 @@ class ImagePickerImpl extends ImagePickerRepository {
@override @override
Future<File?> pickImage() async { Future<File?> pickImage() async {
final picker = ImagePicker(); final picker = ImagePicker();
final pickedFile = await picker.getImage( final pickedFile = await picker.pickImage(
source: ImageSource.gallery, source: ImageSource.gallery,
maxWidth: 400, maxWidth: 400,
); );
@@ -8,7 +8,7 @@ class StreamApiLocalImpl extends StreamApiRepository {
final StreamChatClient _client; final StreamChatClient _client;
@override @override
Future<ChatUser> connectUser(ChatUser user, String? token) async { Future<ChatUser> connectUser(ChatUser user, String token) async {
Map<String, dynamic> extraData = {}; Map<String, dynamic> extraData = {};
if (user.image != null) { if (user.image != null) {
extraData['image'] = user.image; extraData['image'] = user.image;
@@ -16,7 +16,7 @@ class StreamApiLocalImpl extends StreamApiRepository {
if (user.name != null) { if (user.name != null) {
extraData['name'] = user.name; extraData['name'] = user.name;
} }
await _client.disconnect(); await _client.disconnectUser();
await _client.connectUser( await _client.connectUser(
User(id: user.id!, extraData: extraData as Map<String, Object>), User(id: user.id!, extraData: extraData as Map<String, Object>),
token, token,
@@ -28,7 +28,7 @@ class StreamApiLocalImpl extends StreamApiRepository {
Future<List<ChatUser>> getChatUsers() async { Future<List<ChatUser>> getChatUsers() async {
final result = await _client.queryUsers(); final result = await _client.queryUsers();
final chatUsers = result.users final chatUsers = result.users
.where((element) => element.id != _client.state.user!.id) .where((element) => element.id != _client.state.currentUser!.id)
.map( .map(
(e) => ChatUser( (e) => ChatUser(
id: e.id, id: e.id,
@@ -42,7 +42,7 @@ class StreamApiLocalImpl extends StreamApiRepository {
@override @override
Future<String> getToken(String userId) async { Future<String> getToken(String userId) async {
return _client.devToken(userId); return _client.devToken(userId).rawValue;
} }
@override @override
@@ -52,7 +52,7 @@ class StreamApiLocalImpl extends StreamApiRepository {
final channel = _client.channel('messaging', id: channelId, extraData: { final channel = _client.channel('messaging', id: channelId, extraData: {
'name': name!, 'name': name!,
'image': image!, 'image': image!,
'members': [_client.state.user!.id, ...members!], 'members': [_client.state.currentUser!.id, ...members!],
}); });
await channel.watch(); await channel.watch();
return channel; return channel;
@@ -61,11 +61,11 @@ class StreamApiLocalImpl extends StreamApiRepository {
@override @override
Future<Channel> createSimpleChat(String? friendId) async { Future<Channel> createSimpleChat(String? friendId) async {
final channel = _client.channel('messaging', final channel = _client.channel('messaging',
id: '${_client.state.user!.id.hashCode}${friendId.hashCode}', id: '${_client.state.currentUser!.id.hashCode}${friendId.hashCode}',
extraData: { extraData: {
'members': [ 'members': [
friendId, friendId,
_client.state.user!.id, _client.state.currentUser!.id,
], ],
}); });
await channel.watch(); await channel.watch();
@@ -74,7 +74,7 @@ class StreamApiLocalImpl extends StreamApiRepository {
@override @override
Future<void> logout() { Future<void> logout() {
return _client.disconnect(); return _client.disconnectUser();
} }
@override @override
@@ -84,6 +84,6 @@ class StreamApiLocalImpl extends StreamApiRepository {
User(id: userId), User(id: userId),
token, token,
); );
return _client.state.user!.name != userId; return _client.state.currentUser!.name != userId;
} }
} }
@@ -11,7 +11,7 @@ class StreamApiImpl extends StreamApiRepository {
final StreamChatClient _client; final StreamChatClient _client;
@override @override
Future<ChatUser> connectUser(ChatUser user, String? token) async { Future<ChatUser> connectUser(ChatUser user, String token) async {
Map<String, dynamic> extraData = {}; Map<String, dynamic> extraData = {};
if (user.image != null) { if (user.image != null) {
extraData['image'] = user.image; extraData['image'] = user.image;
@@ -19,7 +19,7 @@ class StreamApiImpl extends StreamApiRepository {
if (user.name != null) { if (user.name != null) {
extraData['name'] = user.name; extraData['name'] = user.name;
} }
await _client.disconnect(); await _client.disconnectUser();
await _client.connectUser( await _client.connectUser(
User(id: user.id!, extraData: extraData as Map<String, Object>), User(id: user.id!, extraData: extraData as Map<String, Object>),
token, token,
@@ -31,7 +31,7 @@ class StreamApiImpl extends StreamApiRepository {
Future<List<ChatUser>> getChatUsers() async { Future<List<ChatUser>> getChatUsers() async {
final result = await _client.queryUsers(); final result = await _client.queryUsers();
final chatUsers = result.users final chatUsers = result.users
.where((element) => element.id != _client.state.user!.id) .where((element) => element.id != _client.state.currentUser!.id)
.map( .map(
(e) => ChatUser( (e) => ChatUser(
id: e.id, id: e.id,
@@ -44,7 +44,7 @@ class StreamApiImpl extends StreamApiRepository {
} }
@override @override
Future<String?> getToken(String userId) async { Future<String> getToken(String userId) async {
//TODO: use your own implementation in Production //TODO: use your own implementation in Production
final response = await http.post( final response = await http.post(
Uri.parse('your_backend_url'), Uri.parse('your_backend_url'),
@@ -62,12 +62,13 @@ class StreamApiImpl extends StreamApiRepository {
} }
@override @override
Future<Channel> createGroupChat(String id, String? name, List<String?>? members, Future<Channel> createGroupChat(
String id, String? name, List<String?>? members,
{String? image}) async { {String? image}) async {
final channel = _client.channel('messaging', id: id, extraData: { final channel = _client.channel('messaging', id: id, extraData: {
'name': name!, 'name': name!,
'image': image!, 'image': image!,
'members': [_client.state.user!.id, ...members!], 'members': [_client.state.currentUser!.id, ...members!],
}); });
await channel.watch(); await channel.watch();
return channel; return channel;
@@ -76,11 +77,11 @@ class StreamApiImpl extends StreamApiRepository {
@override @override
Future<Channel> createSimpleChat(String? friendId) async { Future<Channel> createSimpleChat(String? friendId) async {
final channel = _client.channel('messaging', final channel = _client.channel('messaging',
id: '${_client.state.user!.id.hashCode}${friendId.hashCode}', id: '${_client.state.currentUser!.id.hashCode}${friendId.hashCode}',
extraData: { extraData: {
'members': [ 'members': [
friendId, friendId,
_client.state.user!.id, _client.state.currentUser!.id,
], ],
}); });
await channel.watch(); await channel.watch();
@@ -89,7 +90,7 @@ class StreamApiImpl extends StreamApiRepository {
@override @override
Future<void> logout() async { Future<void> logout() async {
return _client.disconnect(); return _client.disconnectUser();
} }
@override @override
@@ -99,6 +100,6 @@ class StreamApiImpl extends StreamApiRepository {
User(id: userId), User(id: userId),
token, token,
); );
return _client.state.user!.name != userId; return _client.state.currentUser!.name != userId;
} }
} }
@@ -3,12 +3,21 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
abstract class StreamApiRepository { abstract class StreamApiRepository {
Future<List<ChatUser>> getChatUsers(); Future<List<ChatUser>> getChatUsers();
Future<String?> getToken(String userId);
Future<String> getToken(String userId);
Future<bool> connectIfExist(String userId); Future<bool> connectIfExist(String userId);
Future<ChatUser> connectUser(ChatUser user, String? token);
Future<ChatUser> connectUser(ChatUser user, String token);
Future<Channel> createGroupChat( Future<Channel> createGroupChat(
String channelId, String? name, List<String?>? members, String channelId,
{String? image}); String? name,
List<String?>? members, {
String? image,
});
Future<Channel> createSimpleChat(String? friendId); Future<Channel> createSimpleChat(String? friendId);
Future<void> logout(); Future<void> logout();
} }
@@ -9,6 +9,7 @@ import '../exceptions/auth_exception.dart';
class ProfileInput { class ProfileInput {
ProfileInput({this.imageFile, this.name}); ProfileInput({this.imageFile, this.name});
final File? imageFile; final File? imageFile;
final String? name; final String? name;
} }
@@ -36,11 +37,12 @@ class ProfileSignInUseCase {
input.imageFile, 'users/${auth.id}'); input.imageFile, 'users/${auth.id}');
} }
await _streamApiRepository.connectUser( await _streamApiRepository.connectUser(
ChatUser( ChatUser(
name: input.name, name: input.name,
id: auth.id, id: auth.id,
image: image, image: image,
), ),
token); token,
);
} }
} }
+8 -4
View File
@@ -19,7 +19,10 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]); SystemChrome.setEnabledSystemUIMode(
SystemUiMode.manual,
overlays: [SystemUiOverlay.bottom],
);
return MultiRepositoryProvider( return MultiRepositoryProvider(
providers: buildRepositories(_streamChatClient), providers: buildRepositories(_streamChatClient),
@@ -36,9 +39,10 @@ class MyApp extends StatelessWidget {
client: _streamChatClient, client: _streamChatClient,
streamChatThemeData: streamChatThemeData:
StreamChatThemeData.fromTheme(Theme.of(context)).copyWith( StreamChatThemeData.fromTheme(Theme.of(context)).copyWith(
ownMessageTheme: MessageTheme( ownMessageTheme: MessageThemeData(
messageBackgroundColor: Theme.of(context).accentColor, messageBackgroundColor:
messageText: TextStyle(color: Colors.white), Theme.of(context).colorScheme.secondary,
messageTextStyle: TextStyle(color: Colors.white),
), ),
), ),
); );
@@ -80,7 +80,7 @@ class MyChannelPreview extends StatelessWidget {
tag: heroTag!, tag: heroTag!,
child: StreamChannel( child: StreamChannel(
channel: channel, channel: channel,
child: ChannelImage( child: ChannelAvatar(
onTap: onImageTap, onTap: onImageTap,
), ),
), ),
@@ -91,8 +91,9 @@ class MyChannelPreview extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Flexible( Flexible(
child: ChannelName( child: ChannelName(
textStyle: textStyle: StreamChatTheme.of(context)
StreamChatTheme.of(context).channelPreviewTheme.title, .channelPreviewTheme
.titleStyle,
), ),
), ),
StreamBuilder<List<Member>>( StreamBuilder<List<Member>>(
@@ -102,7 +103,8 @@ class MyChannelPreview extends StatelessWidget {
if (!snapshot.hasData || if (!snapshot.hasData ||
snapshot.data!.isEmpty || snapshot.data!.isEmpty ||
!snapshot.data!.any((Member e) => !snapshot.data!.any((Member e) =>
e.user!.id == channel.client.state.user!.id)) { e.user!.id ==
channel.client.state.currentUser!.id)) {
return SizedBox(); return SizedBox();
} }
return ChannelUnreadIndicator( return ChannelUnreadIndicator(
@@ -122,7 +124,7 @@ class MyChannelPreview extends StatelessWidget {
(m) => !m.isDeleted && m.shadowed != true, (m) => !m.isDeleted && m.shadowed != true,
); );
if (lastMessage?.user?.id == if (lastMessage?.user?.id ==
StreamChat.of(context).user!.id) { StreamChat.of(context).currentUser!.id) {
return Padding( return Padding(
padding: const EdgeInsets.only(right: 4.0), padding: const EdgeInsets.only(right: 4.0),
child: SendingIndicator( child: SendingIndicator(
@@ -131,9 +133,9 @@ class MyChannelPreview extends StatelessWidget {
.channelPreviewTheme .channelPreviewTheme
.indicatorIconSize, .indicatorIconSize,
isMessageRead: channel.state!.read isMessageRead: channel.state!.read
?.where((element) => .where((element) =>
element.user.id != element.user.id !=
channel.client.state.user!.id) channel.client.state.currentUser!.id)
.where((element) => element.lastRead .where((element) => element.lastRead
.isAfter(lastMessage.createdAt)) .isAfter(lastMessage.createdAt))
.isNotEmpty == .isNotEmpty ==
@@ -181,7 +183,9 @@ class MyChannelPreview extends StatelessWidget {
return Text( return Text(
stringDate, stringDate,
style: StreamChatTheme.of(context).channelPreviewTheme.lastMessageAt, style: StreamChatTheme.of(context)
.channelPreviewTheme
.lastMessageAtStyle,
); );
}, },
); );
@@ -199,11 +203,11 @@ class MyChannelPreview extends StatelessWidget {
' Channel is muted', ' Channel is muted',
style: StreamChatTheme.of(context) style: StreamChatTheme.of(context)
.channelPreviewTheme .channelPreviewTheme
.subtitle! .subtitleStyle!
.copyWith( .copyWith(
color: StreamChatTheme.of(context) color: StreamChatTheme.of(context)
.channelPreviewTheme .channelPreviewTheme
.subtitle! .subtitleStyle!
.color, .color,
), ),
), ),
@@ -213,9 +217,14 @@ class MyChannelPreview extends StatelessWidget {
return TypingIndicator( return TypingIndicator(
channel: channel, channel: channel,
alternativeWidget: _buildLastMessage(context), alternativeWidget: _buildLastMessage(context),
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle!.copyWith( style: StreamChatTheme.of(context)
color: .channelPreviewTheme
StreamChatTheme.of(context).channelPreviewTheme.subtitle!.color, .subtitleStyle!
.copyWith(
color: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitleStyle!
.color,
), ),
); );
} }
@@ -255,23 +264,31 @@ class MyChannelPreview extends StatelessWidget {
text, text,
lastMessage.mentionedUsers, lastMessage.mentionedUsers,
lastMessage.attachments, lastMessage.attachments,
StreamChatTheme.of(context).channelPreviewTheme.subtitle!.copyWith( StreamChatTheme.of(context)
color: StreamChatTheme.of(context) .channelPreviewTheme
.channelPreviewTheme .subtitleStyle!
.subtitle! .copyWith(
.color, color:
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) StreamChatTheme.of(context)
? FontStyle.italic .channelPreviewTheme
: FontStyle.normal), .subtitleStyle!
StreamChatTheme.of(context).channelPreviewTheme.subtitle!.copyWith( .color,
color: StreamChatTheme.of(context) fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
.channelPreviewTheme ? FontStyle.italic
.subtitle! : FontStyle.normal),
.color, StreamChatTheme.of(context)
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) .channelPreviewTheme
? FontStyle.italic .subtitleStyle!
: FontStyle.normal, .copyWith(
fontWeight: FontWeight.bold), color:
StreamChatTheme.of(context)
.channelPreviewTheme
.subtitleStyle!
.color,
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
? FontStyle.italic
: FontStyle.normal,
fontWeight: FontWeight.bold),
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -5,7 +5,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class ChatView extends StatelessWidget { class ChatView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textColor = Theme.of(context).appBarTheme.color; final textColor = Theme.of(context).appBarTheme.backgroundColor;
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).canvasColor, backgroundColor: Theme.of(context).canvasColor,
appBar: AppBar( appBar: AppBar(
@@ -25,7 +25,7 @@ class ChatView extends StatelessWidget {
child: ChannelListView( child: ChannelListView(
filter: Filter.in_( filter: Filter.in_(
'members', 'members',
[StreamChat.of(context).user!.id], [StreamChat.of(context).currentUser!.id],
), ),
sort: [SortOption('last_message_at')], sort: [SortOption('last_message_at')],
channelPreviewBuilder: (context, channel) { channelPreviewBuilder: (context, channel) {
@@ -37,10 +37,11 @@ class ChatView extends StatelessWidget {
onImageTap: () { onImageTap: () {
String? name; String? name;
String? image; String? image;
final currentUser = StreamChat.of(context).client.state.user; final currentUser =
StreamChat.of(context).client.state.currentUser;
if (channel.isGroup) { if (channel.isGroup) {
name = channel.extraData['name']; name = channel.extraData['name'] as String?;
image = channel.extraData['image']; image = channel.extraData['image'] as String?;
} else { } else {
final friend = channel.state!.members final friend = channel.state!.members
.where((element) => element.userId != currentUser!.id) .where((element) => element.userId != currentUser!.id)
@@ -25,8 +25,8 @@ class FriendsSelectionView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textColor = Theme.of(context).appBarTheme.color; final textColor = Theme.of(context).appBarTheme.backgroundColor;
final accentColor = Theme.of(context).accentColor; final accentColor = Theme.of(context).colorScheme.secondary;
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider( BlocProvider(
@@ -48,7 +48,7 @@ class GroupSelectionView extends StatelessWidget {
'New Group', 'New Group',
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
color: Theme.of(context).appBarTheme.color, color: Theme.of(context).appBarTheme.backgroundColor,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
), ),
), ),
@@ -10,9 +10,9 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class SettingsView extends StatelessWidget { class SettingsView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final user = StreamChat.of(context).client.state.user!; final user = StreamChat.of(context).client.state.currentUser!;
final image = user.extraData['image']; final image = user.extraData['image'];
final textColor = Theme.of(context).appBarTheme.color; final textColor = Theme.of(context).appBarTheme.backgroundColor;
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider( BlocProvider(
@@ -44,7 +44,6 @@ class SettingsView extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
AvatarImageView( AvatarImageView(
//TODO: implement change avatar
onTap: () => null, onTap: () => null,
child: image != null child: image != null
? Image.network( ? Image.network(
@@ -82,7 +82,7 @@ class ProfileVerifyView extends StatelessWidget {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
color: Theme.of(context).accentColor, color: Theme.of(context).colorScheme.secondary,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
context.read<ProfileVerifyCubit>().startChatting(); context.read<ProfileVerifyCubit>().startChatting();
+4 -2
View File
@@ -10,7 +10,8 @@ class Themes {
static final themeLight = ThemeData.light().copyWith( static final themeLight = ThemeData.light().copyWith(
backgroundColor: backgroundLightColor, backgroundColor: backgroundLightColor,
// selected color // selected color
accentColor: primaryColor, colorScheme:
ThemeData.light().colorScheme.copyWith(secondary: primaryColor),
// floating action button // floating action button
floatingActionButtonTheme: FloatingActionButtonThemeData( floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: primaryColor, backgroundColor: primaryColor,
@@ -32,7 +33,8 @@ class Themes {
static final themeDark = ThemeData.dark().copyWith( static final themeDark = ThemeData.dark().copyWith(
backgroundColor: backgroundDarkColor, backgroundColor: backgroundDarkColor,
// selected color // selected color
accentColor: primaryColor, colorScheme:
ThemeData.dark().colorScheme.copyWith(secondary: primaryColor),
// floating action button // floating action button
floatingActionButtonTheme: FloatingActionButtonThemeData( floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: primaryColor, backgroundColor: primaryColor,
+4 -4
View File
@@ -10,9 +10,9 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_bloc: ^7.0.0 flutter_bloc: ^7.1.0
stream_chat_flutter: ^2.0.0-nullsafety.3 stream_chat_flutter: ^2.0.0
uuid: ^3.0.4 uuid: ^3.0.5
firebase_core: ^1.2.0 firebase_core: ^1.2.0
google_sign_in: ^5.0.3 google_sign_in: ^5.0.3
@@ -20,7 +20,7 @@ dependencies:
firebase_storage: ^8.1.0 firebase_storage: ^8.1.0
shared_preferences: ^2.0.5 shared_preferences: ^2.0.5
http: any http: any
collection: ^1.15.0-nullsafety.4 collection: ^1.15.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
+3 -3
View File
@@ -2,14 +2,14 @@
![](https://raw.githubusercontent.com/GetStream/flutter-samples/master/images/imessage_clone_hero_v1.png) ![](https://raw.githubusercontent.com/GetStream/flutter-samples/master/images/imessage_clone_hero_v1.png)
iMessage Clone is a sample app implemented using Stream Chat and Flutter. It is a fully fledged messaging app built using core packages. iMessage Clone is a sample app implemented using Stream Chat and Flutter. It's a fully fledged messaging app built using the core Stream packages.
See our [blog post](https://getstream.io/blog/build-an-imessage-clone-with-streams-flutter-chat-sdk/) for a step-by-step guide on how to build this application.
## Getting Started ## Getting Started
Before running this project please ensure Flutter is installed and configured on your machine. If you're new to Flutter, please checkout the [official guide](https://flutter.dev/docs/get-started/install) with installation instructions for your OS. Before running this project please ensure Flutter is installed and configured on your machine. If you're new to Flutter, please checkout the [official guide](https://flutter.dev/docs/get-started/install) with installation instructions for your OS.
This project is only configured to support the following platforms: This project is only configured to support the following platforms:
- Android - Android
+3 -3
View File
@@ -26,7 +26,9 @@ Future<void> main() async {
class IMessage extends StatelessWidget { class IMessage extends StatelessWidget {
final StreamChatClient client; final StreamChatClient client;
IMessage({required this.client}); IMessage({required this.client});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
initializeDateFormatting('en_US', null); initializeDateFormatting('en_US', null);
@@ -58,9 +60,7 @@ class ChatLoader extends StatelessWidget {
Filter.equal('type', 'messaging'), Filter.equal('type', 'messaging'),
]), ]),
sort: [SortOption('last_message_at')], sort: [SortOption('last_message_at')],
pagination: PaginationParams( limit: 20,
limit: 20,
),
emptyBuilder: (BuildContext context) { emptyBuilder: (BuildContext context) {
return Center( return Center(
child: Text('Looks like you are not in any channels'), child: Text('Looks like you are not in any channels'),
+5 -5
View File
@@ -24,20 +24,20 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
intl: ^0.17.0 intl: ^0.17.0
stream_chat_flutter: ^2.2.0 stream_chat_flutter: ^3.0.0
animations: ^2.0.0 animations: ^2.0.1
collection: ^1.15.0 collection: ^1.15.0
cached_network_image: ^3.0.0 cached_network_image: ^3.1.0
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.0 cupertino_icons: ^1.0.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
pedantic: ^1.9.0 pedantic: ^1.11.1
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
@@ -251,7 +251,6 @@
"${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework", "${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework",
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework", "${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
"${BUILT_PRODUCTS_DIR}/flutter_app_badger/flutter_app_badger.framework", "${BUILT_PRODUCTS_DIR}/flutter_app_badger/flutter_app_badger.framework",
"${BUILT_PRODUCTS_DIR}/flutter_keyboard_visibility/flutter_keyboard_visibility.framework",
"${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework", "${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework",
"${BUILT_PRODUCTS_DIR}/flutter_secure_storage/flutter_secure_storage.framework", "${BUILT_PRODUCTS_DIR}/flutter_secure_storage/flutter_secure_storage.framework",
"${BUILT_PRODUCTS_DIR}/image_gallery_saver/image_gallery_saver.framework", "${BUILT_PRODUCTS_DIR}/image_gallery_saver/image_gallery_saver.framework",
@@ -281,7 +280,6 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_app_badger.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_app_badger.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_keyboard_visibility.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_secure_storage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_secure_storage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_gallery_saver.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_gallery_saver.framework",
@@ -39,7 +39,6 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
elevation: 1, elevation: 1,
centerTitle: true, centerTitle: true,
brightness: Theme.of(context).brightness,
title: Text( title: Text(
AppLocalizations.of(context).advancedOptions, AppLocalizations.of(context).advancedOptions,
style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith( style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith(
@@ -13,14 +13,14 @@ class ChannelFileDisplayScreen extends StatefulWidget {
/// limit: the number of users to return (max is 30) /// limit: the number of users to return (max is 30)
/// offset: the offset (max is 1000) /// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel /// message_limit: how many messages should be included to each channel
final PaginationParams? paginationParams; final PaginationParams paginationParams;
/// The builder used when the file list is empty. /// The builder used when the file list is empty.
final WidgetBuilder? emptyBuilder; final WidgetBuilder? emptyBuilder;
const ChannelFileDisplayScreen({ const ChannelFileDisplayScreen({
this.sortOptions, this.sortOptions,
this.paginationParams, this.paginationParams = const PaginationParams(limit: 20),
this.emptyBuilder, this.emptyBuilder,
}); });
@@ -53,7 +53,6 @@ class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
return Scaffold( return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
appBar: AppBar( appBar: AppBar(
brightness: Theme.of(context).brightness,
elevation: 1, elevation: 1,
centerTitle: true, centerTitle: true,
title: Text( title: Text(
@@ -151,7 +150,7 @@ class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
['file'], ['file'],
), ),
sort: widget.sortOptions, sort: widget.sortOptions,
pagination: widget.paginationParams!.copyWith( pagination: widget.paginationParams.copyWith(
offset: messageSearchBloc.messageResponses?.length ?? 0, offset: messageSearchBloc.messageResponses?.length ?? 0,
), ),
), ),
@@ -90,7 +90,7 @@ class _ChannelList extends State<ChannelList> {
), ),
], ],
pullToRefresh: false, pullToRefresh: false,
paginationParams: PaginationParams(limit: 20), limit: 20,
emptyBuilder: (_) { emptyBuilder: (_) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, viewportConstraints) { builder: (context, viewportConstraints) {
@@ -160,9 +160,7 @@ class _ChannelList extends State<ChannelList> {
swipeToAction: true, swipeToAction: true,
filter: Filter.in_('members', [user!.id]), filter: Filter.in_('members', [user!.id]),
presence: true, presence: true,
pagination: PaginationParams( limit: 20,
limit: 20,
),
onViewInfoTap: (channel) { onViewInfoTap: (channel) {
Navigator.pop(context); Navigator.pop(context);
if (channel.memberCount == 2 && channel.isDistinct) { if (channel.memberCount == 2 && channel.isDistinct) {
@@ -14,7 +14,7 @@ class ChannelMediaDisplayScreen extends StatefulWidget {
/// limit: the number of users to return (max is 30) /// limit: the number of users to return (max is 30)
/// offset: the offset (max is 1000) /// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel /// message_limit: how many messages should be included to each channel
final PaginationParams? paginationParams; final PaginationParams paginationParams;
/// The builder used when the file list is empty. /// The builder used when the file list is empty.
final WidgetBuilder? emptyBuilder; final WidgetBuilder? emptyBuilder;
@@ -26,7 +26,7 @@ class ChannelMediaDisplayScreen extends StatefulWidget {
const ChannelMediaDisplayScreen({ const ChannelMediaDisplayScreen({
required this.messageTheme, required this.messageTheme,
this.sortOptions, this.sortOptions,
this.paginationParams, this.paginationParams = const PaginationParams(limit: 20),
this.emptyBuilder, this.emptyBuilder,
this.onShowMessage, this.onShowMessage,
}); });
@@ -62,7 +62,6 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
return Scaffold( return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
appBar: AppBar( appBar: AppBar(
brightness: Theme.of(context).brightness,
elevation: 1, elevation: 1,
centerTitle: true, centerTitle: true,
title: Text( title: Text(
@@ -163,7 +162,7 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
['image', 'video'], ['image', 'video'],
), ),
sort: widget.sortOptions, sort: widget.sortOptions,
pagination: widget.paginationParams!.copyWith( pagination: widget.paginationParams.copyWith(
offset: messageSearchBloc.messageResponses?.length ?? 0, offset: messageSearchBloc.messageResponses?.length ?? 0,
), ),
), ),
@@ -235,7 +235,6 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
direction: SortOption.ASC, direction: SortOption.ASC,
), ),
], ],
paginationParams: PaginationParams(limit: 20),
onShowMessage: (m, c) async { onShowMessage: (m, c) async {
final client = StreamChat.of(context).client; final client = StreamChat.of(context).client;
final message = m; final message = m;
@@ -296,7 +295,6 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
direction: SortOption.ASC, direction: SortOption.ASC,
), ),
], ],
paginationParams: PaginationParams(limit: 20),
onShowMessage: (m, c) async { onShowMessage: (m, c) async {
final client = StreamChat.of(context).client; final client = StreamChat.of(context).client;
final message = m; final message = m;
@@ -356,7 +354,6 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
direction: SortOption.ASC, direction: SortOption.ASC,
), ),
], ],
paginationParams: PaginationParams(limit: 20),
), ),
), ),
), ),
@@ -508,7 +505,6 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
return Scaffold( return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
appBar: AppBar( appBar: AppBar(
brightness: Theme.of(context).brightness,
elevation: 1, elevation: 1,
centerTitle: true, centerTitle: true,
title: Text( title: Text(
@@ -62,7 +62,6 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
child: Scaffold( child: Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
appBar: AppBar( appBar: AppBar(
brightness: Theme.of(context).brightness,
elevation: 1, elevation: 1,
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
leading: const StreamBackButton(), leading: const StreamBackButton(),
@@ -93,7 +93,6 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
return Scaffold( return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
appBar: AppBar( appBar: AppBar(
brightness: Theme.of(context).brightness,
elevation: 1.0, elevation: 1.0,
toolbarHeight: 56.0, toolbarHeight: 56.0,
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
@@ -525,7 +524,6 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
direction: SortOption.ASC, direction: SortOption.ASC,
), ),
], ],
paginationParams: PaginationParams(limit: 20),
onShowMessage: (m, c) async { onShowMessage: (m, c) async {
final client = StreamChat.of(context).client; final client = StreamChat.of(context).client;
final message = m; final message = m;
@@ -737,9 +735,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
setState(() {}); setState(() {});
}, },
crossAxisCount: 4, crossAxisCount: 4,
pagination: PaginationParams( limit: 25,
limit: 25,
),
filter: Filter.and( filter: Filter.and(
[ [
if (_searchController!.text.isNotEmpty) if (_searchController!.text.isNotEmpty)
@@ -120,7 +120,6 @@ class _NewChatScreenState extends State<NewChatScreen> {
return Scaffold( return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
appBar: AppBar( appBar: AppBar(
brightness: Theme.of(context).brightness,
elevation: 0, elevation: 0,
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
leading: const StreamBackButton(), leading: const StreamBackButton(),
@@ -303,9 +302,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
_chipInputTextFieldState!.removeItem(user); _chipInputTextFieldState!.removeItem(user);
} }
}, },
pagination: PaginationParams( limit: 25,
limit: 25,
),
filter: Filter.and([ filter: Filter.and([
if (_userNameQuery.isNotEmpty) if (_userNameQuery.isNotEmpty)
Filter.autoComplete('name', _userNameQuery), Filter.autoComplete('name', _userNameQuery),
@@ -244,9 +244,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
}); });
} }
}, },
pagination: PaginationParams( limit: 25,
limit: 25,
),
filter: Filter.and([ filter: Filter.and([
if (_userNameQuery.isNotEmpty) if (_userNameQuery.isNotEmpty)
Filter.autoComplete('name', _userNameQuery), Filter.autoComplete('name', _userNameQuery),
@@ -63,7 +63,8 @@ void showLocalNotification(
android: AndroidNotificationDetails( android: AndroidNotificationDetails(
'message channel', 'message channel',
AppLocalizations.of(context).messageChannelName, AppLocalizations.of(context).messageChannelName,
AppLocalizations.of(context).messageChannelDescription, channelDescription:
AppLocalizations.of(context).messageChannelDescription,
priority: Priority.high, priority: Priority.high,
importance: Importance.high, importance: Importance.high,
), ),
@@ -14,7 +14,7 @@ class PinnedMessagesScreen extends StatefulWidget {
/// limit: the number of users to return (max is 30) /// limit: the number of users to return (max is 30)
/// offset: the offset (max is 1000) /// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel /// message_limit: how many messages should be included to each channel
final PaginationParams? paginationParams; final PaginationParams paginationParams;
/// The builder used when the file list is empty. /// The builder used when the file list is empty.
final WidgetBuilder? emptyBuilder; final WidgetBuilder? emptyBuilder;
@@ -26,7 +26,7 @@ class PinnedMessagesScreen extends StatefulWidget {
const PinnedMessagesScreen({ const PinnedMessagesScreen({
required this.messageTheme, required this.messageTheme,
this.sortOptions, this.sortOptions,
this.paginationParams, this.paginationParams = const PaginationParams(limit: 20),
this.emptyBuilder, this.emptyBuilder,
this.onShowMessage, this.onShowMessage,
}); });
@@ -61,7 +61,6 @@ class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
return Scaffold( return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
appBar: AppBar( appBar: AppBar(
brightness: Theme.of(context).brightness,
elevation: 1, elevation: 1,
centerTitle: true, centerTitle: true,
title: Text( title: Text(
@@ -156,7 +155,7 @@ class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
true, true,
), ),
sort: widget.sortOptions, sort: widget.sortOptions,
pagination: widget.paginationParams!.copyWith( pagination: widget.paginationParams.copyWith(
offset: messageSearchBloc.messageResponses?.length ?? 0, offset: messageSearchBloc.messageResponses?.length ?? 0,
), ),
), ),
@@ -23,7 +23,7 @@ class UserMentionsPage extends StatelessWidget {
direction: SortOption.ASC, direction: SortOption.ASC,
), ),
], ],
paginationParams: PaginationParams(limit: 20), limit: 20,
showResultCount: false, showResultCount: false,
emptyBuilder: (_) { emptyBuilder: (_) {
return LayoutBuilder( return LayoutBuilder(
+39 -10
View File
@@ -7,25 +7,54 @@ environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
flutter_app_badger: ^1.2.0 flutter_app_badger: ^1.3.0
flutter: flutter:
sdk: flutter sdk: flutter
stream_chat_flutter: ^2.2.0 stream_chat_flutter:
stream_chat_persistence: ^2.2.0 git:
stream_chat_localizations: ^1.1.0 url: https://github.com/GetStream/stream-chat-flutter.git
flutter_local_notifications: ^5.0.0+4 ref: develop
path: packages/stream_chat_flutter
stream_chat_persistence:
git:
url: https://github.com/GetStream/stream-chat-flutter.git
ref: develop
path: packages/stream_chat_persistence
stream_chat_localizations:
git:
url: https://github.com/GetStream/stream-chat-flutter.git
ref: develop
path: packages/stream_chat_localizations
flutter_local_notifications: ^9.0.0
flutter_svg: ^0.22.0 flutter_svg: ^0.22.0
flutter_secure_storage: ^4.2.0 flutter_secure_storage: ^4.2.1
yaml: ^3.1.0 yaml: ^3.1.0
uuid: ^3.0.4 uuid: ^3.0.5
streaming_shared_preferences: ^2.0.0 streaming_shared_preferences: ^2.0.0
lottie: ^1.0.1 lottie: ^1.2.1
collection: ^1.15.0-nullsafety.4 collection: ^1.15.0
dev_dependencies: dev_dependencies:
flutter_launcher_icons: ^0.9.0 flutter_launcher_icons: ^0.9.2
test: any test: any
dependency_overrides:
stream_chat:
git:
url: https://github.com/GetStream/stream-chat-flutter.git
ref: develop
path: packages/stream_chat
stream_chat_flutter_core:
git:
url: https://github.com/GetStream/stream-chat-flutter.git
ref: develop
path: packages/stream_chat_flutter_core
stream_chat_flutter:
git:
url: https://github.com/GetStream/stream-chat-flutter.git
ref: develop
path: packages/stream_chat_flutter
flutter: flutter:
assets: assets:
- assets/ - assets/