diff --git a/example/lib/advanced_options_page.dart b/example/lib/advanced_options_page.dart index e24bd5da..7555f286 100644 --- a/example/lib/advanced_options_page.dart +++ b/example/lib/advanced_options_page.dart @@ -60,15 +60,11 @@ class _AdvancedOptionsPageState extends State { body: Builder( builder: (context) { return Padding( - padding: const EdgeInsets.fromLTRB( - 16, - 16, - 16, - 0, - ), + padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), child: Form( key: _formKey, child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextFormField( controller: _apiKeyController, @@ -93,16 +89,13 @@ class _AdvancedOptionsPageState extends State { color: StreamChatTheme.of(context).colorTheme.black, ), decoration: InputDecoration( - errorStyle: TextStyle(fontSize: 0), + errorStyle: TextStyle(height: 0, fontSize: 0), labelStyle: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _apiKeyError != null ? StreamChatTheme.of(context).colorTheme.accentRed - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), + : StreamChatTheme.of(context).colorTheme.grey, ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), @@ -112,261 +105,231 @@ class _AdvancedOptionsPageState extends State { StreamChatTheme.of(context).colorTheme.whiteSmoke, filled: true, labelText: - 'Chat API Key ${_apiKeyError != null ? ':$_apiKeyError' : ''}', + 'Chat API Key ${_apiKeyError != null ? ': $_apiKeyError' : ''}', ), textInputAction: TextInputAction.next, ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: TextFormField( - controller: _userIdController, - onChanged: (_) { - if (_userIdError != null) { - setState(() { - _userIdError = null; - }); - } - }, - validator: (value) { - if (value.isEmpty) { - setState(() { - _userIdError = 'Please enter the User ID'; - }); - return _userIdError; - } - return null; - }, - style: TextStyle( + SizedBox(height: 8), + TextFormField( + controller: _userIdController, + onChanged: (_) { + if (_userIdError != null) { + setState(() { + _userIdError = null; + }); + } + }, + validator: (value) { + if (value.isEmpty) { + setState(() { + _userIdError = 'Please enter the User ID'; + }); + return _userIdError; + } + return null; + }, + style: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.black, + ), + textInputAction: TextInputAction.next, + decoration: InputDecoration( + errorStyle: TextStyle(height: 0, fontSize: 0), + labelStyle: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14, - color: StreamChatTheme.of(context).colorTheme.black, + color: _userIdError != null + ? StreamChatTheme.of(context).colorTheme.accentRed + : StreamChatTheme.of(context).colorTheme.grey, ), - textInputAction: TextInputAction.next, - decoration: InputDecoration( - errorStyle: TextStyle(fontSize: 0), - labelStyle: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, - color: _userIdError != null - ? StreamChatTheme.of(context).colorTheme.accentRed - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - border: UnderlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - fillColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - filled: true, - labelText: - 'User ID ${_userIdError != null ? ':$_userIdError' : ''}', + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, ), + fillColor: + StreamChatTheme.of(context).colorTheme.whiteSmoke, + filled: true, + labelText: + 'User ID ${_userIdError != null ? ': $_userIdError' : ''}', ), ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: TextFormField( - onChanged: (_) { - if (_userTokenError != null) { - setState(() { - _userTokenError = null; - }); - } - }, - controller: _userTokenController, - validator: (value) { - if (value.isEmpty) { - setState(() { - _userTokenError = 'Please enter the user token'; - }); - return _userTokenError; - } - return null; - }, - style: TextStyle( + SizedBox(height: 8), + TextFormField( + onChanged: (_) { + if (_userTokenError != null) { + setState(() { + _userTokenError = null; + }); + } + }, + controller: _userTokenController, + validator: (value) { + if (value.isEmpty) { + setState(() { + _userTokenError = 'Please enter the user token'; + }); + return _userTokenError; + } + return null; + }, + style: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.black, + ), + textInputAction: TextInputAction.next, + decoration: InputDecoration( + errorStyle: TextStyle(height: 0, fontSize: 0), + labelStyle: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14, - color: StreamChatTheme.of(context).colorTheme.black, + color: _userTokenError != null + ? StreamChatTheme.of(context).colorTheme.accentRed + : StreamChatTheme.of(context).colorTheme.grey, ), - textInputAction: TextInputAction.next, - decoration: InputDecoration( - errorStyle: TextStyle(fontSize: 0), - labelStyle: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, - color: _userTokenError != null - ? StreamChatTheme.of(context).colorTheme.accentRed - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - border: UnderlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - fillColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - filled: true, - labelText: - 'User Token ${_userTokenError != null ? ':$_userTokenError' : ''}', + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, ), + fillColor: + StreamChatTheme.of(context).colorTheme.whiteSmoke, + filled: true, + labelText: + 'User Token ${_userTokenError != null ? ': $_userTokenError' : ''}', ), ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: TextFormField( - controller: _usernameController, - textInputAction: TextInputAction.done, - decoration: InputDecoration( - labelStyle: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - border: UnderlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - fillColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - filled: true, - labelText: 'Username (optional)', + SizedBox(height: 8), + TextFormField( + controller: _usernameController, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + labelStyle: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: StreamChatTheme.of(context).colorTheme.grey, ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: + StreamChatTheme.of(context).colorTheme.whiteSmoke, + filled: true, + labelText: 'Username (optional)', ), ), - Expanded( - child: Align( - alignment: Alignment.bottomCenter, - child: FlatButton( - color: - StreamChatTheme.of(context).colorTheme.accentBlue, - minWidth: double.infinity, - height: 48, - child: Text( - 'Login', - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.white, - fontSize: 16, - ), - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(26), - ), - onPressed: () async { - if (loading) { - return; - } - if (_formKey.currentState.validate()) { - final apiKey = _apiKeyController.text; - final userId = _userIdController.text; - final userToken = _userTokenController.text; - final username = _usernameController.text; + Spacer(), + RaisedButton( + elevation: 0, + child: Text('Login', style: TextStyle(fontSize: 16)), + onPressed: () async { + if (loading) { + return; + } + if (_formKey.currentState.validate()) { + final apiKey = _apiKeyController.text; + final userId = _userIdController.text; + final userToken = _userTokenController.text; + final username = _usernameController.text; - loading = true; - showDialog( - barrierDismissible: false, - context: context, - builder: (context) => Center( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: StreamChatTheme.of(context) - .colorTheme - .white, - ), - height: 100, - width: 100, - child: Center( - child: CircularProgressIndicator(), - ), - ), + loading = true; + showDialog( + barrierDismissible: false, + context: context, + barrierColor: + StreamChatTheme.of(context).colorTheme.overlay, + builder: (context) => Center( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: StreamChatTheme.of(context) + .colorTheme + .white, ), - ); + height: 100, + width: 100, + child: Center( + child: CircularProgressIndicator(), + ), + ), + ), + ); - final client = Client( - apiKey, - logLevel: Level.INFO, - showLocalNotification: - (!kIsWeb && Platform.isAndroid) - ? showLocalNotification - : null, - persistenceEnabled: true, - ); + 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, - ); + try { + await client.setUser( + User(id: userId, extraData: { + 'name': username, + }), + userToken, + ); - if (!kIsWeb) { - initNotifications(client); - } + if (!kIsWeb) { + initNotifications(client); + } - final secureStorage = FlutterSecureStorage(); - secureStorage.write( - key: kStreamApiKey, - value: apiKey, - ); - secureStorage.write( - key: kStreamUserId, - value: userId, - ); - secureStorage.write( - key: kStreamToken, - value: userToken, - ); - } catch (e) { - var errorText = 'Error connecting, retry'; - if (e is Map) { - errorText = e['message'] ?? errorText; - } - Navigator.pop(context); - setState(() { - _apiKeyError = errorText; - }); - loading = false; - await client.disconnect(); - return; - } + final secureStorage = FlutterSecureStorage(); + secureStorage.write( + key: kStreamApiKey, + value: apiKey, + ); + secureStorage.write( + key: kStreamUserId, + value: userId, + ); + secureStorage.write( + key: kStreamToken, + value: userToken, + ); + } catch (e) { + var errorText = 'Error connecting, retry'; + if (e is Map) { + errorText = e['message'] ?? errorText; + } + Navigator.pop(context); + setState(() { + _apiKeyError = errorText; + }); + loading = false; + await client.disconnect(); + return; + } - if (!kIsWeb) { - initNotifications(client); - } + 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(), - themeMode: ThemeMode.system, - builder: (context, widget) { - return StreamChat( - child: widget, - client: client, - ); - }, - home: ChannelListPage(), + Navigator.pop(context); + Navigator.pop(context); + await Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) { + return MaterialApp( + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + themeMode: ThemeMode.system, + builder: (context, widget) { + return StreamChat( + child: widget, + client: client, ); }, - ), - ); - loading = false; - } - }, - ), - ), + home: ChannelListPage(), + ); + }, + ), + ); + loading = false; + } + }, ), StreamVersion(), ], diff --git a/example/lib/choose_user_page.dart b/example/lib/choose_user_page.dart index 55bfa36c..6da88a29 100644 --- a/example/lib/choose_user_page.dart +++ b/example/lib/choose_user_page.dart @@ -72,6 +72,7 @@ class ChooseUserPage extends StatelessWidget { child: SvgPicture.asset( 'assets/logo.svg', height: 40, + color: StreamChatTheme.of(context).colorTheme.accentBlue, ), ), ), @@ -92,9 +93,8 @@ class ChooseUserPage extends StatelessWidget { child: ListView.separated( separatorBuilder: (context, i) { return Container( - width: double.infinity, - color: Colors.black12, height: 1, + color: StreamChatTheme.of(context).colorTheme.greyWhisper, ); }, itemCount: users.length + 1, @@ -108,6 +108,9 @@ class ChooseUserPage extends StatelessWidget { showDialog( barrierDismissible: false, context: context, + barrierColor: StreamChatTheme.of(context) + .colorTheme + .overlay, builder: (context) => Center( child: Container( decoration: BoxDecoration( @@ -177,10 +180,10 @@ class ChooseUserPage extends StatelessWidget { .grey, ), ), - trailing: SvgPicture.asset( - 'assets/icon_arrow_right.svg', - height: 24, - width: 24, + trailing: StreamSvgIcon.arrow_right( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, ), ); }), diff --git a/example/lib/custom_theme.dart b/example/lib/custom_theme.dart index 90635993..d8abcb90 100644 --- a/example/lib/custom_theme.dart +++ b/example/lib/custom_theme.dart @@ -39,28 +39,29 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = ThemeData( - primarySwatch: Colors.green, - ); - - return MaterialApp( - theme: theme, - builder: (context, child) => StreamChat( - child: child, - client: client, - streamChatThemeData: StreamChatThemeData.fromTheme(theme).copyWith( - ownMessageTheme: MessageTheme( - messageBackgroundColor: - StreamChatTheme.of(context).colorTheme.black, - messageText: TextStyle( - color: StreamChatTheme.of(context).colorTheme.white, - ), - avatarTheme: AvatarTheme( - borderRadius: BorderRadius.circular(8), - ), - ), + final themeData = ThemeData(primarySwatch: Colors.green); + final defaultTheme = StreamChatThemeData.fromTheme(themeData); + final customTheme = defaultTheme.merge(StreamChatThemeData( + ownMessageTheme: MessageTheme( + messageBackgroundColor: StreamChatTheme.of(context).colorTheme.black, + messageText: TextStyle( + color: StreamChatTheme.of(context).colorTheme.white, + ), + avatarTheme: AvatarTheme( + borderRadius: BorderRadius.circular(8), ), ), + )); + + return MaterialApp( + theme: themeData, + builder: (context, child) { + return StreamChat( + child: child, + client: client, + streamChatThemeData: customTheme, + ); + }, home: ChannelListPage(), ); } diff --git a/example/lib/group_chat_details_screen.dart b/example/lib/group_chat_details_screen.dart index 03b4877b..31ccc703 100644 --- a/example/lib/group_chat_details_screen.dart +++ b/example/lib/group_chat_details_screen.dart @@ -83,10 +83,7 @@ class _GroupChatDetailsScreenState extends State { 'NAME', style: TextStyle( fontSize: 12, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.grey, ), ), SizedBox(width: 16), @@ -103,11 +100,9 @@ class _GroupChatDetailsScreenState extends State { contentPadding: const EdgeInsets.all(0), hintText: 'Choose a group chat name', hintStyle: TextStyle( - fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5)), + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.grey, + ), ), ), ), @@ -122,7 +117,7 @@ class _GroupChatDetailsScreenState extends State { icon: StreamSvgIcon.check( size: 24, color: _isGroupNameEmpty - ? Colors.grey + ? StreamChatTheme.of(context).colorTheme.grey : StreamChatTheme.of(context).colorTheme.accentBlue, ), onPressed: _isGroupNameEmpty @@ -155,14 +150,7 @@ class _GroupChatDetailsScreenState extends State { Container( width: double.maxFinite, decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - StreamChatTheme.of(context).colorTheme.whiteSmoke, - StreamChatTheme.of(context).colorTheme.whiteSnow, - ], - ), + gradient: StreamChatTheme.of(context).colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.symmetric( @@ -172,7 +160,7 @@ class _GroupChatDetailsScreenState extends State { child: Text( '$_totalUsers ${_totalUsers > 1 ? 'Members' : 'Member'}', style: TextStyle( - fontWeight: FontWeight.w500, + color: StreamChatTheme.of(context).colorTheme.grey, ), ), ), @@ -185,29 +173,14 @@ class _GroupChatDetailsScreenState extends State { itemCount: _selectedUsers.length + 1, separatorBuilder: (_, __) => Container( height: 1, - color: Theme.of(context).brightness == Brightness.dark - ? StreamChatTheme.of(context) - .colorTheme - .white - .withOpacity(0.1) - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.1), + color: StreamChatTheme.of(context).colorTheme.greyWhisper, ), itemBuilder: (_, index) { if (index == _selectedUsers.length) { return Container( height: 1, - color: Theme.of(context).brightness == Brightness.dark - ? StreamChatTheme.of(context) - .colorTheme - .white - .withOpacity(0.1) - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.1), + color: + StreamChatTheme.of(context).colorTheme.greyWhisper, ); } final user = _selectedUsers[index]; diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index ffab48df..985fe62d 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -589,6 +589,7 @@ class _GroupInfoScreenState extends State { showDialog( context: context, + barrierColor: StreamChatTheme.of(context).colorTheme.overlay, builder: (context) { return StatefulBuilder(builder: (context, modalSetState) { modalSetStateCallback = modalSetState; diff --git a/example/lib/new_chat_screen.dart b/example/lib/new_chat_screen.dart index ddebe848..1de36b46 100644 --- a/example/lib/new_chat_screen.dart +++ b/example/lib/new_chat_screen.dart @@ -157,8 +157,7 @@ class _NewChatScreenState extends State { decoration: BoxDecoration( color: StreamChatTheme.of(context) .colorTheme - .black - .withOpacity(0.05), + .greyGainsboro, borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.only(left: 24), @@ -166,15 +165,19 @@ class _NewChatScreenState extends State { padding: const EdgeInsets.fromLTRB(8, 4, 12, 4), child: Text( user.name, + maxLines: 1, style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black), + color: + StreamChatTheme.of(context).colorTheme.black, + ), ), ), ), - Opacity( - opacity: .8, + Container( + foregroundDecoration: BoxDecoration( + color: StreamChatTheme.of(context).colorTheme.overlay, + shape: BoxShape.circle, + ), child: UserAvatar( showOnlineStatus: false, user: user, @@ -184,11 +187,7 @@ class _NewChatScreenState extends State { ), ), ), - Positioned( - child: StreamSvgIcon.close( - color: StreamChatTheme.of(context).colorTheme.white, - ), - ), + StreamSvgIcon.close(), ], ), ); @@ -237,21 +236,7 @@ class _NewChatScreenState extends State { Container( width: double.maxFinite, decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - colors: [ - StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.02), - StreamChatTheme.of(context) - .colorTheme - .white - .withOpacity(0.05), - ], - stops: [0, 1], - ), + gradient: StreamChatTheme.of(context).colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.symmetric( diff --git a/example/lib/new_group_chat_screen.dart b/example/lib/new_group_chat_screen.dart index 505db69c..ceb91473 100644 --- a/example/lib/new_group_chat_screen.dart +++ b/example/lib/new_group_chat_screen.dart @@ -52,248 +52,227 @@ class _NewGroupChatScreenState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, - body: SafeArea( - child: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { - return [ - SliverAppBar( - brightness: Theme.of(context).brightness, - pinned: true, - forceElevated: true, - elevation: 1, - backgroundColor: StreamChatTheme.of(context).colorTheme.white, - leading: const StreamBackButton(), - title: Text( - 'Add Group Members', - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.black, - fontSize: 16, - ), - ), - centerTitle: true, - actions: [ - if (_selectedUsers.isNotEmpty) - IconButton( - icon: StreamSvgIcon.arrow_right( - color: - StreamChatTheme.of(context).colorTheme.accentBlue, - ), - onPressed: () async { - final updatedList = await Navigator.pushNamed( - context, - Routes.NEW_GROUP_CHAT_DETAILS, - arguments: _selectedUsers.toList(growable: false), - ); - if (updatedList != null) { - setState(() { - _selectedUsers - ..clear() - ..addAll(updatedList); - }); - } - }, - ) - ], + appBar: AppBar( + elevation: 1, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + leading: const StreamBackButton(), + title: Text( + 'Add Group Members', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.black, + fontSize: 16, + ), + ), + centerTitle: true, + actions: [ + if (_selectedUsers.isNotEmpty) + IconButton( + icon: StreamSvgIcon.arrow_right( + color: StreamChatTheme.of(context).colorTheme.accentBlue, ), + onPressed: () async { + final updatedList = await Navigator.pushNamed( + context, + Routes.NEW_GROUP_CHAT_DETAILS, + arguments: _selectedUsers.toList(growable: false), + ); + if (updatedList != null) { + setState(() { + _selectedUsers + ..clear() + ..addAll(updatedList); + }); + } + }, + ) + ], + ), + body: NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverToBoxAdapter( + child: SearchTextField( + controller: _controller, + ), + ), + if (_selectedUsers.isNotEmpty) SliverToBoxAdapter( - child: SearchTextField( - controller: _controller, - ), - ), - if (_selectedUsers.isNotEmpty) - SliverToBoxAdapter( - child: Container( - height: 104, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: _selectedUsers.length, - padding: const EdgeInsets.all(8), - separatorBuilder: (_, __) => SizedBox(width: 16), - itemBuilder: (_, index) { - final user = _selectedUsers.elementAt(index); - return Column( - children: [ - Stack( - children: [ - UserAvatar( - onlineIndicatorAlignment: Alignment(0.9, 0.9), - user: user, - showOnlineStatus: true, - borderRadius: BorderRadius.circular(32), - constraints: BoxConstraints.tightFor( - height: 64, - width: 64, - ), + child: Container( + height: 104, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: _selectedUsers.length, + padding: const EdgeInsets.all(8), + separatorBuilder: (_, __) => SizedBox(width: 16), + itemBuilder: (_, index) { + final user = _selectedUsers.elementAt(index); + return Column( + children: [ + Stack( + children: [ + UserAvatar( + onlineIndicatorAlignment: Alignment(0.9, 0.9), + user: user, + showOnlineStatus: true, + borderRadius: BorderRadius.circular(32), + constraints: BoxConstraints.tightFor( + height: 64, + width: 64, ), - Positioned( - top: -4, - right: -4, - child: GestureDetector( - onTap: () { - if (_selectedUsers.contains(user)) { - setState( - () => _selectedUsers.remove(user)); - } - }, - child: Container( - decoration: BoxDecoration( + ), + Positioned( + top: -4, + right: -4, + child: GestureDetector( + onTap: () { + if (_selectedUsers.contains(user)) { + setState( + () => _selectedUsers.remove(user)); + } + }, + child: Container( + decoration: BoxDecoration( + color: StreamChatTheme.of(context) + .colorTheme + .white, + shape: BoxShape.circle, + border: Border.all( color: StreamChatTheme.of(context) .colorTheme - .white, - shape: BoxShape.circle, - border: Border.all( - color: StreamChatTheme.of(context) - .colorTheme - .whiteSnow, - ), - ), - child: StreamSvgIcon.close( - color: StreamChatTheme.of(context) - .colorTheme - .black, - size: 24, + .whiteSnow, ), ), + child: StreamSvgIcon.close( + color: StreamChatTheme.of(context) + .colorTheme + .black, + size: 24, + ), ), - ) - ], + ), + ) + ], + ), + SizedBox(height: 4), + Text( + user.name.split(' ')[0], + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12, ), - SizedBox(height: 4), - Text( - user.name.split(' ')[0], - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 12, - ), - ), - ], - ); - }, - ), - ), - ), - SliverPersistentHeader( - pinned: true, - delegate: _HeaderDelegate( - height: 30, - child: Container( - width: double.maxFinite, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - colors: [ - StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.02), - StreamChatTheme.of(context) - .colorTheme - .white - .withOpacity(0.05), + ), ], - stops: [0, 1], - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 8, - ), - child: Text( - _isSearchActive - ? 'Matches for \"$_userNameQuery\"' - : 'On the platform', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), - ), - ), - ), - ), - ), - ), - ]; - }, - body: GestureDetector( - behavior: HitTestBehavior.opaque, - onPanDown: (_) => FocusScope.of(context).unfocus(), - child: UsersBloc( - child: UserListView( - selectedUsers: _selectedUsers, - pullToRefresh: false, - groupAlphabetically: _isSearchActive ? false : true, - onUserTap: (user, _) { - if (!_selectedUsers.contains(user)) { - setState(() { - _selectedUsers.add(user); - }); - } else { - setState(() { - _selectedUsers.remove(user); - }); - } - }, - pagination: PaginationParams( - limit: 25, - ), - filter: { - if (_userNameQuery.isNotEmpty) - 'name': { - r'$autocomplete': _userNameQuery, - }, - 'id': { - r'$ne': StreamChat.of(context).user.id, - } - }, - sort: [ - SortOption( - 'name', - direction: 1, - ), - ], - emptyBuilder: (_) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(24), - child: StreamSvgIcon.search( - size: 96, - color: Colors.grey, - ), - ), - Text( - 'No user matches these keywords...', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5)), - ), - ], - ), - ), - ), ); }, - ); - }, + ), + ), ), + SliverPersistentHeader( + pinned: true, + delegate: _HeaderDelegate( + height: 30, + child: Container( + width: double.maxFinite, + decoration: BoxDecoration( + gradient: StreamChatTheme.of(context).colorTheme.bgGradient, + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 8, + ), + child: Text( + _isSearchActive + ? 'Matches for \"$_userNameQuery\"' + : 'On the platform', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.grey, + ), + ), + ), + ), + ), + ), + ]; + }, + body: GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: UsersBloc( + child: UserListView( + selectedUsers: _selectedUsers, + pullToRefresh: false, + groupAlphabetically: _isSearchActive ? false : true, + onUserTap: (user, _) { + if (!_selectedUsers.contains(user)) { + setState(() { + _selectedUsers.add(user); + }); + } else { + setState(() { + _selectedUsers.remove(user); + }); + } + }, + pagination: PaginationParams( + limit: 25, + ), + filter: { + if (_userNameQuery.isNotEmpty) + 'name': { + r'$autocomplete': _userNameQuery, + }, + 'id': { + r'$ne': StreamChat.of(context).user.id, + } + }, + sort: [ + SortOption( + 'name', + direction: 1, + ), + ], + emptyBuilder: (_) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: StreamSvgIcon.search( + size: 96, + color: StreamChatTheme.of(context) + .colorTheme + .grey, + ), + ), + Text( + 'No user matches these keywords...', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .grey, + ), + ), + ], + ), + ), + ), + ); + }, + ); + }, ), ), ), diff --git a/example/lib/stream_version.dart b/example/lib/stream_version.dart index a3d52ab6..b3f926be 100644 --- a/example/lib/stream_version.dart +++ b/example/lib/stream_version.dart @@ -28,9 +28,8 @@ class StreamVersion extends StatelessWidget { return Text( 'Stream SDK v ${streamChatDep}', style: TextStyle( - fontSize: 14.5, - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(.13), + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ), ); }, diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index 0001df6f..332117d4 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -29,27 +29,8 @@ class ImageActionsModal extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( behavior: HitTestBehavior.translucent, - onTap: () { - Navigator.pop(context); - }, - child: Stack( - children: [ - Positioned.fill( - child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.black.withOpacity(0.8), - Colors.transparent, - ], - stops: [0.0, 0.4], - )), - )), - _buildPage(context), - ], - ), + onTap: () => Navigator.maybePop(context), + child: _buildPage(context), ); } @@ -57,53 +38,14 @@ class ImageActionsModal extends StatelessWidget { return Material( color: Colors.transparent, child: Column( + crossAxisAlignment: CrossAxisAlignment.end, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - width: 40.0, - ), - ), - Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - userName, - style: StreamChatTheme.of(context) - .textTheme - .headlineBold - .copyWith( - color: - StreamChatTheme.of(context).colorTheme.white), - ), - ), - Text( - sentAt, - style: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .white - .withOpacity(0.5)), - ), - ], - ), - IconButton( - icon: StreamSvgIcon.close( - size: 24.0, - color: StreamChatTheme.of(context).colorTheme.white, - ), - onPressed: () { - Navigator.pop(context); - }, - ), - ], + SizedBox( + height: kToolbarHeight, + child: IconButton( + icon: StreamSvgIcon.close(), + onPressed: () => Navigator.maybePop(context), + ), ), Align( alignment: Alignment.centerRight, @@ -127,10 +69,8 @@ class ImageActionsModal extends StatelessWidget { 'Reply', StreamSvgIcon.Icon_curve_line_left_up( size: 24.0, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), + color: + StreamChatTheme.of(context).colorTheme.grey, ), () {}), _buildButton( @@ -147,10 +87,8 @@ class ImageActionsModal extends StatelessWidget { 'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}', StreamSvgIcon.Icon_save( size: 24.0, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), + color: + StreamChatTheme.of(context).colorTheme.grey, ), () async { var url = urls[currentIndex].imageUrl ?? urls[currentIndex].assetUrl ?? diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 3a6cfbcc..7ee9a128 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -157,9 +157,7 @@ class _ImageFooterState extends State { icon: StreamSvgIcon.Icon_grid( color: StreamChatTheme.of(context).colorTheme.black, ), - onPressed: () { - _buildPhotosModal(context); - }, + onPressed: () => _showPhotosModal(context), ), ], ), @@ -168,13 +166,14 @@ class _ImageFooterState extends State { ); } - Widget _buildPhotosModal(context) { + void _showPhotosModal(context) { var videoAttachments = widget.mediaAttachments .where((element) => element.type == 'video') .toList(); showModalBottomSheet( context: context, + barrierColor: StreamChatTheme.of(context).colorTheme.overlay, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.0), ), @@ -207,9 +206,7 @@ class _ImageFooterState extends State { icon: StreamSvgIcon.close( color: StreamChatTheme.of(context).colorTheme.black, ), - onPressed: () { - Navigator.pop(context); - }, + onPressed: () => Navigator.maybePop(context), ), ), ], diff --git a/lib/src/image_header.dart b/lib/src/image_header.dart index 6139bc7c..1f6237ab 100644 --- a/lib/src/image_header.dart +++ b/lib/src/image_header.dart @@ -106,6 +106,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { showDialog( context: context, + barrierColor: StreamChatTheme.of(context).colorTheme.overlay, builder: (context) { return StreamChannel( channel: channel, diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart index 6294ef6a..c7ff56e0 100644 --- a/lib/src/message_actions_modal.dart +++ b/lib/src/message_actions_modal.dart @@ -73,17 +73,17 @@ class MessageActionsModal extends StatelessWidget { return GestureDetector( behavior: HitTestBehavior.translucent, - onTap: () => Navigator.pop(context), + onTap: () => Navigator.maybePop(context), child: Stack( children: [ Positioned.fill( child: BackdropFilter( filter: ImageFilter.blur( - sigmaX: 10.8731, - sigmaY: 10.8731, + sigmaX: 10, + sigmaY: 10, ), child: Container( - color: Colors.black.withOpacity(.2), + color: StreamChatTheme.of(context).colorTheme.overlay, ), ), ), diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 68a1633b..db9790b9 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -122,6 +122,7 @@ class MessageListView extends StatefulWidget { this.itemPositionListener, this.onMessageSwiped, this.highlightInitialMessage = false, + this.messageHighlightColor, this.onShowMessage, }) : super(key: key); @@ -175,6 +176,9 @@ class MessageListView extends StatefulWidget { /// Also See [StreamChannel] final bool highlightInitialMessage; + /// Color used while highlighting initial message + final Color messageHighlightColor; + final ShowMessageCallback onShowMessage; @override @@ -346,18 +350,9 @@ class _MessageListViewState extends State { buildParentMessage(widget.parentMessage), Container( decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - StreamChatTheme.of(context) - .colorTheme - .whiteSmoke, - StreamChatTheme.of(context) - .colorTheme - .whiteSnow, - ], - ), + gradient: StreamChatTheme.of(context) + .colorTheme + .bgGradient, ), child: Padding( padding: const EdgeInsets.all(8.0), @@ -845,17 +840,17 @@ class _MessageListViewState extends State { if (!initialMessageHighlightComplete && widget.highlightInitialMessage && _isInitialMessage(message.id)) { - final accentColor = Theme.of(context).accentColor; + final colorTheme = StreamChatTheme.of(context).colorTheme; + final highlightColor = + widget.messageHighlightColor ?? colorTheme.highlight; child = TweenAnimationBuilder( tween: ColorTween( - begin: accentColor.withOpacity(0.7), - end: Colors.transparent, + begin: highlightColor, + end: colorTheme.white.withOpacity(0), ), - duration: const Duration(seconds: 2), + duration: const Duration(seconds: 3), child: child, - onEnd: () { - initialMessageHighlightComplete = true; - }, + onEnd: () => initialMessageHighlightComplete = true, builder: (_, color, child) { return Container( color: color, diff --git a/lib/src/message_reactions_modal.dart b/lib/src/message_reactions_modal.dart index 232f6f84..375f5e0c 100644 --- a/lib/src/message_reactions_modal.dart +++ b/lib/src/message_reactions_modal.dart @@ -40,27 +40,35 @@ class MessageReactionsModal extends StatelessWidget { final user = StreamChat.of(context).user; final roughMaxSize = 2 * size.width / 3; + var messageTextLength = message.text.length; + if (message.quotedMessage != null) { + var quotedMessageLength = message.quotedMessage.text.length + 40; + if (message.quotedMessage.attachments?.isNotEmpty == true) { + quotedMessageLength += 40; + } + if (quotedMessageLength > messageTextLength) { + messageTextLength = quotedMessageLength; + } + } final roughSentenceSize = - message.text.length * messageTheme.messageText.fontSize * 1.2; + messageTextLength * messageTheme.messageText.fontSize * 1.2; final divFactor = message.attachments?.isNotEmpty == true ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); return GestureDetector( behavior: HitTestBehavior.translucent, - onTap: () { - Navigator.pop(context); - }, + onTap: () => Navigator.maybePop(context), child: Stack( children: [ Positioned.fill( child: BackdropFilter( filter: ImageFilter.blur( - sigmaX: 10.8731, - sigmaY: 10.8731, + sigmaX: 10, + sigmaY: 10, ), child: Container( - color: Colors.black.withOpacity(0.2), + color: StreamChatTheme.of(context).colorTheme.overlay, ), ), ), diff --git a/lib/src/message_search_list_view.dart b/lib/src/message_search_list_view.dart index d59ae4a0..3ff9f151 100644 --- a/lib/src/message_search_list_view.dart +++ b/lib/src/message_search_list_view.dart @@ -313,21 +313,7 @@ class _MessageSearchListViewState extends State { Container( width: double.maxFinite, decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - colors: [ - StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.02), - StreamChatTheme.of(context) - .colorTheme - .white - .withOpacity(0.05), - ], - stops: [0, 1], - ), + gradient: StreamChatTheme.of(context).colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.symmetric( @@ -337,10 +323,7 @@ class _MessageSearchListViewState extends State { child: Text( '${items.length} results', style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.grey, ), ), ), diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 15364606..4b22f3ec 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -374,7 +374,9 @@ class _MessageWidgetState extends State { elevation: 0.0, shape: widget.shape ?? RoundedRectangleBorder( - side: isOnlyEmoji + side: isOnlyEmoji && + !(showThreadReplyIndicator || + showInChannel) ? BorderSide.none : widget.borderSide ?? BorderSide( @@ -676,6 +678,7 @@ class _MessageWidgetState extends State { final channel = StreamChannel.of(context).channel; showDialog( context: context, + barrierColor: StreamChatTheme.of(context).colorTheme.overlay, builder: (context) { return StreamChannel( channel: channel, @@ -716,6 +719,7 @@ class _MessageWidgetState extends State { final channel = StreamChannel.of(context).channel; showDialog( context: context, + barrierColor: StreamChatTheme.of(context).colorTheme.overlay, builder: (context) { return StreamChannel( channel: channel, diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 521dc3c0..b5883fdb 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -78,6 +78,7 @@ class StreamChatState extends State with WidgetsBindingObserver { primaryIconTheme: streamTheme.primaryIconTheme, accentColor: streamTheme.colorTheme.accentBlue, scaffoldBackgroundColor: streamTheme.colorTheme.white, + buttonTheme: streamTheme.buttonTheme, ), child: widget.child, ); @@ -91,76 +92,7 @@ class StreamChatState extends State with WidgetsBindingObserver { StreamChatThemeData themeData, ) { final defaultTheme = StreamChatThemeData.getDefaultTheme(Theme.of(context)); - final theme = defaultTheme.copyWith( - defaultChannelImage: themeData?.defaultChannelImage, - primaryIconTheme: themeData?.primaryIconTheme, - defaultUserImage: themeData?.defaultUserImage, - channelTheme: defaultTheme.channelTheme.copyWith( - channelHeaderTheme: - defaultTheme.channelTheme.channelHeaderTheme.copyWith( - color: themeData?.channelTheme?.channelHeaderTheme?.color, - lastMessageAt: - themeData?.channelTheme?.channelHeaderTheme?.lastMessageAt, - title: themeData?.channelTheme?.channelHeaderTheme?.title, - avatarTheme: defaultTheme.channelPreviewTheme.avatarTheme.copyWith( - constraints: themeData - ?.channelTheme?.channelHeaderTheme?.avatarTheme?.constraints, - borderRadius: themeData - ?.channelTheme?.channelHeaderTheme?.avatarTheme?.borderRadius, - ), - ), - inputBackground: themeData?.channelTheme?.inputBackground, - messageInputButtonIconTheme: - themeData?.channelTheme?.messageInputButtonIconTheme, - messageInputButtonTheme: - themeData?.channelTheme?.messageInputButtonTheme, - ), - ownMessageTheme: defaultTheme.ownMessageTheme.copyWith( - replies: themeData?.ownMessageTheme?.replies, - createdAt: themeData?.ownMessageTheme?.createdAt, - messageText: themeData?.ownMessageTheme?.messageText, - messageBackgroundColor: - themeData?.ownMessageTheme?.messageBackgroundColor, - messageAuthor: themeData?.ownMessageTheme?.messageAuthor, - messageLinks: themeData?.ownMessageTheme?.messageLinks, - avatarTheme: defaultTheme.ownMessageTheme.avatarTheme.copyWith( - constraints: themeData?.ownMessageTheme?.avatarTheme?.constraints, - borderRadius: themeData?.ownMessageTheme?.avatarTheme?.borderRadius, - ), - reactionsBorderColor: themeData?.ownMessageTheme?.reactionsBorderColor, - reactionsBackgroundColor: - themeData?.ownMessageTheme?.reactionsBackgroundColor, - ), - otherMessageTheme: defaultTheme.otherMessageTheme.copyWith( - replies: themeData?.otherMessageTheme?.replies, - createdAt: themeData?.otherMessageTheme?.createdAt, - messageText: themeData?.otherMessageTheme?.messageText, - messageBackgroundColor: - themeData?.otherMessageTheme?.messageBackgroundColor, - messageAuthor: themeData?.otherMessageTheme?.messageAuthor, - messageLinks: themeData?.otherMessageTheme?.messageLinks, - avatarTheme: defaultTheme.otherMessageTheme.avatarTheme.copyWith( - constraints: themeData?.otherMessageTheme?.avatarTheme?.constraints, - borderRadius: themeData?.otherMessageTheme?.avatarTheme?.borderRadius, - ), - reactionsBorderColor: - themeData?.otherMessageTheme?.reactionsBorderColor, - reactionsBackgroundColor: - themeData?.otherMessageTheme?.reactionsBackgroundColor, - ), - channelPreviewTheme: defaultTheme.channelPreviewTheme.copyWith( - avatarTheme: defaultTheme.channelPreviewTheme.avatarTheme.copyWith( - constraints: themeData?.channelPreviewTheme?.avatarTheme?.constraints, - borderRadius: - themeData?.channelPreviewTheme?.avatarTheme?.borderRadius, - ), - title: themeData?.channelPreviewTheme?.title, - lastMessageAt: themeData?.channelPreviewTheme?.lastMessageAt, - subtitle: themeData?.channelPreviewTheme?.subtitle, - unreadCounterColor: themeData?.channelPreviewTheme?.unreadCounterColor, - ), - ); - return theme; + return defaultTheme.merge(themeData) ?? themeData; } /// The current user diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index 246459c6..8640467c 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -45,6 +45,9 @@ class StreamChatThemeData { /// The text themes used in the widgets final TextTheme textTheme; + /// The button themes used in the widgets + final ButtonThemeData buttonTheme; + /// The text themes used in the widgets final ColorTheme colorTheme; @@ -73,8 +76,9 @@ class StreamChatThemeData { final List reactionIcons; /// Create a theme from scratch - StreamChatThemeData({ + const StreamChatThemeData({ this.textTheme, + this.buttonTheme, this.colorTheme, this.channelPreviewTheme, this.channelTheme, @@ -89,32 +93,24 @@ class StreamChatThemeData { /// Create a theme from a Material [Theme] factory StreamChatThemeData.fromTheme(ThemeData theme) { final defaultTheme = getDefaultTheme(theme); - - return defaultTheme.copyWith( + final customizedTheme = StreamChatThemeData( primaryIconTheme: theme.primaryIconTheme, - channelTheme: defaultTheme.channelTheme, - ownMessageTheme: defaultTheme.ownMessageTheme.copyWith( - replies: defaultTheme.ownMessageTheme.replies.copyWith( - color: theme.accentColor, - ), - messageLinks: TextStyle( - color: theme.accentColor, - ), + ownMessageTheme: MessageTheme( + replies: TextStyle(color: theme.accentColor), + messageLinks: TextStyle(color: theme.accentColor), ), - otherMessageTheme: defaultTheme.otherMessageTheme.copyWith( - replies: defaultTheme.otherMessageTheme.replies.copyWith( - color: theme.accentColor, - ), - messageLinks: TextStyle( - color: theme.accentColor, - ), + otherMessageTheme: MessageTheme( + replies: TextStyle(color: theme.accentColor), + messageLinks: TextStyle(color: theme.accentColor), ), ); + return defaultTheme.merge(customizedTheme) ?? customizedTheme; } /// Creates a copy of [StreamChatThemeData] with specified attributes overridden. StreamChatThemeData copyWith({ TextTheme textTheme, + ButtonThemeData buttonTheme, ColorTheme colorTheme, ChannelPreviewTheme channelPreviewTheme, ChannelTheme channelTheme, @@ -126,88 +122,41 @@ class StreamChatThemeData { List reactionIcons, }) => StreamChatThemeData( - textTheme: this.textTheme?.copyWith( - title: textTheme?.title, - body: textTheme?.body, - bodyBold: textTheme?.bodyBold, - captionBold: textTheme?.captionBold, - footnote: textTheme?.footnote, - footnoteBold: textTheme?.footnoteBold, - headline: textTheme?.headline, - headlineBold: textTheme?.headlineBold, - ), - colorTheme: this.colorTheme?.copyWith( - black: colorTheme?.black, - grey: colorTheme?.grey, - greyGainsboro: colorTheme?.greyGainsboro, - greyWhisper: colorTheme?.greyWhisper, - whiteSmoke: colorTheme?.whiteSmoke, - whiteSnow: colorTheme?.whiteSnow, - white: colorTheme?.white, - blueAlice: colorTheme?.blueAlice, - ), + textTheme: textTheme ?? this.textTheme, + buttonTheme: buttonTheme ?? this.buttonTheme, + colorTheme: colorTheme ?? this.colorTheme, primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme, defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage, defaultUserImage: defaultUserImage ?? this.defaultUserImage, - channelPreviewTheme: this.channelPreviewTheme?.copyWith( - title: channelPreviewTheme.title, - subtitle: channelPreviewTheme.subtitle, - lastMessageAt: channelPreviewTheme.lastMessageAt, - avatarTheme: channelPreviewTheme.avatarTheme, - ), - channelTheme: channelTheme?.copyWith( - channelHeaderTheme: channelTheme.channelHeaderTheme ?? - this.channelTheme.channelHeaderTheme, - messageInputButtonIconTheme: - channelTheme.messageInputButtonIconTheme ?? - this.channelTheme.messageInputButtonIconTheme, - messageInputButtonTheme: channelTheme.messageInputButtonTheme ?? - this.channelTheme.messageInputButtonTheme, - inputBackground: channelTheme.inputBackground ?? - this.channelTheme.inputBackground, - ) ?? - this.channelTheme, - ownMessageTheme: ownMessageTheme?.copyWith( - messageText: ownMessageTheme?.messageText ?? - this.ownMessageTheme.messageText, - messageAuthor: ownMessageTheme?.messageAuthor ?? - this.ownMessageTheme.messageAuthor, - messageLinks: ownMessageTheme?.messageLinks ?? - this.ownMessageTheme.messageLinks, - createdAt: - ownMessageTheme?.createdAt ?? this.ownMessageTheme.createdAt, - replies: ownMessageTheme?.replies ?? this.ownMessageTheme.replies, - messageBackgroundColor: ownMessageTheme?.messageBackgroundColor ?? - this.ownMessageTheme.messageBackgroundColor, - avatarTheme: ownMessageTheme?.avatarTheme ?? - this.ownMessageTheme.avatarTheme, - messageBorderColor: ownMessageTheme?.messageBorderColor ?? - this.ownMessageTheme.messageBorderColor, - ) ?? - this.ownMessageTheme, - otherMessageTheme: otherMessageTheme?.copyWith( - messageText: otherMessageTheme?.messageText ?? - this.otherMessageTheme.messageText, - messageAuthor: otherMessageTheme?.messageAuthor ?? - this.otherMessageTheme.messageAuthor, - messageLinks: otherMessageTheme?.messageLinks ?? - this.otherMessageTheme.messageLinks, - createdAt: otherMessageTheme?.createdAt ?? - this.otherMessageTheme.createdAt, - replies: - otherMessageTheme?.replies ?? this.otherMessageTheme.replies, - messageBackgroundColor: - otherMessageTheme?.messageBackgroundColor ?? - this.otherMessageTheme.messageBackgroundColor, - messageBorderColor: otherMessageTheme?.messageBorderColor ?? - this.otherMessageTheme.messageBorderColor, - avatarTheme: otherMessageTheme?.avatarTheme ?? - this.otherMessageTheme.avatarTheme, - ) ?? - this.otherMessageTheme, + channelPreviewTheme: channelPreviewTheme ?? this.channelPreviewTheme, + channelTheme: channelTheme ?? this.channelTheme, + ownMessageTheme: ownMessageTheme ?? this.ownMessageTheme, + otherMessageTheme: otherMessageTheme ?? this.otherMessageTheme, reactionIcons: reactionIcons ?? this.reactionIcons, ); + StreamChatThemeData merge(StreamChatThemeData other) { + if (other == null) return this; + return copyWith( + textTheme: textTheme?.merge(other.textTheme) ?? other.textTheme, + buttonTheme: other.buttonTheme, + colorTheme: colorTheme?.merge(other.colorTheme) ?? other.colorTheme, + primaryIconTheme: other.primaryIconTheme, + defaultChannelImage: other.defaultChannelImage, + defaultUserImage: other.defaultUserImage, + channelPreviewTheme: + channelPreviewTheme?.merge(other.channelPreviewTheme) ?? + other.channelPreviewTheme, + channelTheme: + channelTheme?.merge(other.channelTheme) ?? other.channelTheme, + ownMessageTheme: ownMessageTheme?.merge(other.ownMessageTheme) ?? + other.ownMessageTheme, + otherMessageTheme: otherMessageTheme?.merge(other.otherMessageTheme) ?? + other.otherMessageTheme, + reactionIcons: other.reactionIcons, + ); + } + /// Get the default Stream Chat theme static StreamChatThemeData getDefaultTheme(ThemeData theme) { final accentColor = Color(0xff006cff); @@ -217,6 +166,17 @@ class StreamChatThemeData { return StreamChatThemeData( textTheme: textTheme, colorTheme: colorTheme, + buttonTheme: ButtonThemeData( + height: 48.0, + buttonColor: isDark ? Color(0xffffffff) : Color(0xff006aff), + textTheme: ButtonTextTheme.accent, + colorScheme: theme.colorScheme.copyWith( + secondary: isDark ? Color(0xff005eff) : Color(0xffffffff), + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26), + ), + ), primaryIconTheme: IconThemeData(color: colorTheme.black.withOpacity(.5)), defaultChannelImage: (context, channel) => SizedBox(), defaultUserImage: (context, user) => Center( @@ -483,6 +443,22 @@ class TextTheme { captionBold: captionBold ?? this.captionBold, ); } + + TextTheme merge(TextTheme other) { + if (other == null) return this; + return copyWith( + body: body?.merge(other.body) ?? other.body, + title: title?.merge(other.title) ?? other.title, + headlineBold: + headlineBold?.merge(other.headlineBold) ?? other.headlineBold, + headline: headline?.merge(other.headline) ?? other.headline, + bodyBold: bodyBold?.merge(other.bodyBold) ?? other.bodyBold, + footnoteBold: + footnoteBold?.merge(other.footnoteBold) ?? other.footnoteBold, + footnote: footnote?.merge(other.footnote) ?? other.footnote, + captionBold: captionBold?.merge(other.captionBold) ?? other.captionBold, + ); + } } enum ColorThemeType { @@ -502,6 +478,10 @@ class ColorTheme { final Color accentBlue; final Color accentRed; final Color accentGreen; + final Color highlight; + final Color overlay; + final Color overlayDark; + final Gradient bgGradient; ColorTheme.light({ this.black = const Color(0xff000000), @@ -513,8 +493,17 @@ class ColorTheme { this.white = const Color(0xffffffff), this.blueAlice = const Color(0xffe9f2ff), this.accentBlue = const Color(0xff005FFF), - this.accentRed = const Color(0xffFF3742), + this.accentRed = const Color(0xffFF3842), this.accentGreen = const Color(0xff20E070), + this.highlight = const Color(0xfffbf4dd), + this.overlay = const Color.fromRGBO(0, 0, 0, 0.2), + this.overlayDark = const Color.fromRGBO(0, 0, 0, 0.6), + this.bgGradient = const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [const Color(0xfff7f7f7), const Color(0xfffcfcfc)], + stops: [0, 1], + ), }); ColorTheme.dark({ @@ -529,6 +518,15 @@ class ColorTheme { this.accentBlue = const Color(0xff005FFF), this.accentRed = const Color(0xffFF3742), this.accentGreen = const Color(0xff20E070), + this.highlight = const Color(0xff302d22), + this.overlay = const Color.fromRGBO(0, 0, 0, 0.4), + this.overlayDark = const Color.fromRGBO(255, 255, 255, 0.6), + this.bgGradient = const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [const Color(0xff101214), const Color(0xff070a0d)], + stops: [0, 1], + ), }); ColorTheme copyWith({ @@ -544,6 +542,10 @@ class ColorTheme { Color accentBlue, Color accentRed, Color accentGreen, + Color highlight, + Color overlay, + Color overlayDark, + Gradient bgGradient, }) { return type == ColorThemeType.light ? ColorTheme.light( @@ -558,6 +560,10 @@ class ColorTheme { accentBlue: accentBlue ?? this.accentBlue, accentRed: accentRed ?? this.accentRed, accentGreen: accentGreen ?? this.accentGreen, + highlight: highlight ?? this.highlight, + overlay: overlay ?? this.overlay, + overlayDark: overlayDark ?? this.overlayDark, + bgGradient: bgGradient ?? this.bgGradient, ) : ColorTheme.dark( black: black ?? this.black, @@ -571,8 +577,33 @@ class ColorTheme { accentBlue: accentBlue ?? this.accentBlue, accentRed: accentRed ?? this.accentRed, accentGreen: accentGreen ?? this.accentGreen, + highlight: highlight ?? this.highlight, + overlay: overlay ?? this.overlay, + overlayDark: overlayDark ?? this.overlayDark, + bgGradient: bgGradient ?? this.bgGradient, ); } + + ColorTheme merge(ColorTheme other) { + if (other == null) return this; + return copyWith( + black: other.black, + grey: other.grey, + greyGainsboro: other.greyGainsboro, + greyWhisper: other.greyWhisper, + whiteSmoke: other.whiteSmoke, + whiteSnow: other.whiteSnow, + white: other.white, + blueAlice: other.blueAlice, + accentBlue: other.accentBlue, + accentRed: other.accentRed, + accentGreen: other.accentGreen, + highlight: other.highlight, + overlay: other.overlay, + overlayDark: other.overlayDark, + bgGradient: other.bgGradient, + ); + } } /// Channel theme data @@ -604,20 +635,26 @@ class ChannelTheme { Color inputBackground, }) => ChannelTheme( - channelHeaderTheme: channelHeaderTheme?.copyWith( - title: channelHeaderTheme?.title ?? this.channelHeaderTheme.title, - lastMessageAt: channelHeaderTheme?.lastMessageAt ?? - this.channelHeaderTheme.lastMessageAt, - avatarTheme: channelHeaderTheme?.avatarTheme ?? - this.channelHeaderTheme.avatarTheme, - color: channelHeaderTheme?.color ?? this.channelHeaderTheme.color, - ) ?? - this.channelHeaderTheme, + channelHeaderTheme: channelHeaderTheme ?? this.channelHeaderTheme, messageInputButtonIconTheme: messageInputButtonIconTheme ?? this.messageInputButtonIconTheme, messageInputButtonTheme: messageInputButtonTheme ?? this.messageInputButtonTheme, + inputBackground: inputBackground ?? this.inputBackground, ); + + ChannelTheme merge(ChannelTheme other) { + if (other == null) return this; + return copyWith( + channelHeaderTheme: channelHeaderTheme?.merge(other.channelHeaderTheme) ?? + other.channelHeaderTheme, + messageInputButtonIconTheme: messageInputButtonIconTheme + ?.merge(other.messageInputButtonIconTheme) ?? + other.messageInputButtonIconTheme, + messageInputButtonTheme: other.messageInputButtonTheme, + inputBackground: other.inputBackground, + ); + } } class AvatarTheme { @@ -637,6 +674,14 @@ class AvatarTheme { constraints: constraints ?? this.constraints, borderRadius: borderRadius ?? this.borderRadius, ); + + AvatarTheme merge(AvatarTheme other) { + if (other == null) return this; + return copyWith( + constraints: other.constraints, + borderRadius: other.borderRadius, + ); + } } class MessageTheme { @@ -690,6 +735,24 @@ class MessageTheme { reactionsBackgroundColor ?? this.reactionsBackgroundColor, reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor, ); + + MessageTheme merge(MessageTheme other) { + if (other == null) return this; + return copyWith( + messageText: messageText?.merge(other.messageText) ?? other.messageText, + messageAuthor: + messageAuthor?.merge(other.messageAuthor) ?? other.messageAuthor, + messageLinks: + messageLinks?.merge(other.messageLinks) ?? other.messageLinks, + createdAt: createdAt?.merge(other.createdAt) ?? other.createdAt, + replies: replies?.merge(other.replies) ?? other.replies, + messageBackgroundColor: other.messageBackgroundColor, + messageBorderColor: other.messageBorderColor, + avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, + reactionsBackgroundColor: other.reactionsBackgroundColor, + reactionsBorderColor: other.reactionsBorderColor, + ); + } } class ChannelPreviewTheme { @@ -721,6 +784,18 @@ class ChannelPreviewTheme { avatarTheme: avatarTheme ?? this.avatarTheme, unreadCounterColor: unreadCounterColor ?? this.unreadCounterColor, ); + + ChannelPreviewTheme merge(ChannelPreviewTheme other) { + if (other == null) return this; + return copyWith( + title: title?.merge(other.title) ?? other.title, + subtitle: subtitle?.merge(other.subtitle) ?? other.subtitle, + lastMessageAt: + lastMessageAt?.merge(other.lastMessageAt) ?? other.lastMessageAt, + avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, + unreadCounterColor: other.unreadCounterColor, + ); + } } class ChannelHeaderTheme { @@ -748,4 +823,15 @@ class ChannelHeaderTheme { avatarTheme: avatarTheme ?? this.avatarTheme, color: color ?? this.color, ); + + ChannelHeaderTheme merge(ChannelHeaderTheme other) { + if (other == null) return this; + return copyWith( + title: title?.merge(other.title) ?? other.title, + lastMessageAt: + lastMessageAt?.merge(other.lastMessageAt) ?? other.lastMessageAt, + avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, + color: other.color, + ); + } } diff --git a/lib/src/stream_svg_icon.dart b/lib/src/stream_svg_icon.dart index e7ef46c3..edf1a697 100644 --- a/lib/src/stream_svg_icon.dart +++ b/lib/src/stream_svg_icon.dart @@ -194,6 +194,18 @@ class StreamSvgIcon extends StatelessWidget { ); } + factory StreamSvgIcon.checkSend({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_check_send.svg', + color: color, + width: size, + height: size, + ); + } + factory StreamSvgIcon.penWrite({ double size, Color color, diff --git a/lib/src/user_item.dart b/lib/src/user_item.dart index 50a8b7a7..58a354fc 100644 --- a/lib/src/user_item.dart +++ b/lib/src/user_item.dart @@ -73,14 +73,8 @@ class UserItem extends StatelessWidget { ), ), trailing: selected - ? CircleAvatar( - backgroundColor: - StreamChatTheme.of(context).colorTheme.accentBlue, - child: StreamSvgIcon.check( - size: 20, - color: StreamChatTheme.of(context).colorTheme.white, - ), - radius: 10, + ? StreamSvgIcon.checkSend( + color: StreamChatTheme.of(context).colorTheme.accentBlue, ) : null, title: Text( diff --git a/lib/src/user_list_view.dart b/lib/src/user_list_view.dart index 35692841..ef51ad92 100644 --- a/lib/src/user_list_view.dart +++ b/lib/src/user_list_view.dart @@ -363,12 +363,10 @@ class _UserListViewState extends State child: Text( header, style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14.5, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.3)), + fontWeight: FontWeight.bold, + fontSize: 14.5, + color: StreamChatTheme.of(context).colorTheme.grey, + ), ), ), ); @@ -478,9 +476,7 @@ class _UserListViewState extends State Widget _separatorBuilder(context, i) { return Container( height: 1, - color: Theme.of(context).brightness == Brightness.dark - ? StreamChatTheme.of(context).colorTheme.white.withOpacity(0.1) - : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.1), + color: StreamChatTheme.of(context).colorTheme.greyWhisper, ); }