From c8bbd59732f1e11d2a104f15632a23f86560b608 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 6 Nov 2020 17:09:29 +0100 Subject: [PATCH 01/34] add choose user pages --- example/assets/logo.svg | 3 + example/lib/advanced_options_page.dart | 266 +++++++++++++++++++++++++ example/lib/choose_user_page.dart | 227 +++++++++++++++++++++ example/lib/main.dart | 77 +------ example/pubspec.yaml | 5 +- lib/src/stream_chat_theme.dart | 1 + lib/src/user_avatar.dart | 4 +- pubspec.yaml | 3 +- 8 files changed, 507 insertions(+), 79 deletions(-) create mode 100644 example/assets/logo.svg create mode 100644 example/lib/advanced_options_page.dart create mode 100644 example/lib/choose_user_page.dart diff --git a/example/assets/logo.svg b/example/assets/logo.svg new file mode 100644 index 00000000..49eecf7c --- /dev/null +++ b/example/assets/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/example/lib/advanced_options_page.dart b/example/lib/advanced_options_page.dart new file mode 100644 index 00000000..4d1de2ae --- /dev/null +++ b/example/lib/advanced_options_page.dart @@ -0,0 +1,266 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_apns/flutter_apns.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart' + hide Message; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'main.dart'; + +void showLocalNotification(Message message, ChannelModel channel) async { + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + final initializationSettingsAndroid = + AndroidInitializationSettings('launch_background'); + final initializationSettingsIOS = IOSInitializationSettings(); + final initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS, + ); + await flutterLocalNotificationsPlugin.initialize(initializationSettings); + await flutterLocalNotificationsPlugin.show( + message.id.hashCode, + '${message.user.name} @ ${channel.name}', + message.text, + NotificationDetails( + android: AndroidNotificationDetails( + 'message channel', + 'Message channel', + 'Channel used for showing messages', + priority: Priority.high, + importance: Importance.high, + ), + iOS: IOSNotificationDetails(), + ), + ); +} + +Future backgroundHandler(Map notification) async { + final messageId = notification['data']['message_id']; + + final notificationData = + await NotificationService.getAndStoreMessage(messageId); + + showLocalNotification( + notificationData.message, + notificationData.channel, + ); +} + +void _initNotifications(Client client) { + final connector = createPushConnector(); + connector.configure( + onBackgroundMessage: backgroundHandler, + ); + + connector.requestNotificationPermissions(); + connector.token.addListener(() { + if (connector.token.value != null) { + client.addDevice( + connector.token.value, + Platform.isAndroid ? 'firebase' : 'apn', + ); + } + }); +} + +class AdvancedOptionsPage extends StatelessWidget { + final _formKey = GlobalKey(); + final TextEditingController _apiKeyController = TextEditingController(); + final TextEditingController _userIdController = TextEditingController(); + final TextEditingController _userTokenController = TextEditingController(); + final TextEditingController _usernameController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomPadding: false, + appBar: AppBar( + backgroundColor: StreamChatTheme.of(context).primaryColor, + elevation: 1, + centerTitle: true, + title: Text( + 'Advanced Options', + style: Theme.of(context).textTheme.subtitle1.copyWith( + fontWeight: FontWeight.bold, + ), + ), + leading: IconButton( + icon: Icon( + StreamIcons.left, + color: Colors.black, + ), + onPressed: () { + Navigator.pop(context); + }, + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + child: Form( + key: _formKey, + onChanged: () {}, + child: Column( + children: [ + TextFormField( + controller: _apiKeyController, + validator: (value) { + if (value.isEmpty) { + return 'Please enter the user Chat API Key'; + } + return null; + }, + decoration: InputDecoration( + labelStyle: TextStyle( + fontSize: 14, + color: Colors.black.withOpacity(.5), + ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: Color(0xffF5F5F5), + filled: true, + labelText: 'Chat API Key', + ), + textInputAction: TextInputAction.next, + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: TextFormField( + controller: _userIdController, + validator: (value) { + if (value.isEmpty) { + return 'Please enter the User ID'; + } + return null; + }, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + labelStyle: TextStyle( + fontSize: 14, + color: Colors.black.withOpacity(.5), + ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: Color(0xffF5F5F5), + filled: true, + labelText: 'User ID', + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: TextFormField( + controller: _userTokenController, + validator: (value) { + if (value.isEmpty) { + return 'Please enter the user token'; + } + return null; + }, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + labelStyle: TextStyle( + fontSize: 14, + color: Colors.black.withOpacity(.5), + ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: Color(0xffF5F5F5), + filled: true, + labelText: 'User Token', + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: TextFormField( + controller: _usernameController, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + labelStyle: TextStyle( + fontSize: 14, + color: Colors.black.withOpacity(.5), + ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: Color(0xffF5F5F5), + filled: true, + labelText: 'Username (optional)', + ), + ), + ), + Expanded( + child: Align( + alignment: Alignment.bottomCenter, + child: FlatButton( + color: StreamChatTheme.of(context).accentColor, + minWidth: double.infinity, + height: 48, + child: Text( + 'Login', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26), + ), + onPressed: () async { + if (_formKey.currentState.validate()) { + final apiKey = _apiKeyController.text; + final userId = _userIdController.text; + final userToken = _userTokenController.text; + final username = _usernameController.text; + + final client = StreamChat.of(context).client; + client.apiKey = apiKey; + + await client.setUser( + User(id: userId, extraData: { + 'name': username, + }), + userToken, + ); + + if (!kIsWeb) { + _initNotifications(client); + } + + Navigator.pop(context); + await Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) { + return StreamChat( + client: client, + child: ChannelListPage(), + ); + }, + ), + ); + } + }, + ), + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/choose_user_page.dart b/example/lib/choose_user_page.dart new file mode 100644 index 00000000..2d9f1c7a --- /dev/null +++ b/example/lib/choose_user_page.dart @@ -0,0 +1,227 @@ +import 'dart:io'; + +import 'package:example/advanced_options_page.dart'; +import 'package:example/main.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_apns/flutter_apns.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart' + hide Message; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +void showLocalNotification(Message message, ChannelModel channel) async { + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + final initializationSettingsAndroid = + AndroidInitializationSettings('launch_background'); + final initializationSettingsIOS = IOSInitializationSettings(); + final initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS, + ); + await flutterLocalNotificationsPlugin.initialize(initializationSettings); + await flutterLocalNotificationsPlugin.show( + message.id.hashCode, + '${message.user.name} @ ${channel.name}', + message.text, + NotificationDetails( + android: AndroidNotificationDetails( + 'message channel', + 'Message channel', + 'Channel used for showing messages', + priority: Priority.high, + importance: Importance.high, + ), + iOS: IOSNotificationDetails(), + ), + ); +} + +Future backgroundHandler(Map notification) async { + final messageId = notification['data']['message_id']; + + final notificationData = + await NotificationService.getAndStoreMessage(messageId); + + showLocalNotification( + notificationData.message, + notificationData.channel, + ); +} + +void _initNotifications(Client client) { + final connector = createPushConnector(); + connector.configure( + onBackgroundMessage: backgroundHandler, + ); + + connector.requestNotificationPermissions(); + connector.token.addListener(() { + if (connector.token.value != null) { + client.addDevice( + connector.token.value, + Platform.isAndroid ? 'firebase' : 'apn', + ); + } + }); +} + +class ChooseUserPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + final users = { + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidmlzaGFsIn0._JHWzo92fpTWZMZriJHXqOng6ShYVmWrdaIaPwEPKBg': + User( + id: 'vishal', + extraData: { + 'name': 'Vishal', + }, + ), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A': + User( + id: 'super-band-9', + extraData: { + 'name': 'John Doe', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.GMEKyEhONmFnqtkf1TR1A3oUOSIWhjfQv5RpI906dAM': + User( + id: 'salvatore', + extraData: { + 'name': 'Salvatore', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidG9tbWFzbyJ9.GTalMeZHaBdpIM5w-KWrVIDSy-ODkHTRkf1GZbWAveM': + User( + id: 'tommaso', + extraData: { + 'name': 'Tommaso', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiamFhcCJ9.xpGE2lu4OoYS7-2yy6PbF0gJnxOaxeGO4EU6xo4EdMU': + User( + id: 'jaap', + extraData: { + 'name': 'Jaap', + }, + ), + }; + return Scaffold( + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 34, + bottom: 20, + ), + child: Center( + child: SvgPicture.asset( + 'assets/logo.svg', + height: 40, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 13.0), + child: Text( + 'Welcome to Stream Chat', + style: TextStyle( + fontSize: 22, + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + Text( + 'Select a user to try the Flutter SDK:', + style: TextStyle( + fontSize: 14.5, + color: Colors.black, + ), + ), + Expanded( + child: ListView( + children: [ + ...users.entries.map((entry) { + final token = entry.key; + final user = entry.value; + return ListTile( + onTap: () async { + final client = StreamChat.of(context).client; + + await client.setUser( + User(id: user.id, extraData: { + 'name': user.name, + }), + token, + ); + + if (!kIsWeb) { + _initNotifications(client); + } + + await Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) { + return StreamChat( + client: client, + child: ChannelListPage(), + ); + }, + ), + ); + }, + leading: UserAvatar( + user: user, + constraints: BoxConstraints.tight( + Size.fromRadius(20), + ), + ), + title: Text( + user.name, + style: TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text('Stream test account'), + trailing: Icon( + StreamIcons.arrow_right, + color: StreamChatTheme.of(context).accentColor, + ), + ); + }), + ListTile( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AdvancedOptionsPage(), + ), + ); + }, + leading: CircleAvatar( + child: Icon( + StreamIcons.settings, + color: Colors.black, + ), + backgroundColor: StreamChatTheme.of(context).secondaryColor, + ), + title: Text( + 'Advanced Options', + style: TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text('Custom settings'), + trailing: Icon( + StreamIcons.arrow_right, + color: StreamChatTheme.of(context).accentColor, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index ca727cf7..8218f203 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,70 +1,11 @@ import 'dart:io'; +import 'package:example/choose_user_page.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_apns/apns.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart' - hide Message; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -void showLocalNotification(Message message, ChannelModel channel) async { - FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); - final initializationSettingsAndroid = - AndroidInitializationSettings('launch_background'); - final initializationSettingsIOS = IOSInitializationSettings(); - final initializationSettings = InitializationSettings( - android: initializationSettingsAndroid, - iOS: initializationSettingsIOS, - ); - await flutterLocalNotificationsPlugin.initialize(initializationSettings); - await flutterLocalNotificationsPlugin.show( - message.id.hashCode, - '${message.user.name} @ ${channel.name}', - message.text, - NotificationDetails( - android: AndroidNotificationDetails( - 'message channel', - 'Message channel', - 'Channel used for showing messages', - priority: Priority.high, - importance: Importance.high, - ), - iOS: IOSNotificationDetails(), - ), - ); -} - -Future backgroundHandler(Map notification) async { - final messageId = notification['data']['message_id']; - - final notificationData = - await NotificationService.getAndStoreMessage(messageId); - - showLocalNotification( - notificationData.message, - notificationData.channel, - ); -} - -void _initNotifications(Client client) { - final connector = createPushConnector(); - connector.configure( - onBackgroundMessage: backgroundHandler, - ); - - connector.requestNotificationPermissions(); - connector.token.addListener(() { - if (connector.token.value != null) { - client.addDevice( - connector.token.value, - Platform.isAndroid ? 'firebase' : 'apn', - ); - } - }); -} - void main() async { final client = Client( 's2dxdhpxd94g', @@ -74,17 +15,6 @@ void main() async { persistenceEnabled: true, ); - await client.setUser( - User(id: 'super-band-9', extraData: { - 'name': 'Jonathan Doe', - }), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', - ); - - if (!kIsWeb) { - _initNotifications(client); - } - runApp(MyApp(client)); } @@ -98,16 +28,15 @@ class MyApp extends StatelessWidget { return MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), + //TODO change to system once dark theme is implemented themeMode: ThemeMode.light, - - ///TODO change to system once dark theme is implemented builder: (context, widget) { return StreamChat( child: widget, client: client, ); }, - home: ChannelListPage(), + home: ChooseUserPage(), ); } } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 1e527964..e4604f44 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,7 +1,7 @@ name: example description: A new Flutter project. -version: 1.0.42+44 +version: 1.0.43+45 environment: sdk: ">=2.2.2 <3.0.0" @@ -13,6 +13,7 @@ dependencies: path: ../ flutter_apns: ^1.3.1 flutter_local_notifications: ^2.0.0 + flutter_svg: ^0.19.1 dev_dependencies: flutter_test: @@ -23,6 +24,8 @@ dev_dependencies: test: any flutter: + assets: + - assets/ uses-material-design: true flutter_icons: diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index cc959a35..1dfd42b7 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -218,6 +218,7 @@ class StreamChatThemeData { final accentColor = Color(0xff006cff); final isDark = theme.brightness == Brightness.dark; return StreamChatThemeData( + secondaryColor: Color(0xffEAEAEA), accentColor: accentColor, primaryColor: isDark ? Colors.black : Colors.white, primaryIconTheme: IconThemeData( diff --git a/lib/src/user_avatar.dart b/lib/src/user_avatar.dart index a86206eb..ce3d29ec 100644 --- a/lib/src/user_avatar.dart +++ b/lib/src/user_avatar.dart @@ -55,9 +55,7 @@ class UserAvatar extends StatelessWidget { errorWidget: (_, __, ___) { return Center( child: Text( - user.extraData?.containsKey('name') ?? false - ? user.extraData['name'][0] - : '', + user.name[0], style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, diff --git a/pubspec.yaml b/pubspec.yaml index 0e768587..e5c57193 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,7 +26,8 @@ dependencies: file_picker: ^2.0.8+1 image_picker: ^0.6.7+2 flutter_keyboard_visibility: ^3.2.1 - stream_chat: ^0.2.10+1 + stream_chat: + path: ../stream_chat_dart emojis: ^0.9.3 mime: ^0.9.6+3 visibility_detector: ^0.1.5 From 6613476700d7d10586c43ad0b524d4a4e3e20149 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 9 Nov 2020 09:43:47 +0100 Subject: [PATCH 02/34] channel query on local notification was using event.type --- example/lib/main.dart | 3 +-- example/pubspec.yaml | 2 +- lib/src/stream_chat.dart | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 9eee4db0..fd051afc 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -69,8 +69,7 @@ void main() async { final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, - showLocalNotification: - (!kIsWeb && Platform.isAndroid) ? showLocalNotification : null, + showLocalNotification: (m, c) {}, persistenceEnabled: true, ); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index c56b9834..babdff9a 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.22+23 +version: 1.0.23+24 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index dfc6095b..d11f5fef 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -186,10 +186,10 @@ class StreamChatState extends State with WidgetsBindingObserver { .listen((event) async { var channel = client.state.channels[event.cid]; - if (channel == null) { + if (channel != null) { channel = client.channel( - event.type, - id: event.cid.split(':')[1], + event.channelType, + id: event.channelId, ); await channel.query(); } From 1285f82a6488b6aa6e017bc4c288aadebd21318b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 9 Nov 2020 09:44:10 +0100 Subject: [PATCH 03/34] fix condition --- lib/src/stream_chat.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index d11f5fef..081849b5 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -186,7 +186,7 @@ class StreamChatState extends State with WidgetsBindingObserver { .listen((event) async { var channel = client.state.channels[event.cid]; - if (channel != null) { + if (channel == null) { channel = client.channel( event.channelType, id: event.channelId, From 2f8891af7f23cac11f2717138e1c861782ad3a0a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 9 Nov 2020 09:46:54 +0100 Subject: [PATCH 04/34] version bump --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e51cb8bc..e924ddc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.11+1 + +- Fix error with channel query while handling background notifications + ## 0.2.11 - Update llc dependency diff --git a/pubspec.yaml b/pubspec.yaml index 56d2270d..70e4ed1e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.11 +version: 0.2.11+1 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues From 7740932f2f576a70784858871a6b29b998b247b0 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 9 Nov 2020 09:48:04 +0100 Subject: [PATCH 05/34] fix example --- example/lib/main.dart | 3 ++- example/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index fd051afc..9eee4db0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -69,7 +69,8 @@ void main() async { final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, - showLocalNotification: (m, c) {}, + showLocalNotification: + (!kIsWeb && Platform.isAndroid) ? showLocalNotification : null, persistenceEnabled: true, ); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index babdff9a..9f50d014 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.23+24 +version: 1.0.24+25 environment: sdk: ">=2.2.2 <3.0.0" From 59359784fbefa4bd084c383f1926bee53a61524c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 9 Nov 2020 10:41:05 +0100 Subject: [PATCH 06/34] remove channel.query --- example/android/app/build.gradle | 2 +- example/ios/Flutter/.last_build_id | 2 +- example/ios/Podfile.lock | 110 +++++++++++++---------------- example/lib/main.dart | 3 +- example/pubspec.yaml | 2 +- lib/src/stream_chat.dart | 13 ++-- 6 files changed, 59 insertions(+), 73 deletions(-) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index dda667cf..4fd90eac 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -39,7 +39,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.example" - minSdkVersion 16 + minSdkVersion 21 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/example/ios/Flutter/.last_build_id b/example/ios/Flutter/.last_build_id index ab2c77bd..8aba7787 100644 --- a/example/ios/Flutter/.last_build_id +++ b/example/ios/Flutter/.last_build_id @@ -1 +1 @@ -13ecb9b157bb3e3d1ca2efb15c813a88 \ No newline at end of file +bb5f9103d9045cd6244bcae1f5f343e5 \ No newline at end of file diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index f289e1b7..f17b1bc2 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -33,49 +33,45 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter - - Firebase/CoreOnly (6.26.0): - - FirebaseCore (= 6.7.2) - - Firebase/Messaging (6.26.0): + - Firebase/CoreOnly (6.33.0): + - FirebaseCore (= 6.10.3) + - Firebase/Messaging (6.33.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 4.4.1) - - firebase_core (0.5.0): - - Firebase/CoreOnly (~> 6.26.0) + - FirebaseMessaging (~> 4.7.0) + - firebase_core (0.5.1): + - Firebase/CoreOnly (~> 6.33.0) - Flutter - - firebase_messaging (7.0.2): - - Firebase/CoreOnly (~> 6.26.0) - - Firebase/Messaging (~> 6.26.0) + - firebase_messaging (7.0.3): + - Firebase/CoreOnly (~> 6.33.0) + - Firebase/Messaging (~> 6.33.0) - firebase_core - Flutter - - FirebaseAnalyticsInterop (1.5.0) - - FirebaseCore (6.7.2): - - FirebaseCoreDiagnostics (~> 1.3) - - FirebaseCoreDiagnosticsInterop (~> 1.2) - - GoogleUtilities/Environment (~> 6.5) - - GoogleUtilities/Logger (~> 6.5) + - FirebaseCore (6.10.3): + - FirebaseCoreDiagnostics (~> 1.6) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/Logger (~> 6.7) - FirebaseCoreDiagnostics (1.7.0): - GoogleDataTransport (~> 7.4) - GoogleUtilities/Environment (~> 6.7) - GoogleUtilities/Logger (~> 6.7) - nanopb (~> 1.30906.0) - - FirebaseCoreDiagnosticsInterop (1.2.0) - - FirebaseInstallations (1.3.0): - - FirebaseCore (~> 6.6) - - GoogleUtilities/Environment (~> 6.6) - - GoogleUtilities/UserDefaults (~> 6.6) + - FirebaseInstallations (1.7.0): + - FirebaseCore (~> 6.10) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/UserDefaults (~> 6.7) - PromisesObjC (~> 1.2) - - FirebaseInstanceID (4.3.4): - - FirebaseCore (~> 6.6) - - FirebaseInstallations (~> 1.0) - - GoogleUtilities/Environment (~> 6.5) - - GoogleUtilities/UserDefaults (~> 6.5) - - FirebaseMessaging (4.4.1): - - FirebaseAnalyticsInterop (~> 1.5) - - FirebaseCore (~> 6.6) - - FirebaseInstanceID (~> 4.3) - - GoogleUtilities/AppDelegateSwizzler (~> 6.5) - - GoogleUtilities/Environment (~> 6.5) - - GoogleUtilities/Reachability (~> 6.5) - - GoogleUtilities/UserDefaults (~> 6.5) + - FirebaseInstanceID (4.8.0): + - FirebaseCore (~> 6.10) + - FirebaseInstallations (~> 1.6) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/UserDefaults (~> 6.7) + - FirebaseMessaging (4.7.1): + - FirebaseCore (~> 6.10) + - FirebaseInstanceID (~> 4.7) + - GoogleUtilities/AppDelegateSwizzler (~> 6.7) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/Reachability (~> 6.7) + - GoogleUtilities/UserDefaults (~> 6.7) - Protobuf (>= 3.9.2, ~> 3.9) - Flutter (1.0.0) - flutter_apns (0.0.1): @@ -87,7 +83,7 @@ PODS: - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) - - GoogleDataTransport (7.4.0): + - GoogleDataTransport (7.5.1): - nanopb (~> 1.30906.0) - GoogleUtilities/AppDelegateSwizzler (6.7.2): - GoogleUtilities/Environment @@ -115,16 +111,16 @@ PODS: - nanopb/encode (1.30906.0) - path_provider (0.0.1): - Flutter - - PromisesObjC (1.2.10) + - PromisesObjC (1.2.11) - Protobuf (3.13.0) - - SDWebImage (5.9.2): - - SDWebImage/Core (= 5.9.2) - - SDWebImage/Core (5.9.2) + - SDWebImage (5.9.4): + - SDWebImage/Core (= 5.9.4) + - SDWebImage/Core (5.9.4) - shared_preferences (0.0.1): - Flutter - - sqflite (0.0.1): + - sqflite (0.0.2): - Flutter - - FMDB (~> 2.7.2) + - FMDB (>= 2.7.5) - sqlite3 (3.32.3): - sqlite3/common (= 3.32.3) - sqlite3/common (3.32.3) @@ -144,7 +140,7 @@ PODS: - sqlite3/perf-threadsafe - sqlite3/rtree - Starscream (4.0.4) - - StreamChatClient (2.4.0): + - StreamChatClient (2.4.1): - Starscream (~> 4.0) - SwiftyGif (5.3.0) - url_launcher (0.0.1): @@ -177,10 +173,8 @@ SPEC REPOS: - DKImagePickerController - DKPhotoGallery - Firebase - - FirebaseAnalyticsInterop - FirebaseCore - FirebaseCoreDiagnostics - - FirebaseCoreDiagnosticsInterop - FirebaseInstallations - FirebaseInstanceID - FirebaseMessaging @@ -232,35 +226,33 @@ SPEC CHECKSUMS: DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 - Firebase: 7cf5f9c67f03cb3b606d1d6535286e1080e57eb6 - firebase_core: 3134fe79d257d430f163b558caf52a10a87efe8a - firebase_messaging: 2844c37f9ce87c0904b38fe435223161b1a71528 - FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae - FirebaseCore: f42e5e5f382cdcf6b617ed737bf6c871a6947b17 + Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5 + firebase_core: aa25a5dc6b492ecab37587c53d8420135f0cac90 + firebase_messaging: 0aea2cd5885b65e19ede58ee3507f485c992cc75 + FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1 - FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 - FirebaseInstallations: 6f5f680e65dc374397a483c32d1799ba822a395b - FirebaseInstanceID: cef67c4967c7cecb56ea65d8acbb4834825c587b - FirebaseMessaging: 29543feb343b09546ab3aa04d008ee8595b43c44 + FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2 + FirebaseInstanceID: bd3ffc24367f901a43c063b36c640b345a4a5dd1 + FirebaseMessaging: 5eca4ef173de76253352511aafef774caa1cba2a Flutter: 0e3d915762c693b495b44d77113d4970485de6ec - flutter_apns: f516b118e423fe7c0a38771180549c4d6cb67c2f + flutter_apns: ddc629f26016140bf52165040b0a8e8869f9ce32 flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - GoogleDataTransport: b7f406340a291370045a270c599e53c6fa6ec20f + GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833 GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3 image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09 nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c - PromisesObjC: b14b1c6b68e306650688599de8a45e49fae81151 + PromisesObjC: 8c196f5a328c2cba3e74624585467a557dcb482f Protobuf: 3dac39b34a08151c6d949560efe3f86134a3f748 - SDWebImage: 0b42b8719ab0c5257177d5894306e8a336b21cbb + SDWebImage: b69257f4ab14e9b6a2ef53e910fdf914d8f757c1 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d - sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 + sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 sqlite3: 8f7d2078ae27778699a622a94b853285793422a2 sqlite3_flutter_libs: 5651f8ff48e3b44d910863c4ea5916085b1b245f Starscream: 5178aed56b316f13fa3bc55694e583d35dd414d9 - StreamChatClient: 8c83a141e753e45fa096ff56d4b782d59e46f251 + StreamChatClient: d061cf52babcd9df930aee9cf864ab0379427eff SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40 url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e @@ -268,4 +260,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: eb001256612a59f8f9e4d083ad8b9671e69dd184 -COCOAPODS: 1.8.4 +COCOAPODS: 1.10.0.rc.1 diff --git a/example/lib/main.dart b/example/lib/main.dart index 9eee4db0..fd051afc 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -69,8 +69,7 @@ void main() async { final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, - showLocalNotification: - (!kIsWeb && Platform.isAndroid) ? showLocalNotification : null, + showLocalNotification: (m, c) {}, persistenceEnabled: true, ); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9f50d014..abed021e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.24+25 +version: 1.0.25+26 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 081849b5..bff3c9f7 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -184,15 +184,10 @@ class StreamChatState extends State with WidgetsBindingObserver { .where((e) => e.user?.id != user.id) .where((e) => e.message.silent != true) .listen((event) async { - var channel = client.state.channels[event.cid]; - - if (channel == null) { - channel = client.channel( - event.channelType, - id: event.channelId, - ); - await channel.query(); - } + final channel = client.channel( + event.channelType, + id: event.channelId, + ); client.showLocalNotification( event.message, From 13b909b5fb86c5e1a8819c01b8dff9bca44d6af4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 9 Nov 2020 10:41:57 +0100 Subject: [PATCH 07/34] fix example --- example/lib/main.dart | 3 ++- example/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index fd051afc..9eee4db0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -69,7 +69,8 @@ void main() async { final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, - showLocalNotification: (m, c) {}, + showLocalNotification: + (!kIsWeb && Platform.isAndroid) ? showLocalNotification : null, persistenceEnabled: true, ); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index abed021e..0ba6b827 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.25+26 +version: 1.0.26+27 environment: sdk: ">=2.2.2 <3.0.0" From bb6ba72b1a00912a4475c0af4d7e55902cab37b2 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 9 Nov 2020 18:39:16 +0100 Subject: [PATCH 08/34] refactoring --- example/lib/advanced_options_page.dart | 67 +-------- example/lib/choose_user_page.dart | 189 +++++++++---------------- example/lib/main.dart | 2 + example/lib/notifications_service.dart | 62 ++++++++ example/pubspec.yaml | 2 +- 5 files changed, 138 insertions(+), 184 deletions(-) create mode 100644 example/lib/notifications_service.dart diff --git a/example/lib/advanced_options_page.dart b/example/lib/advanced_options_page.dart index 4d1de2ae..b6062c30 100644 --- a/example/lib/advanced_options_page.dart +++ b/example/lib/advanced_options_page.dart @@ -1,70 +1,9 @@ -import 'dart:io'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_apns/flutter_apns.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart' - hide Message; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'main.dart'; - -void showLocalNotification(Message message, ChannelModel channel) async { - FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); - final initializationSettingsAndroid = - AndroidInitializationSettings('launch_background'); - final initializationSettingsIOS = IOSInitializationSettings(); - final initializationSettings = InitializationSettings( - android: initializationSettingsAndroid, - iOS: initializationSettingsIOS, - ); - await flutterLocalNotificationsPlugin.initialize(initializationSettings); - await flutterLocalNotificationsPlugin.show( - message.id.hashCode, - '${message.user.name} @ ${channel.name}', - message.text, - NotificationDetails( - android: AndroidNotificationDetails( - 'message channel', - 'Message channel', - 'Channel used for showing messages', - priority: Priority.high, - importance: Importance.high, - ), - iOS: IOSNotificationDetails(), - ), - ); -} - -Future backgroundHandler(Map notification) async { - final messageId = notification['data']['message_id']; - - final notificationData = - await NotificationService.getAndStoreMessage(messageId); - - showLocalNotification( - notificationData.message, - notificationData.channel, - ); -} - -void _initNotifications(Client client) { - final connector = createPushConnector(); - connector.configure( - onBackgroundMessage: backgroundHandler, - ); - - connector.requestNotificationPermissions(); - connector.token.addListener(() { - if (connector.token.value != null) { - client.addDevice( - connector.token.value, - Platform.isAndroid ? 'firebase' : 'apn', - ); - } - }); -} +import 'notifications_service.dart'; class AdvancedOptionsPage extends StatelessWidget { final _formKey = GlobalKey(); @@ -111,7 +50,7 @@ class AdvancedOptionsPage extends StatelessWidget { controller: _apiKeyController, validator: (value) { if (value.isEmpty) { - return 'Please enter the user Chat API Key'; + return 'Please enter the Chat API Key'; } return null; }, @@ -237,7 +176,7 @@ class AdvancedOptionsPage extends StatelessWidget { ); if (!kIsWeb) { - _initNotifications(client); + initNotifications(client); } Navigator.pop(context); diff --git a/example/lib/choose_user_page.dart b/example/lib/choose_user_page.dart index 2d9f1c7a..ef710e53 100644 --- a/example/lib/choose_user_page.dart +++ b/example/lib/choose_user_page.dart @@ -1,71 +1,11 @@ -import 'dart:io'; - import 'package:example/advanced_options_page.dart'; import 'package:example/main.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_apns/flutter_apns.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart' - hide Message; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -void showLocalNotification(Message message, ChannelModel channel) async { - FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); - final initializationSettingsAndroid = - AndroidInitializationSettings('launch_background'); - final initializationSettingsIOS = IOSInitializationSettings(); - final initializationSettings = InitializationSettings( - android: initializationSettingsAndroid, - iOS: initializationSettingsIOS, - ); - await flutterLocalNotificationsPlugin.initialize(initializationSettings); - await flutterLocalNotificationsPlugin.show( - message.id.hashCode, - '${message.user.name} @ ${channel.name}', - message.text, - NotificationDetails( - android: AndroidNotificationDetails( - 'message channel', - 'Message channel', - 'Channel used for showing messages', - priority: Priority.high, - importance: Importance.high, - ), - iOS: IOSNotificationDetails(), - ), - ); -} - -Future backgroundHandler(Map notification) async { - final messageId = notification['data']['message_id']; - - final notificationData = - await NotificationService.getAndStoreMessage(messageId); - - showLocalNotification( - notificationData.message, - notificationData.channel, - ); -} - -void _initNotifications(Client client) { - final connector = createPushConnector(); - connector.configure( - onBackgroundMessage: backgroundHandler, - ); - - connector.requestNotificationPermissions(); - connector.token.addListener(() { - if (connector.token.value != null) { - client.addDevice( - connector.token.value, - Platform.isAndroid ? 'firebase' : 'apn', - ); - } - }); -} +import 'notifications_service.dart'; class ChooseUserPage extends StatelessWidget { @override @@ -142,82 +82,93 @@ class ChooseUserPage extends StatelessWidget { ), ), Expanded( - child: ListView( - children: [ - ...users.entries.map((entry) { - final token = entry.key; - final user = entry.value; - return ListTile( - onTap: () async { - final client = StreamChat.of(context).client; + child: ListView.separated( + separatorBuilder: (context, i) { + return Container( + width: double.infinity, + color: Colors.black12, + height: 1, + ); + }, + itemCount: users.length + 1, + itemBuilder: (context, i) { + return [ + ...users.entries.map((entry) { + final token = entry.key; + final user = entry.value; + return ListTile( + onTap: () async { + final client = StreamChat.of(context).client; - await client.setUser( - User(id: user.id, extraData: { - 'name': user.name, - }), - token, - ); + await client.setUser( + User(id: user.id, extraData: { + 'name': user.name, + }), + token, + ); - if (!kIsWeb) { - _initNotifications(client); - } + if (!kIsWeb) { + initNotifications(client); + } - await Navigator.pushReplacement( + await Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) { + return StreamChat( + client: client, + child: ChannelListPage(), + ); + }, + ), + ); + }, + leading: UserAvatar( + user: user, + constraints: BoxConstraints.tight( + Size.fromRadius(20), + ), + ), + title: Text( + user.name, + style: TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text('Stream test account'), + trailing: Icon( + StreamIcons.arrow_right, + color: StreamChatTheme.of(context).accentColor, + ), + ); + }), + ListTile( + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) { - return StreamChat( - client: client, - child: ChannelListPage(), - ); - }, + builder: (context) => AdvancedOptionsPage(), ), ); }, - leading: UserAvatar( - user: user, - constraints: BoxConstraints.tight( - Size.fromRadius(20), + leading: CircleAvatar( + child: Icon( + StreamIcons.settings, + color: Colors.black, ), + backgroundColor: + StreamChatTheme.of(context).secondaryColor, ), title: Text( - user.name, + 'Advanced Options', style: TextStyle(fontWeight: FontWeight.bold), ), - subtitle: Text('Stream test account'), + subtitle: Text('Custom settings'), trailing: Icon( StreamIcons.arrow_right, color: StreamChatTheme.of(context).accentColor, ), - ); - }), - ListTile( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AdvancedOptionsPage(), - ), - ); - }, - leading: CircleAvatar( - child: Icon( - StreamIcons.settings, - color: Colors.black, - ), - backgroundColor: StreamChatTheme.of(context).secondaryColor, ), - title: Text( - 'Advanced Options', - style: TextStyle(fontWeight: FontWeight.bold), - ), - subtitle: Text('Custom settings'), - trailing: Icon( - StreamIcons.arrow_right, - color: StreamChatTheme.of(context).accentColor, - ), - ), - ], + ][i]; + }, ), ), ], diff --git a/example/lib/main.dart b/example/lib/main.dart index 8218f203..5af7f134 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,6 +6,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'notifications_service.dart'; + void main() async { final client = Client( 's2dxdhpxd94g', diff --git a/example/lib/notifications_service.dart b/example/lib/notifications_service.dart new file mode 100644 index 00000000..c5b77c0e --- /dev/null +++ b/example/lib/notifications_service.dart @@ -0,0 +1,62 @@ +import 'dart:io'; + +import 'package:flutter_apns/flutter_apns.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart' + hide Message; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +void showLocalNotification(Message message, ChannelModel channel) async { + final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + final initializationSettingsAndroid = + AndroidInitializationSettings('launch_background'); + final initializationSettingsIOS = IOSInitializationSettings(); + final initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS, + ); + await flutterLocalNotificationsPlugin.initialize(initializationSettings); + await flutterLocalNotificationsPlugin.show( + message.id.hashCode, + '${message.user.name} @ ${channel.name}', + message.text, + NotificationDetails( + android: AndroidNotificationDetails( + 'message channel', + 'Message channel', + 'Channel used for showing messages', + priority: Priority.high, + importance: Importance.high, + ), + iOS: IOSNotificationDetails(), + ), + ); +} + +Future backgroundHandler(Map notification) async { + final messageId = notification['data']['message_id']; + + final notificationData = + await NotificationService.getAndStoreMessage(messageId); + + showLocalNotification( + notificationData.message, + notificationData.channel, + ); +} + +void initNotifications(Client client) { + final connector = createPushConnector(); + connector.configure( + onBackgroundMessage: backgroundHandler, + ); + + connector.requestNotificationPermissions(); + connector.token.addListener(() { + if (connector.token.value != null) { + client.addDevice( + connector.token.value, + Platform.isAndroid ? 'firebase' : 'apn', + ); + } + }); +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e4604f44..8917cc07 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,7 +1,7 @@ name: example description: A new Flutter project. -version: 1.0.43+45 +version: 1.0.44+46 environment: sdk: ">=2.2.2 <3.0.0" From 89fdb47e0eaa2f9c9175bb18188c5fca516452e8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 10:07:28 +0100 Subject: [PATCH 09/34] show unread indicator only for members --- example/lib/multiple_conversation.dart | 6 ++--- example/pubspec.yaml | 2 +- lib/src/channel_preview.dart | 10 +++++---- lib/src/message_list_view.dart | 31 +++++++++++++++++--------- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/example/lib/multiple_conversation.dart b/example/lib/multiple_conversation.dart index 647ae7dd..b5fdd5af 100644 --- a/example/lib/multiple_conversation.dart +++ b/example/lib/multiple_conversation.dart @@ -56,9 +56,9 @@ class ChannelListPage extends StatelessWidget { body: ChannelsBloc( child: ChannelListView( filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } + // 'members': { + // '\$in': [StreamChat.of(context).user.id], + // } }, sort: [SortOption('last_message_at')], pagination: PaginationParams( diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 1e527964..0cba8d30 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,7 +1,7 @@ name: example description: A new Flutter project. -version: 1.0.42+44 +version: 1.0.43+45 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index 8ed15657..cd1a0910 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -70,9 +70,11 @@ class ChannelPreview extends StatelessWidget { StreamChatTheme.of(context).channelPreviewTheme.title, ), ), - UnreadIndicator( - channel: channel, - ), + if (channel.state.members.contains( + (Member e) => e.userId == channel.client.state.user.id)) + UnreadIndicator( + channel: channel, + ), ], ), subtitle: Row( @@ -92,7 +94,7 @@ class ChannelPreview extends StatelessWidget { .isAfter(channel .state.lastMessage.createdAt)) .length == - channel.memberCount - 1, + (channel.memberCount ?? 0) - 1, ), ); } diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 946ad66f..2a60b75b 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -289,7 +289,9 @@ class _MessageListViewState extends State { return messageWidget; }, ), - if (widget.showScrollToBottom) + if (streamChannel.channel.state.members.contains((Member e) => + e.userId == streamChannel.channel.client.state.user.id) && + widget.showScrollToBottom) StreamBuilder( stream: streamChannel.channel.state.unreadCountStream, builder: (context, snapshot) { @@ -374,8 +376,16 @@ class _MessageListViewState extends State { left: 10, top: -10, child: CircleAvatar( - radius: 20, - child: Text(unreadCount.toString()), + child: Padding( + padding: const EdgeInsets.all(3.0), + child: Text( + unreadCount.toString(), + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.bold, + ), + ), + ), ), ), ], @@ -555,13 +565,14 @@ class _MessageListViewState extends State { final channel = StreamChannel.of(context).channel; final readList = channel.state?.read - ?.where((element) => element.user.id != userId) - ?.where((read) => - (read.lastRead.isAfter(message.createdAt) || - read.lastRead.isAtSameMomentAs(message.createdAt)) && - (index == 0 || - read.lastRead.isBefore(messages[index - 1].createdAt))) - ?.toList(); + ?.where((element) => element.user.id != userId) + ?.where((read) => + (read.lastRead.isAfter(message.createdAt) || + read.lastRead.isAtSameMomentAs(message.createdAt)) && + (index == 0 || + read.lastRead.isBefore(messages[index - 1].createdAt))) + ?.toList() ?? + []; final allRead = readList.length >= (channel.memberCount ?? 0) - 1; From 755dd511edfc77c0640a5abef837e38c3bceedc6 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 10 Nov 2020 19:32:46 +0530 Subject: [PATCH 10/34] feat: Added url attachments --- lib/src/message_widget.dart | 194 ++++++++++++++++++++++++++++++------ 1 file changed, 165 insertions(+), 29 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 476217f3..0affe079 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'dart:ui'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:emojis/emoji.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -343,6 +344,83 @@ class _MessageWidgetState extends State { ); } + Widget _buildUrlAttachment() { + var urlAttachment = widget.message.attachments + .firstWhere((element) => element.ogScrapeUrl != null); + + var host = Uri.parse(urlAttachment.ogScrapeUrl).host; + var splitList = host.split('.'); + var hostName = splitList.length == 3 ? splitList[1] : splitList[0]; + var hostDisplayName = + _getWebsiteName(hostName.toLowerCase()) ?? hostName.capitalize(); + + return Column( + children: [ + SizedBox( + height: 16.0, + ), + Container( + margin: EdgeInsets.symmetric(horizontal: 8.0), + child: Stack( + clipBehavior: Clip.antiAlias, + children: [ + CachedNetworkImage(imageUrl: urlAttachment.imageUrl), + Positioned( + left: 0.0, + bottom: 0.0, + child: Container( + child: Padding( + padding: + const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0), + child: Text( + hostDisplayName, + style: TextStyle( + fontWeight: FontWeight.w700, + color: Color(0xFF006CFF), + ), + ), + ), + decoration: BoxDecoration( + borderRadius: + BorderRadius.only(topRight: Radius.circular(16.0)), + color: Color(0xFFE9F2FF), + ), + ), + ), + ], + ), + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(8.0)), + ), + Padding( + padding: widget.textPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (urlAttachment.title != null) + Text( + urlAttachment.title, + maxLines: 1, + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 12.0, + ), + ), + if (urlAttachment.text != null) + Text( + urlAttachment.text, + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 12.0, + ), + ), + ], + ), + ), + ], + ); + } + Padding _buildBottomRow(double leftPadding) { return Padding( padding: EdgeInsets.only( @@ -525,7 +603,8 @@ class _MessageWidgetState extends State { List _parseAttachments(BuildContext context) { final images = widget.message.attachments - ?.where((element) => element.type == 'image') + ?.where((element) => + element.type == 'image' && element.ogScrapeUrl == null) ?.toList() ?? []; @@ -548,7 +627,9 @@ class _MessageWidgetState extends State { ]; } - return widget.message.attachments?.map((attachment) { + return widget.message.attachments + ?.where((element) => element.ogScrapeUrl == null) + ?.map((attachment) { final attachmentBuilder = widget.attachmentBuilders[attachment.type]; if (attachmentBuilder == null) { @@ -757,28 +838,35 @@ class _MessageWidgetState extends State { Widget child = Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, - child: Padding( - padding: widget.textPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - getFailedMessageWidget(context), - widget.textBuilder != null - ? widget.textBuilder(context, widget.message) - : MessageText( - onLinkTap: widget.onLinkTap, - message: widget.message, - onMentionTap: widget.onMentionTap, - messageTheme: isOnlyEmoji - ? widget.messageTheme.copyWith( - messageText: - widget.messageTheme.messageText.copyWith( - fontSize: 40, - )) - : widget.messageTheme, - ), - ], - ), + child: Column( + children: [ + Padding( + padding: widget.textPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + getFailedMessageWidget(context), + widget.textBuilder != null + ? widget.textBuilder(context, widget.message) + : MessageText( + onLinkTap: widget.onLinkTap, + message: widget.message, + onMentionTap: widget.onMentionTap, + messageTheme: isOnlyEmoji + ? widget.messageTheme.copyWith( + messageText: + widget.messageTheme.messageText.copyWith( + fontSize: 40, + )) + : widget.messageTheme, + ), + ], + ), + ), + if (widget.message.attachments + .any((element) => element.ogScrapeUrl != null)) + _buildUrlAttachment(), + ], ), ); @@ -796,11 +884,18 @@ class _MessageWidgetState extends State { } Color _getBackgroundColor() { - return (widget.message.status == MessageSendingStatus.FAILED || - widget.message.status == MessageSendingStatus.FAILED_UPDATE || - widget.message.status == MessageSendingStatus.FAILED_DELETE) - ? Color(0xffd0021B).withOpacity(.1) - : widget.messageTheme.messageBackgroundColor; + if ((widget.message.status == MessageSendingStatus.FAILED || + widget.message.status == MessageSendingStatus.FAILED_UPDATE || + widget.message.status == MessageSendingStatus.FAILED_DELETE)) { + return Color(0xffd0021B).withOpacity(.1); + } + + if (widget.message.attachments + .any((element) => element.ogScrapeUrl != null)) { + return Color(0xFFE9F2FF); + } + + return widget.messageTheme.messageBackgroundColor; } void retryMessage(BuildContext context) { @@ -825,4 +920,45 @@ class _MessageWidgetState extends State { return; } } + + String _getWebsiteName(String hostName) { + switch (hostName) { + case 'reddit': + return 'Reddit'; + case 'youtube': + return 'Youtube'; + case 'wikipedia': + return 'Wikipedia'; + case 'twitter': + return 'Twitter'; + case 'facebook': + return 'Facebook'; + case 'amazon': + return 'Amazon'; + case 'yelp': + return 'Yelp'; + case 'imdb': + return 'IMDB'; + case 'pinterest': + return 'Pinterest'; + case 'tripadvisor': + return 'TripAdvisor'; + case 'instagram': + return 'Instagram'; + case 'walmart': + return 'Walmart'; + case 'craigslist': + return 'Craigslist'; + case 'ebay': + return 'eBay'; + case 'linkedin': + return 'LinkedIn'; + case 'google': + return 'Google'; + case 'apple': + return 'Apple'; + default: + return null; + } + } } From 477673aa28eb65ee51bf221b71513cdfb62bc888 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 10 Nov 2020 19:38:05 +0530 Subject: [PATCH 11/34] fix: Fix case when image is null --- lib/src/message_widget.dart | 58 +++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 0affe079..4291e00e 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -356,42 +356,44 @@ class _MessageWidgetState extends State { return Column( children: [ + if (urlAttachment.imageUrl != null) SizedBox( height: 16.0, ), - Container( - margin: EdgeInsets.symmetric(horizontal: 8.0), - child: Stack( - clipBehavior: Clip.antiAlias, - children: [ - CachedNetworkImage(imageUrl: urlAttachment.imageUrl), - Positioned( - left: 0.0, - bottom: 0.0, - child: Container( - child: Padding( - padding: - const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0), - child: Text( - hostDisplayName, - style: TextStyle( - fontWeight: FontWeight.w700, - color: Color(0xFF006CFF), + if (urlAttachment.imageUrl != null) + Container( + margin: EdgeInsets.symmetric(horizontal: 8.0), + child: Stack( + clipBehavior: Clip.antiAlias, + children: [ + CachedNetworkImage(imageUrl: urlAttachment.imageUrl), + Positioned( + left: 0.0, + bottom: 0.0, + child: Container( + child: Padding( + padding: const EdgeInsets.only( + top: 8.0, left: 8.0, right: 8.0), + child: Text( + hostDisplayName, + style: TextStyle( + fontWeight: FontWeight.w700, + color: Color(0xFF006CFF), + ), ), ), - ), - decoration: BoxDecoration( - borderRadius: - BorderRadius.only(topRight: Radius.circular(16.0)), - color: Color(0xFFE9F2FF), + decoration: BoxDecoration( + borderRadius: + BorderRadius.only(topRight: Radius.circular(16.0)), + color: Color(0xFFE9F2FF), + ), ), ), - ), - ], + ], + ), + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(8.0)), ), - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration(borderRadius: BorderRadius.circular(8.0)), - ), Padding( padding: widget.textPadding, child: Column( From b1401a5f0ced55840eb6e7a76cce196872b096b3 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 16:05:51 +0100 Subject: [PATCH 12/34] create a new app and client for advanced options --- example/lib/advanced_options_page.dart | 393 +++++++++++++++---------- example/lib/choose_user_page.dart | 19 ++ example/pubspec.yaml | 2 +- 3 files changed, 253 insertions(+), 161 deletions(-) diff --git a/example/lib/advanced_options_page.dart b/example/lib/advanced_options_page.dart index b6062c30..4fb801c9 100644 --- a/example/lib/advanced_options_page.dart +++ b/example/lib/advanced_options_page.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -5,13 +7,24 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'main.dart'; import 'notifications_service.dart'; -class AdvancedOptionsPage extends StatelessWidget { +class AdvancedOptionsPage extends StatefulWidget { + @override + _AdvancedOptionsPageState createState() => _AdvancedOptionsPageState(); +} + +class _AdvancedOptionsPageState extends State { final _formKey = GlobalKey(); + final TextEditingController _apiKeyController = TextEditingController(); + final TextEditingController _userIdController = TextEditingController(); + final TextEditingController _userTokenController = TextEditingController(); + final TextEditingController _usernameController = TextEditingController(); + bool loading = false; + @override Widget build(BuildContext context) { return Scaffold( @@ -36,169 +49,229 @@ class AdvancedOptionsPage extends StatelessWidget { }, ), ), - body: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, - ), - child: Form( - key: _formKey, - onChanged: () {}, - child: Column( - children: [ - TextFormField( - controller: _apiKeyController, - validator: (value) { - if (value.isEmpty) { - return 'Please enter the Chat API Key'; - } - return null; - }, - decoration: InputDecoration( - labelStyle: TextStyle( - fontSize: 14, - color: Colors.black.withOpacity(.5), + body: Builder( + builder: (context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + child: Form( + key: _formKey, + child: Column( + children: [ + TextFormField( + controller: _apiKeyController, + validator: (value) { + if (value.isEmpty) { + return 'Please enter the Chat API Key'; + } + return null; + }, + decoration: InputDecoration( + labelStyle: TextStyle( + fontSize: 14, + color: Colors.black.withOpacity(.5), + ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: Color(0xffF5F5F5), + filled: true, + labelText: 'Chat API Key', + ), + textInputAction: TextInputAction.next, ), - border: UnderlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - fillColor: Color(0xffF5F5F5), - filled: true, - labelText: 'Chat API Key', - ), - textInputAction: TextInputAction.next, - ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: TextFormField( - controller: _userIdController, - validator: (value) { - if (value.isEmpty) { - return 'Please enter the User ID'; - } - return null; - }, - textInputAction: TextInputAction.next, - decoration: InputDecoration( - labelStyle: TextStyle( - fontSize: 14, - color: Colors.black.withOpacity(.5), - ), - border: UnderlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - fillColor: Color(0xffF5F5F5), - filled: true, - labelText: 'User ID', - ), - ), - ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: TextFormField( - controller: _userTokenController, - validator: (value) { - if (value.isEmpty) { - return 'Please enter the user token'; - } - return null; - }, - textInputAction: TextInputAction.next, - decoration: InputDecoration( - labelStyle: TextStyle( - fontSize: 14, - color: Colors.black.withOpacity(.5), - ), - border: UnderlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - fillColor: Color(0xffF5F5F5), - filled: true, - labelText: 'User Token', - ), - ), - ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: TextFormField( - controller: _usernameController, - textInputAction: TextInputAction.done, - decoration: InputDecoration( - labelStyle: TextStyle( - fontSize: 14, - color: Colors.black.withOpacity(.5), - ), - border: UnderlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - fillColor: Color(0xffF5F5F5), - filled: true, - labelText: 'Username (optional)', - ), - ), - ), - Expanded( - child: Align( - alignment: Alignment.bottomCenter, - child: FlatButton( - color: StreamChatTheme.of(context).accentColor, - minWidth: double.infinity, - height: 48, - child: Text( - 'Login', - style: TextStyle( - color: Colors.white, - fontSize: 16, + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: TextFormField( + controller: _userIdController, + validator: (value) { + if (value.isEmpty) { + return 'Please enter the User ID'; + } + return null; + }, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + labelStyle: TextStyle( + fontSize: 14, + color: Colors.black.withOpacity(.5), + ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: Color(0xffF5F5F5), + filled: true, + labelText: 'User ID', ), ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(26), - ), - onPressed: () async { - if (_formKey.currentState.validate()) { - final apiKey = _apiKeyController.text; - final userId = _userIdController.text; - final userToken = _userTokenController.text; - final username = _usernameController.text; - - final client = StreamChat.of(context).client; - client.apiKey = apiKey; - - await client.setUser( - User(id: userId, extraData: { - 'name': username, - }), - userToken, - ); - - if (!kIsWeb) { - initNotifications(client); - } - - Navigator.pop(context); - await Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) { - return StreamChat( - client: client, - child: ChannelListPage(), - ); - }, - ), - ); - } - }, ), - ), - ) - ], - ), - ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: TextFormField( + controller: _userTokenController, + validator: (value) { + if (value.isEmpty) { + return 'Please enter the user token'; + } + return null; + }, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + labelStyle: TextStyle( + fontSize: 14, + color: Colors.black.withOpacity(.5), + ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: Color(0xffF5F5F5), + filled: true, + labelText: 'User Token', + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: TextFormField( + controller: _usernameController, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + labelStyle: TextStyle( + fontSize: 14, + color: Colors.black.withOpacity(.5), + ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: Color(0xffF5F5F5), + filled: true, + labelText: 'Username (optional)', + ), + ), + ), + Expanded( + child: Align( + alignment: Alignment.bottomCenter, + child: FlatButton( + color: StreamChatTheme.of(context).accentColor, + minWidth: double.infinity, + height: 48, + child: Text( + 'Login', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26), + ), + onPressed: () async { + if (loading) { + return; + } + loading = true; + if (_formKey.currentState.validate()) { + final apiKey = _apiKeyController.text; + final userId = _userIdController.text; + final userToken = _userTokenController.text; + final username = _usernameController.text; + + showDialog( + barrierDismissible: false, + context: context, + builder: (context) => Center( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + ), + height: 100, + width: 100, + child: Center( + child: CircularProgressIndicator(), + ), + ), + ), + ); + + print('CREATE CLIENT'); + final client = Client( + apiKey, + logLevel: Level.INFO, + showLocalNotification: + (!kIsWeb && Platform.isAndroid) + ? showLocalNotification + : null, + persistenceEnabled: true, + ); + + try { + await client.setUser( + User(id: userId, extraData: { + 'name': username, + }), + userToken, + ); + } catch (e) { + var errorText = 'Error connecting, retry'; + if (e is Map) { + errorText = e['message'] ?? errorText; + } + Navigator.pop(context); + Scaffold.of(context).showSnackBar( + SnackBar( + content: Text(errorText), + ), + ); + loading = false; + await client.disconnect(); + return; + } + + if (!kIsWeb) { + initNotifications(client); + } + + Navigator.pop(context); + Navigator.pop(context); + await Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) { + return MaterialApp( + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + //TODO change to system once dark theme is implemented + themeMode: ThemeMode.light, + builder: (context, widget) { + return StreamChat( + child: widget, + client: client, + ); + }, + home: ChannelListPage(), + ); + }, + ), + ); + loading = false; + } + }, + ), + ), + ) + ], + ), + ), + ); + }, ), ); } diff --git a/example/lib/choose_user_page.dart b/example/lib/choose_user_page.dart index ef710e53..35fe2d0d 100644 --- a/example/lib/choose_user_page.dart +++ b/example/lib/choose_user_page.dart @@ -98,6 +98,24 @@ class ChooseUserPage extends StatelessWidget { final user = entry.value; return ListTile( onTap: () async { + showDialog( + barrierDismissible: false, + context: context, + builder: (context) => Center( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + ), + height: 100, + width: 100, + child: Center( + child: CircularProgressIndicator(), + ), + ), + ), + ); + final client = StreamChat.of(context).client; await client.setUser( @@ -111,6 +129,7 @@ class ChooseUserPage extends StatelessWidget { initNotifications(client); } + Navigator.pop(context); await Navigator.pushReplacement( context, MaterialPageRoute( diff --git a/example/pubspec.yaml b/example/pubspec.yaml index b94a312a..4a950698 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,7 +1,7 @@ name: example description: A new Flutter project. -version: 1.0.45+47 +version: 1.0.46+48 environment: sdk: ">=2.2.2 <3.0.0" From 1ef232c2a98878420b48f89cae07dad0989111ce Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 16:14:58 +0100 Subject: [PATCH 13/34] fix dep --- pubspec.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index e5c57193..11a79617 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,8 +26,7 @@ dependencies: file_picker: ^2.0.8+1 image_picker: ^0.6.7+2 flutter_keyboard_visibility: ^3.2.1 - stream_chat: - path: ../stream_chat_dart + stream_chat: ^0.2.10+2 emojis: ^0.9.3 mime: ^0.9.6+3 visibility_detector: ^0.1.5 From 3cfe5efd0956834c18f00cd19f1b9f59bda507ea Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 16:46:25 +0100 Subject: [PATCH 14/34] fix tests --- lib/src/channel_preview.dart | 19 ++++++++++++++----- test/src/channel_preview_test.dart | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index cd1a0910..c23d7732 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -70,11 +70,20 @@ class ChannelPreview extends StatelessWidget { StreamChatTheme.of(context).channelPreviewTheme.title, ), ), - if (channel.state.members.contains( - (Member e) => e.userId == channel.client.state.user.id)) - UnreadIndicator( - channel: channel, - ), + StreamBuilder>( + stream: channel.state.membersStream, + initialData: channel.state.members, + builder: (context, snapshot) { + if (!snapshot.hasData || + snapshot.data.isEmpty || + !snapshot.data.any((Member e) => + e.user.id == channel.client.state.user.id)) { + return SizedBox(); + } + return UnreadIndicator( + channel: channel, + ); + }), ], ), subtitle: Row( diff --git a/test/src/channel_preview_test.dart b/test/src/channel_preview_test.dart index 94c45ac2..578d4b28 100644 --- a/test/src/channel_preview_test.dart +++ b/test/src/channel_preview_test.dart @@ -19,6 +19,7 @@ void main() { when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(channel.lastMessageAt).thenReturn(lastMessageAt); when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); when(channel.isMuted).thenReturn(false); when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(channel.extraDataStream).thenAnswer((i) => Stream.value({ @@ -28,7 +29,19 @@ void main() { 'name': 'test name', }); when(channelState.unreadCount).thenReturn(1); - when(channelState.members).thenReturn([]); + when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); when(channelState.lastMessage).thenReturn(Message( text: 'hello', user: User(id: 'other-user'), From f87fa8ae8fd361c71e6e0cdfa468de8e19f1c16a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 17:12:48 +0100 Subject: [PATCH 15/34] add inkwell --- lib/src/message_widget.dart | 135 +++++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 57 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 4291e00e..1f3a12dd 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -16,6 +16,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'image_group.dart'; import 'message_text.dart'; +import 'utils.dart'; typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment); @@ -354,69 +355,89 @@ class _MessageWidgetState extends State { var hostDisplayName = _getWebsiteName(hostName.toLowerCase()) ?? hostName.capitalize(); - return Column( + return Stack( children: [ - if (urlAttachment.imageUrl != null) - SizedBox( - height: 16.0, - ), - if (urlAttachment.imageUrl != null) - Container( - margin: EdgeInsets.symmetric(horizontal: 8.0), - child: Stack( - clipBehavior: Clip.antiAlias, - children: [ - CachedNetworkImage(imageUrl: urlAttachment.imageUrl), - Positioned( - left: 0.0, - bottom: 0.0, - child: Container( - child: Padding( - padding: const EdgeInsets.only( - top: 8.0, left: 8.0, right: 8.0), - child: Text( - hostDisplayName, - style: TextStyle( - fontWeight: FontWeight.w700, - color: Color(0xFF006CFF), + Column( + children: [ + if (urlAttachment.imageUrl != null) + SizedBox( + height: 16.0, + ), + if (urlAttachment.imageUrl != null) + Container( + margin: EdgeInsets.symmetric(horizontal: 8.0), + child: Stack( + clipBehavior: Clip.antiAlias, + children: [ + CachedNetworkImage(imageUrl: urlAttachment.imageUrl), + Positioned( + left: 0.0, + bottom: 0.0, + child: Container( + child: Padding( + padding: const EdgeInsets.only( + top: 8.0, + left: 8.0, + right: 8.0, + ), + child: Text( + hostDisplayName, + style: TextStyle( + fontWeight: FontWeight.w700, + color: Color(0xFF006CFF), + ), + ), + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topRight: Radius.circular(16.0), + ), + color: Color(0xFFE9F2FF), ), ), ), - decoration: BoxDecoration( - borderRadius: - BorderRadius.only(topRight: Radius.circular(16.0)), - color: Color(0xFFE9F2FF), + ], + ), + clipBehavior: Clip.antiAlias, + decoration: + BoxDecoration(borderRadius: BorderRadius.circular(8.0)), + ), + Padding( + padding: widget.textPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (urlAttachment.title != null) + Text( + urlAttachment.title, + maxLines: 1, + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 12.0, + ), ), - ), - ), - ], + if (urlAttachment.text != null) + Text( + urlAttachment.text, + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 12.0, + ), + ), + ], + ), + ), + ], + ), + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => launchURL( + context, + urlAttachment.ogScrapeUrl, + ), ), - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration(borderRadius: BorderRadius.circular(8.0)), - ), - Padding( - padding: widget.textPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (urlAttachment.title != null) - Text( - urlAttachment.title, - maxLines: 1, - style: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 12.0, - ), - ), - if (urlAttachment.text != null) - Text( - urlAttachment.text, - style: TextStyle( - fontWeight: FontWeight.w400, - fontSize: 12.0, - ), - ), - ], ), ), ], From 24c44520e610eef8af2f2b5c985368248639307d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 17:16:40 +0100 Subject: [PATCH 16/34] fix alignment --- lib/src/message_widget.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 1f3a12dd..d1be132d 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -862,6 +862,7 @@ class _MessageWidgetState extends State { transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: widget.textPadding, From 2d125ea83692dbb65a80047c748cacb2494a8c09 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 17:25:25 +0100 Subject: [PATCH 17/34] use hostname if not null --- lib/src/message_widget.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index d1be132d..55d08424 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -352,8 +352,9 @@ class _MessageWidgetState extends State { var host = Uri.parse(urlAttachment.ogScrapeUrl).host; var splitList = host.split('.'); var hostName = splitList.length == 3 ? splitList[1] : splitList[0]; - var hostDisplayName = - _getWebsiteName(hostName.toLowerCase()) ?? hostName.capitalize(); + var hostDisplayName = urlAttachment.authorName?.capitalize() ?? + _getWebsiteName(hostName.toLowerCase()) ?? + hostName.capitalize(); return Stack( children: [ From a91f1c500044e0c07bb225fa3247f0f47458fb47 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 17:28:10 +0100 Subject: [PATCH 18/34] fix image alignent --- lib/src/message_widget.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 55d08424..6f81f149 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -359,6 +359,7 @@ class _MessageWidgetState extends State { return Stack( children: [ Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (urlAttachment.imageUrl != null) SizedBox( @@ -370,7 +371,11 @@ class _MessageWidgetState extends State { child: Stack( clipBehavior: Clip.antiAlias, children: [ - CachedNetworkImage(imageUrl: urlAttachment.imageUrl), + Center( + child: CachedNetworkImage( + imageUrl: urlAttachment.imageUrl, + ), + ), Positioned( left: 0.0, bottom: 0.0, From 0306fa4124de75ca4712f0d9df1ca8bacccc177d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 17:58:24 +0100 Subject: [PATCH 19/34] upgrade dependencies --- pubspec.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 70e4ed1e..9394aa96 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,13 +16,13 @@ dependencies: jiffy: ^3.0.1 flutter_portal: ^0.3.0 cached_network_image: ^2.2.0+1 - flutter_markdown: ^0.4.2 + flutter_markdown: ^0.5.0 url_launcher: ^5.4.11 - video_player: ^0.10.11+1 - chewie: ^0.9.10 - file_picker: ^2.0.0 + video_player: ^1.0.0 + chewie: ^0.10.4 + file_picker: ^2.0.12 image_picker: ^0.6.7+2 - flutter_keyboard_visibility: ^3.2.1 + flutter_keyboard_visibility: ^3.3.0 stream_chat: ^0.2.10 mime: ^0.9.6+3 visibility_detector: ^0.1.5 From f63d65a1bb23f2be67b4f8ab74430d4025dc5ed3 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 17:58:45 +0100 Subject: [PATCH 20/34] check if user.extradata["image"] is null --- lib/src/message_text.dart | 6 +++++- lib/src/user_avatar.dart | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/src/message_text.dart b/lib/src/message_text.dart index 2ee6a6f8..a043c53a 100644 --- a/lib/src/message_text.dart +++ b/lib/src/message_text.dart @@ -24,7 +24,11 @@ class MessageText extends StatelessWidget { final text = _replaceMentions(message.text); return MarkdownBody( data: text, - onTapLink: (link) { + onTapLink: ( + String link, + String href, + String title, + ) { if (link.startsWith('@')) { final mentionedUser = message.mentionedUsers.firstWhere( (u) => '@${u.name.replaceAll(' ', '')}' == link, diff --git a/lib/src/user_avatar.dart b/lib/src/user_avatar.dart index 8ea9a525..3e5a599f 100644 --- a/lib/src/user_avatar.dart +++ b/lib/src/user_avatar.dart @@ -38,7 +38,9 @@ class UserAvatar extends StatelessWidget { decoration: BoxDecoration( color: StreamChatTheme.of(context).accentColor, ), - child: user.extraData?.containsKey('image') ?? false + child: (user.extraData?.containsKey('image') == true && + user.extraData['image'] != null && + user.extraData['image'] != '') ? CachedNetworkImage( imageUrl: user.extraData['image'], errorWidget: (_, __, ___) { From 4383e74d491ce0fa35868875ed9dfa73fab69fe7 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 17:59:51 +0100 Subject: [PATCH 21/34] bump version --- CHANGELOG.md | 5 +++++ pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e924ddc7..03db8a6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.2.12 + +- Upgrade dependencies +- Check if user.extraData['image'] is not null before using it + ## 0.2.11+1 - Fix error with channel query while handling background notifications diff --git a/pubspec.yaml b/pubspec.yaml index 9394aa96..7733d25b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.11+1 +version: 0.2.12 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues From d38bb92737d58d0b3d85820179b2830d7f2cb318 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 18:07:33 +0100 Subject: [PATCH 22/34] extract condition --- lib/src/user_avatar.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/user_avatar.dart b/lib/src/user_avatar.dart index 3e5a599f..9b833cfe 100644 --- a/lib/src/user_avatar.dart +++ b/lib/src/user_avatar.dart @@ -18,6 +18,9 @@ class UserAvatar extends StatelessWidget { @override Widget build(BuildContext context) { + final hasImage = user.extraData?.containsKey('image') == true && + user.extraData['image'] != null && + user.extraData['image'] != ''; return GestureDetector( onTap: () { if (onTap != null) { @@ -38,9 +41,7 @@ class UserAvatar extends StatelessWidget { decoration: BoxDecoration( color: StreamChatTheme.of(context).accentColor, ), - child: (user.extraData?.containsKey('image') == true && - user.extraData['image'] != null && - user.extraData['image'] != '') + child: hasImage ? CachedNetworkImage( imageUrl: user.extraData['image'], errorWidget: (_, __, ___) { From 10017099976a815be35f855eca58379d01bfda63 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 10:33:04 +0100 Subject: [PATCH 23/34] update llc dependency --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 9dd2fd95..a3cb6d18 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: file_picker: ^2.0.8+1 image_picker: ^0.6.7+2 flutter_keyboard_visibility: ^3.2.1 - stream_chat: ^0.2.10+1 + stream_chat: ^0.2.11 emojis: ^0.9.3 mime: ^0.9.6+3 visibility_detector: ^0.1.5 From 269ad0b99bed3a9817b81db7590bb6fa80aaa62f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 12:03:39 +0100 Subject: [PATCH 24/34] add drawer --- example/lib/choose_user_page.dart | 22 +++++++ example/lib/main.dart | 103 +++++++++++++++++++++++++++++- example/pubspec.yaml | 3 +- lib/src/channel_list_view.dart | 6 +- 4 files changed, 129 insertions(+), 5 deletions(-) diff --git a/example/lib/choose_user_page.dart b/example/lib/choose_user_page.dart index 35fe2d0d..beea6b1b 100644 --- a/example/lib/choose_user_page.dart +++ b/example/lib/choose_user_page.dart @@ -2,11 +2,17 @@ import 'package:example/advanced_options_page.dart'; import 'package:example/main.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'notifications_service.dart'; +const kStreamApiKey = 'STREAM_API_KEY'; +const kStreamUserId = 'STREAM_USER_ID'; +const kStreamToken = 'STREAM_TOKEN'; +const kDefaultStreamApiKey = 's2dxdhpxd94g'; + class ChooseUserPage extends StatelessWidget { @override Widget build(BuildContext context) { @@ -116,6 +122,7 @@ class ChooseUserPage extends StatelessWidget { ), ); + final secureStorage = FlutterSecureStorage(); final client = StreamChat.of(context).client; await client.setUser( @@ -125,6 +132,21 @@ class ChooseUserPage extends StatelessWidget { token, ); + await Future.wait([ + secureStorage.write( + key: kStreamApiKey, + value: kDefaultStreamApiKey, + ), + secureStorage.write( + key: kStreamUserId, + value: user.id, + ), + secureStorage.write( + key: kStreamToken, + value: token, + ), + ]); + if (!kIsWeb) { initNotifications(client); } diff --git a/example/lib/main.dart b/example/lib/main.dart index 5af7f134..1da02072 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,19 +4,34 @@ import 'package:example/choose_user_page.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'notifications_service.dart'; void main() async { + WidgetsFlutterBinding.ensureInitialized(); + final secureStorage = FlutterSecureStorage(); + + final apiKey = await secureStorage.read(key: kStreamApiKey); + final userId = await secureStorage.read(key: kStreamUserId); + final client = Client( - 's2dxdhpxd94g', + apiKey ?? kDefaultStreamApiKey, logLevel: Level.INFO, showLocalNotification: (!kIsWeb && Platform.isAndroid) ? showLocalNotification : null, persistenceEnabled: true, ); + if (userId != null) { + final token = await secureStorage.read(key: kStreamToken); + await client.setUser( + User(id: userId), + token, + ); + } + runApp(MyApp(client)); } @@ -38,7 +53,7 @@ class MyApp extends StatelessWidget { client: client, ); }, - home: ChooseUserPage(), + home: client.state.user == null ? ChooseUserPage() : ChannelListPage(), ); } } @@ -46,7 +61,89 @@ class MyApp extends StatelessWidget { class ChannelListPage extends StatelessWidget { @override Widget build(BuildContext context) { + final user = StreamChat.of(context).user; return Scaffold( + drawer: Drawer( + child: Padding( + padding: EdgeInsets.only( + top: MediaQuery.of(context).viewPadding.top + 8, + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + bottom: 20.0, + left: 8, + ), + child: Row( + children: [ + UserAvatar( + user: user, + showOnlineStatus: false, + constraints: BoxConstraints.tight(Size.fromRadius(20)), + ), + Padding( + padding: const EdgeInsets.only(left: 16.0), + child: Text( + user.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ListTile( + leading: Icon(StreamIcons.edit), + title: Text( + 'New direct message', + style: TextStyle( + fontSize: 14.5, + ), + ), + ), + ListTile( + leading: Icon(StreamIcons.group), + title: Text( + 'New group', + style: TextStyle( + fontSize: 14.5, + ), + ), + ), + Expanded( + child: Container( + alignment: Alignment.bottomCenter, + child: ListTile( + onTap: () async { + await StreamChat.of(context).client.disconnect(); + + final secureStorage = FlutterSecureStorage(); + await secureStorage.deleteAll(); + Navigator.pop(context); + await Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => ChooseUserPage(), + ), + ); + }, + leading: Icon(StreamIcons.user), + title: Text( + 'Sign out', + style: TextStyle( + fontSize: 14.5, + ), + ), + ), + ), + ), + ], + ), + ), + ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () { @@ -60,7 +157,7 @@ class ChannelListPage extends StatelessWidget { swipeToAction: true, filter: { 'members': { - '\$in': [StreamChat.of(context).user.id], + '\$in': [user.id], } }, options: { diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e36c3751..f8bfcfe9 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.47+49 +version: 1.0.48+50 environment: sdk: ">=2.2.2 <3.0.0" @@ -13,6 +13,7 @@ dependencies: flutter_apns: ^1.3.1 flutter_local_notifications: ^2.0.0 flutter_svg: ^0.19.1 + flutter_secure_storage: ^3.3.5 dev_dependencies: flutter_test: diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 3b698782..9f09235b 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'package:flutter/foundation.dart'; @@ -482,6 +483,8 @@ class _ChannelListViewState extends State } } + StreamSubscription _subscription; + @override void initState() { super.initState(); @@ -506,7 +509,7 @@ class _ChannelListViewState extends State final client = StreamChat.of(context).client; - client + _subscription = client .on( EventType.connectionRecovered, EventType.notificationAddedToChannel, @@ -544,6 +547,7 @@ class _ChannelListViewState extends State @override void dispose() { + _subscription.cancel(); WidgetsBinding.instance.removeObserver(this); super.dispose(); } From d24a74be78c203be5916ce94fb5d1bee245393c5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 12:16:05 +0100 Subject: [PATCH 25/34] use random pngs --- lib/src/stream_chat_theme.dart | 8 +++++--- lib/src/user_avatar.dart | 25 ++++++------------------- lib/src/utils.dart | 5 +++++ 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index 1dfd42b7..3b3725ed 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -1,3 +1,4 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/channel_header.dart'; @@ -5,6 +6,7 @@ import 'package:stream_chat_flutter/src/channel_preview.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/reaction_icon.dart'; import 'package:stream_chat_flutter/src/stream_icons.dart'; +import 'package:stream_chat_flutter/src/utils.dart'; /// Inherited widget providing the [StreamChatThemeData] to the widget tree class StreamChatTheme extends InheritedWidget { @@ -226,9 +228,9 @@ class StreamChatThemeData { defaultChannelImage: (context, channel) => SizedBox(), backgroundColor: isDark ? Colors.black : Colors.white, defaultUserImage: (context, user) => Center( - child: Text( - user.name?.substring(0, 1) ?? '', - style: TextStyle(color: Colors.white), + child: CachedNetworkImage( + imageUrl: getRandomPicUrl(user), + fit: BoxFit.cover, ), ), channelPreviewTheme: ChannelPreviewTheme( diff --git a/lib/src/user_avatar.dart b/lib/src/user_avatar.dart index 41c830e2..86bf7abd 100644 --- a/lib/src/user_avatar.dart +++ b/lib/src/user_avatar.dart @@ -27,6 +27,7 @@ class UserAvatar extends StatelessWidget { final hasImage = user.extraData?.containsKey('image') == true && user.extraData['image'] != null && user.extraData['image'] != ''; + final streamChatTheme = StreamChatTheme.of(context); return GestureDetector( onTap: onTap != null ? () { @@ -39,36 +40,22 @@ class UserAvatar extends StatelessWidget { children: [ ClipRRect( borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - .borderRadius, + streamChatTheme.ownMessageTheme.avatarTheme.borderRadius, child: Container( constraints: constraints ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - .constraints, + streamChatTheme.ownMessageTheme.avatarTheme.constraints, decoration: BoxDecoration( - color: StreamChatTheme.of(context).accentColor, + color: streamChatTheme.accentColor, ), child: hasImage ? CachedNetworkImage( imageUrl: user.extraData['image'], errorWidget: (_, __, ___) { - return Center( - child: Text( - user.name[0], - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - ); + return streamChatTheme.defaultUserImage(context, user); }, fit: BoxFit.cover, ) - : StreamChatTheme.of(context).defaultUserImage(context, user), + : streamChatTheme.defaultUserImage(context, user), ), ), if (showOnlineStatus && user.online == true) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index b20e5f59..20e3ce1f 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; import 'package:url_launcher/url_launcher.dart'; Future launchURL(BuildContext context, String url) async { @@ -42,3 +43,7 @@ Future showConfirmationDialog( }, ); } + +/// Get random png with initials +String getRandomPicUrl(User user) => + 'https://getstream.io/random_png/?id=${user.id}&name=${user.name}'; From 09dc280f6de9128446ffe9d3bc31e389bb9f0c04 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 12:21:17 +0100 Subject: [PATCH 26/34] use random png on default users --- example/lib/choose_user_page.dart | 8 +++++--- example/pubspec.yaml | 2 +- lib/stream_chat_flutter.dart | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/example/lib/choose_user_page.dart b/example/lib/choose_user_page.dart index beea6b1b..943a66a1 100644 --- a/example/lib/choose_user_page.dart +++ b/example/lib/choose_user_page.dart @@ -53,6 +53,10 @@ class ChooseUserPage extends StatelessWidget { }, ), }; + + users.updateAll( + (_, value) => value..extraData['image'] = getRandomPicUrl(value)); + return Scaffold( body: Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -126,9 +130,7 @@ class ChooseUserPage extends StatelessWidget { final client = StreamChat.of(context).client; await client.setUser( - User(id: user.id, extraData: { - 'name': user.name, - }), + user, token, ); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f8bfcfe9..87a45b01 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.48+50 +version: 1.0.49+51 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index 4c5760bf..e433e58e 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -28,4 +28,5 @@ export 'src/system_message.dart'; export 'src/thread_header.dart'; export 'src/typing_indicator.dart'; export 'src/user_avatar.dart'; +export 'src/utils.dart'; export 'src/video_attachment.dart'; From d4da4870a245b67abdb9f48322587431f37a6b6c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 12:52:37 +0100 Subject: [PATCH 27/34] add package version --- example/lib/choose_user_page.dart | 28 ++++++++++++++++++++++++++++ example/pubspec.yaml | 4 +++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/example/lib/choose_user_page.dart b/example/lib/choose_user_page.dart index 943a66a1..42952e99 100644 --- a/example/lib/choose_user_page.dart +++ b/example/lib/choose_user_page.dart @@ -2,9 +2,11 @@ import 'package:example/advanced_options_page.dart'; import 'package:example/main.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:yaml/yaml.dart'; import 'notifications_service.dart'; @@ -214,6 +216,32 @@ class ChooseUserPage extends StatelessWidget { }, ), ), + Container( + padding: const EdgeInsets.symmetric(vertical: 16), + alignment: Alignment.bottomCenter, + child: FutureBuilder( + future: rootBundle.loadString('pubspec.lock'), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return SizedBox(); + } + + final pubspec = snapshot.data; + final yaml = loadYaml(pubspec); + final streamChatDep = + yaml['packages']['stream_chat_flutter']['version']; + + print('streamChatDep: ${streamChatDep}'); + return Text( + 'Stream SDK v ${streamChatDep}', + style: TextStyle( + fontSize: 14.5, + color: Colors.black.withOpacity(.13), + ), + ); + }, + ), + ), ], ), ); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 87a45b01..8f9e849e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.49+51 +version: 1.0.50+52 environment: sdk: ">=2.2.2 <3.0.0" @@ -14,6 +14,7 @@ dependencies: flutter_local_notifications: ^2.0.0 flutter_svg: ^0.19.1 flutter_secure_storage: ^3.3.5 + yaml: ^2.2.1 dev_dependencies: flutter_test: @@ -26,6 +27,7 @@ dev_dependencies: flutter: assets: - assets/ + - pubspec.lock uses-material-design: true flutter_icons: From 49f792bceb273a15d467ba53a51b2e4f7bd38dc0 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 11 Nov 2020 17:23:51 +0530 Subject: [PATCH 28/34] rfac, fix: Separated URL attachment to widget, fixed clipping --- lib/src/message_widget.dart | 96 ++----------------------------- lib/src/url_attachment.dart | 110 ++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 91 deletions(-) create mode 100644 lib/src/url_attachment.dart diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 6f81f149..a41bc97f 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -12,6 +12,7 @@ import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/message_actions_modal.dart'; import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; +import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'image_group.dart'; @@ -356,97 +357,10 @@ class _MessageWidgetState extends State { _getWebsiteName(hostName.toLowerCase()) ?? hostName.capitalize(); - return Stack( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (urlAttachment.imageUrl != null) - SizedBox( - height: 16.0, - ), - if (urlAttachment.imageUrl != null) - Container( - margin: EdgeInsets.symmetric(horizontal: 8.0), - child: Stack( - clipBehavior: Clip.antiAlias, - children: [ - Center( - child: CachedNetworkImage( - imageUrl: urlAttachment.imageUrl, - ), - ), - Positioned( - left: 0.0, - bottom: 0.0, - child: Container( - child: Padding( - padding: const EdgeInsets.only( - top: 8.0, - left: 8.0, - right: 8.0, - ), - child: Text( - hostDisplayName, - style: TextStyle( - fontWeight: FontWeight.w700, - color: Color(0xFF006CFF), - ), - ), - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topRight: Radius.circular(16.0), - ), - color: Color(0xFFE9F2FF), - ), - ), - ), - ], - ), - clipBehavior: Clip.antiAlias, - decoration: - BoxDecoration(borderRadius: BorderRadius.circular(8.0)), - ), - Padding( - padding: widget.textPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (urlAttachment.title != null) - Text( - urlAttachment.title, - maxLines: 1, - style: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 12.0, - ), - ), - if (urlAttachment.text != null) - Text( - urlAttachment.text, - style: TextStyle( - fontWeight: FontWeight.w400, - fontSize: 12.0, - ), - ), - ], - ), - ), - ], - ), - Positioned.fill( - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () => launchURL( - context, - urlAttachment.ogScrapeUrl, - ), - ), - ), - ), - ], + return UrlAttachment( + urlAttachment: urlAttachment, + hostDisplayName: hostDisplayName, + textPadding: widget.textPadding, ); } diff --git a/lib/src/url_attachment.dart b/lib/src/url_attachment.dart new file mode 100644 index 00000000..4b296a6a --- /dev/null +++ b/lib/src/url_attachment.dart @@ -0,0 +1,110 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/utils.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class UrlAttachment extends StatelessWidget { + Attachment urlAttachment; + String hostDisplayName; + EdgeInsets textPadding; + + UrlAttachment({ + @required this.urlAttachment, + @required this.hostDisplayName, + @required this.textPadding, + }); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (urlAttachment.imageUrl != null) + SizedBox( + height: 16.0, + ), + if (urlAttachment.imageUrl != null) + Container( + margin: EdgeInsets.symmetric(horizontal: 8.0), + child: Stack( + children: [ + Center( + child: CachedNetworkImage( + imageUrl: urlAttachment.imageUrl, + ), + ), + Positioned( + left: 0.0, + bottom: -1, + child: Container( + child: Padding( + padding: const EdgeInsets.only( + top: 8.0, + left: 8.0, + right: 8.0, + ), + child: Text( + hostDisplayName, + style: TextStyle( + fontWeight: FontWeight.w700, + color: Color(0xFF006CFF), + ), + ), + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topRight: Radius.circular(16.0), + ), + color: Color(0xFFE9F2FF), + ), + ), + ), + ], + ), + decoration: + BoxDecoration(borderRadius: BorderRadius.circular(8.0)), + ), + Padding( + padding: textPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (urlAttachment.title != null) + Text( + urlAttachment.title, + maxLines: 1, + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 12.0, + ), + ), + if (urlAttachment.text != null) + Text( + urlAttachment.text, + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 12.0, + ), + ), + ], + ), + ), + ], + ), + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => launchURL( + context, + urlAttachment.ogScrapeUrl, + ), + ), + ), + ), + ], + ); + } +} From 80d04016437b76a6233f2175a808d8ce6c7fc369 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 12:56:00 +0100 Subject: [PATCH 29/34] add package version in advanced options --- example/lib/advanced_options_page.dart | 12 +++++--- example/lib/choose_user_page.dart | 30 ++------------------ example/lib/stream_version.dart | 39 ++++++++++++++++++++++++++ example/pubspec.yaml | 2 +- 4 files changed, 50 insertions(+), 33 deletions(-) create mode 100644 example/lib/stream_version.dart diff --git a/example/lib/advanced_options_page.dart b/example/lib/advanced_options_page.dart index 4fb801c9..f0373345 100644 --- a/example/lib/advanced_options_page.dart +++ b/example/lib/advanced_options_page.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:example/stream_version.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -52,9 +53,11 @@ class _AdvancedOptionsPageState extends State { body: Builder( builder: (context) { return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, + padding: const EdgeInsets.fromLTRB( + 16, + 16, + 16, + 0, ), child: Form( key: _formKey, @@ -266,7 +269,8 @@ class _AdvancedOptionsPageState extends State { }, ), ), - ) + ), + StreamVersion(), ], ), ), diff --git a/example/lib/choose_user_page.dart b/example/lib/choose_user_page.dart index 42952e99..b5296f8a 100644 --- a/example/lib/choose_user_page.dart +++ b/example/lib/choose_user_page.dart @@ -1,12 +1,11 @@ import 'package:example/advanced_options_page.dart'; import 'package:example/main.dart'; +import 'package:example/stream_version.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:yaml/yaml.dart'; import 'notifications_service.dart'; @@ -216,32 +215,7 @@ class ChooseUserPage extends StatelessWidget { }, ), ), - Container( - padding: const EdgeInsets.symmetric(vertical: 16), - alignment: Alignment.bottomCenter, - child: FutureBuilder( - future: rootBundle.loadString('pubspec.lock'), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return SizedBox(); - } - - final pubspec = snapshot.data; - final yaml = loadYaml(pubspec); - final streamChatDep = - yaml['packages']['stream_chat_flutter']['version']; - - print('streamChatDep: ${streamChatDep}'); - return Text( - 'Stream SDK v ${streamChatDep}', - style: TextStyle( - fontSize: 14.5, - color: Colors.black.withOpacity(.13), - ), - ); - }, - ), - ), + StreamVersion(), ], ), ); diff --git a/example/lib/stream_version.dart b/example/lib/stream_version.dart new file mode 100644 index 00000000..ee340f89 --- /dev/null +++ b/example/lib/stream_version.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:yaml/yaml.dart'; + +class StreamVersion extends StatelessWidget { + const StreamVersion({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 16), + alignment: Alignment.bottomCenter, + child: FutureBuilder( + future: rootBundle.loadString('pubspec.lock'), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return SizedBox(); + } + + final pubspec = snapshot.data; + final yaml = loadYaml(pubspec); + final streamChatDep = + yaml['packages']['stream_chat_flutter']['version']; + + print('streamChatDep: ${streamChatDep}'); + return Text( + 'Stream SDK v ${streamChatDep}', + style: TextStyle( + fontSize: 14.5, + color: Colors.black.withOpacity(.13), + ), + ); + }, + ), + ); + } +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 8f9e849e..855373bf 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.50+52 +version: 1.0.51+53 environment: sdk: ">=2.2.2 <3.0.0" From 36d7ad060a911f5e457a976c83eaba36b9c1c360 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 13:03:46 +0100 Subject: [PATCH 30/34] update sending indicator color --- lib/src/sending_indicator.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/sending_indicator.dart b/lib/src/sending_indicator.dart index e93984e2..ce8915c3 100644 --- a/lib/src/sending_indicator.dart +++ b/lib/src/sending_indicator.dart @@ -18,6 +18,7 @@ class SendingIndicator extends StatelessWidget { return Icon( Icons.done_all, size: 8, + color: StreamChatTheme.of(context).accentColor, ); } if (message.status == MessageSendingStatus.SENT || message.status == null) { From e07c865c676b2a7dfcb4689f97508c04b48e6666 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 14:16:00 +0100 Subject: [PATCH 31/34] fix url attachment image border radius --- lib/src/url_attachment.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/url_attachment.dart b/lib/src/url_attachment.dart index 4b296a6a..c6b0b3f7 100644 --- a/lib/src/url_attachment.dart +++ b/lib/src/url_attachment.dart @@ -27,6 +27,7 @@ class UrlAttachment extends StatelessWidget { ), if (urlAttachment.imageUrl != null) Container( + clipBehavior: Clip.antiAliasWithSaveLayer, margin: EdgeInsets.symmetric(horizontal: 8.0), child: Stack( children: [ @@ -63,8 +64,9 @@ class UrlAttachment extends StatelessWidget { ), ], ), - decoration: - BoxDecoration(borderRadius: BorderRadius.circular(8.0)), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.0), + ), ), Padding( padding: textPadding, From 7b151dfb613b7a6f640753c9a08e598e9b5664af Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 14:22:33 +0100 Subject: [PATCH 32/34] update podfile --- example/ios/Podfile.lock | 10 ++++++++-- example/ios/Runner.xcodeproj/project.pbxproj | 2 ++ example/pubspec.yaml | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index d20eaee6..f23764fa 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -38,7 +38,7 @@ PODS: - Firebase/Messaging (6.33.0): - Firebase/CoreOnly - FirebaseMessaging (~> 4.7.0) - - firebase_core (0.5.1): + - firebase_core (0.5.2): - Firebase/CoreOnly (~> 6.33.0) - Flutter - firebase_messaging (7.0.3): @@ -82,6 +82,8 @@ PODS: - Flutter - flutter_local_notifications (0.0.1): - Flutter + - flutter_secure_storage (3.3.1): + - Flutter - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) @@ -161,6 +163,7 @@ DEPENDENCIES: - flutter_app_badger (from `.symlinks/plugins/flutter_app_badger/ios`) - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) + - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - image_picker (from `.symlinks/plugins/image_picker/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) @@ -210,6 +213,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" flutter_local_notifications: :path: ".symlinks/plugins/flutter_local_notifications/ios" + flutter_secure_storage: + :path: ".symlinks/plugins/flutter_secure_storage/ios" image_picker: :path: ".symlinks/plugins/image_picker/ios" path_provider: @@ -232,7 +237,7 @@ SPEC CHECKSUMS: DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5 - firebase_core: aa25a5dc6b492ecab37587c53d8420135f0cac90 + firebase_core: 350ba329d1641211bc6183a3236893cafdacfea7 firebase_messaging: 0aea2cd5885b65e19ede58ee3507f485c992cc75 FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1 @@ -244,6 +249,7 @@ SPEC CHECKSUMS: flutter_app_badger: 65de4d6f0c34a891df49e6cfb8a1c0496426fa68 flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 + flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833 GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 6d377561..1baa240f 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -325,6 +325,7 @@ "${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_secure_storage/flutter_secure_storage.framework", "${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", @@ -354,6 +355,7 @@ "${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_secure_storage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 855373bf..894bc679 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.51+53 +version: 1.0.52+54 environment: sdk: ">=2.2.2 <3.0.0" From aa478bfef5b26695b47169d8e2bfb09a0d16065f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 14:54:22 +0100 Subject: [PATCH 33/34] use bold fontweight --- example/lib/advanced_options_page.dart | 4 ++++ example/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/example/lib/advanced_options_page.dart b/example/lib/advanced_options_page.dart index f0373345..8a4c79c0 100644 --- a/example/lib/advanced_options_page.dart +++ b/example/lib/advanced_options_page.dart @@ -74,6 +74,7 @@ class _AdvancedOptionsPageState extends State { decoration: InputDecoration( labelStyle: TextStyle( fontSize: 14, + fontWeight: FontWeight.bold, color: Colors.black.withOpacity(.5), ), border: UnderlineInputBorder( @@ -99,6 +100,7 @@ class _AdvancedOptionsPageState extends State { textInputAction: TextInputAction.next, decoration: InputDecoration( labelStyle: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14, color: Colors.black.withOpacity(.5), ), @@ -125,6 +127,7 @@ class _AdvancedOptionsPageState extends State { textInputAction: TextInputAction.next, decoration: InputDecoration( labelStyle: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14, color: Colors.black.withOpacity(.5), ), @@ -146,6 +149,7 @@ class _AdvancedOptionsPageState extends State { decoration: InputDecoration( labelStyle: TextStyle( fontSize: 14, + fontWeight: FontWeight.bold, color: Colors.black.withOpacity(.5), ), border: UnderlineInputBorder( diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 894bc679..c6743cd4 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.52+54 +version: 1.0.53+55 environment: sdk: ">=2.2.2 <3.0.0" From e32902d5aa3fe96922ea2852903883b0510aebe6 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 15:21:00 +0100 Subject: [PATCH 34/34] add unread indicator in backbutton --- lib/src/back_button.dart | 65 ++++++++++++++++----------- lib/src/channel_header.dart | 5 ++- lib/src/channel_preview.dart | 4 +- lib/src/channel_unread_indicator.dart | 48 ++++++++++++++++++++ lib/src/unread_indicator.dart | 61 +++++++++++++------------ 5 files changed, 123 insertions(+), 60 deletions(-) create mode 100644 lib/src/channel_unread_indicator.dart diff --git a/lib/src/back_button.dart b/lib/src/back_button.dart index 657a44ef..78b86ae5 100644 --- a/lib/src/back_button.dart +++ b/lib/src/back_button.dart @@ -1,44 +1,57 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/stream_icons.dart'; +import 'package:stream_chat_flutter/src/unread_indicator.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class StreamBackButton extends StatelessWidget { const StreamBackButton({ Key key, this.onPressed, this.icon = Icons.arrow_back_ios_outlined, + this.showUnreads = false, }) : super(key: key); final VoidCallback onPressed; final IconData icon; + final bool showUnreads; @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(14.0), - child: RawMaterialButton( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - disabledElevation: 0, - hoverElevation: 0, - onPressed: () { - if (onPressed != null) { - onPressed(); - } else { - Navigator.of(context).pop(); - } - }, - fillColor: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(.1) - : Colors.black.withOpacity(.1), - child: Icon( - icon ?? Icons.arrow_back_ios_outlined, - size: 15, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, + return Stack( + children: [ + Padding( + padding: const EdgeInsets.all(14.0), + child: RawMaterialButton( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + disabledElevation: 0, + hoverElevation: 0, + onPressed: () { + if (onPressed != null) { + onPressed(); + } else { + Navigator.of(context).pop(); + } + }, + child: Icon( + icon ?? StreamIcons.left, + size: 24, + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black, + ), + ), ), - ), + if (showUnreads) + Positioned( + top: 7, + right: 7, + child: UnreadIndicator(), + ), + ], ); } } diff --git a/lib/src/channel_header.dart b/lib/src/channel_header.dart index 81f95a87..ee0fd179 100644 --- a/lib/src/channel_header.dart +++ b/lib/src/channel_header.dart @@ -81,7 +81,10 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { brightness: Theme.of(context).brightness, elevation: 1, leading: showBackButton - ? StreamBackButton(onPressed: onBackPressed) + ? StreamBackButton( + onPressed: onBackPressed, + showUnreads: true, + ) : SizedBox(), backgroundColor: StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index c23d7732..ba3090c2 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/unread_indicator.dart'; import '../stream_chat_flutter.dart'; import 'channel_name.dart'; +import 'channel_unread_indicator.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png) @@ -80,7 +80,7 @@ class ChannelPreview extends StatelessWidget { e.user.id == channel.client.state.user.id)) { return SizedBox(); } - return UnreadIndicator( + return ChannelUnreadIndicator( channel: channel, ); }), diff --git a/lib/src/channel_unread_indicator.dart b/lib/src/channel_unread_indicator.dart new file mode 100644 index 00000000..8080b1e9 --- /dev/null +++ b/lib/src/channel_unread_indicator.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; + +class ChannelUnreadIndicator extends StatelessWidget { + const ChannelUnreadIndicator({ + Key key, + @required this.channel, + }) : super(key: key); + + final Channel channel; + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: channel.state.unreadCountStream, + initialData: channel.state.unreadCount, + builder: (context, snapshot) { + if (!snapshot.hasData || snapshot.data == 0) { + return SizedBox(); + } + return Material( + borderRadius: BorderRadius.circular(8), + color: StreamChatTheme.of(context) + .channelPreviewTheme + .unreadCounterColor, + child: Padding( + padding: const EdgeInsets.only( + left: 5.0, + right: 5.0, + top: 2, + bottom: 1, + ), + child: Center( + child: Text( + '${snapshot.data}', + style: TextStyle( + fontSize: 11, + color: Colors.white, + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/src/unread_indicator.dart b/lib/src/unread_indicator.dart index 7e49e202..c9f42fad 100644 --- a/lib/src/unread_indicator.dart +++ b/lib/src/unread_indicator.dart @@ -1,47 +1,46 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class UnreadIndicator extends StatelessWidget { const UnreadIndicator({ Key key, - @required this.channel, }) : super(key: key); - final Channel channel; - @override Widget build(BuildContext context) { + final client = StreamChat.of(context).client; return StreamBuilder( - stream: channel.state.unreadCountStream, - initialData: channel.state.unreadCount, - builder: (context, snapshot) { - if (!snapshot.hasData || snapshot.data == 0) { - return SizedBox(); - } - return Material( - borderRadius: BorderRadius.circular(8), - color: StreamChatTheme.of(context) - .channelPreviewTheme - .unreadCounterColor, - child: Padding( - padding: const EdgeInsets.only( - left: 5.0, - right: 5.0, - top: 2, - bottom: 1, - ), - child: Center( - child: Text( - '${snapshot.data}', - style: TextStyle( - fontSize: 11, - color: Colors.white, - ), + stream: client.state.totalUnreadCountStream, + initialData: client.state.totalUnreadCount, + builder: (context, snapshot) { + if (!snapshot.hasData || snapshot.data == 0) { + return SizedBox(); + } + return Material( + borderRadius: BorderRadius.circular(8), + color: StreamChatTheme.of(context) + .channelPreviewTheme + .unreadCounterColor, + child: Padding( + padding: const EdgeInsets.only( + left: 5.0, + right: 5.0, + top: 2, + bottom: 1, + ), + child: Center( + child: Text( + '${snapshot.data}', + style: TextStyle( + fontSize: 11, + color: Colors.white, ), ), ), - ); - }); + ), + ); + }, + ); } }