From c8bbd59732f1e11d2a104f15632a23f86560b608 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 6 Nov 2020 17:09:29 +0100 Subject: [PATCH 01/13] 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 bb6ba72b1a00912a4475c0af4d7e55902cab37b2 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 9 Nov 2020 18:39:16 +0100 Subject: [PATCH 02/13] 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 b1401a5f0ced55840eb6e7a76cce196872b096b3 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Nov 2020 16:05:51 +0100 Subject: [PATCH 03/13] 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 04/13] 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 05/13] 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 269ad0b99bed3a9817b81db7590bb6fa80aaa62f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 12:03:39 +0100 Subject: [PATCH 06/13] 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 07/13] 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 08/13] 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 09/13] 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 80d04016437b76a6233f2175a808d8ce6c7fc369 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 12:56:00 +0100 Subject: [PATCH 10/13] 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 11/13] 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 7b151dfb613b7a6f640753c9a08e598e9b5664af Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Nov 2020 14:22:33 +0100 Subject: [PATCH 12/13] 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 13/13] 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"