diff --git a/analysis_options.yaml b/analysis_options.yaml index 8d6f9064..42965ec7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,63 +1,63 @@ -include: package:pedantic/analysis_options.yaml - -analyzer: - enable-experiment: - - extension-methods - exclude: - - lib/**/*.g.dart - - example/** - -linter: - rules: - # these rules are documented on and in the same order as - # the Dart Lint rules page to make maintenance easier - # https://github.com/dart-lang/linter/blob/master/example/all.yaml - # - always_declare_return_types - # - always_specify_types - # - annotate_overrides - # - avoid_as - - avoid_empty_else - - avoid_init_to_null - - avoid_return_types_on_setters - - avoid_web_libraries_in_flutter - - await_only_futures - - camel_case_types - - cancel_subscriptions - - close_sinks - # - comment_references # we do not presume as to what people want to reference in their dartdocs - # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 - - control_flow_in_finally - - empty_constructor_bodies - - empty_statements - - hash_and_equals - - implementation_imports - # - invariant_booleans - # - iterable_contains_unrelated_type - - library_names - # - library_prefixes - # - list_remove_unrelated_type - # - literal_only_boolean_expressions - - non_constant_identifier_names - # - one_member_abstracts - # - only_throw_errors - # - overridden_fields -# - package_api_docs - - package_names - - package_prefixed_library_names - - prefer_is_not_empty - # - prefer_mixin # https://github.com/dart-lang/language/issues/32 - - public_member_api_docs - - slash_for_doc_comments - # - sort_constructors_first - # - sort_unnamed_constructors_first - # - super_goes_last # no longer needed w/ Dart 2 - - test_types_in_equals - - throw_in_finally - # - type_annotate_public_apis # subset of always_specify_types - - type_init_formals - # - unawaited_futures - - unnecessary_brace_in_string_interps - - unnecessary_getters_setters - - unnecessary_statements +#include: package:pedantic/analysis_options.yaml +# +#analyzer: +# enable-experiment: +# - extension-methods +# exclude: +# - lib/**/*.g.dart +# - example/** +# +#linter: +# rules: +# # these rules are documented on and in the same order as +# # the Dart Lint rules page to make maintenance easier +# # https://github.com/dart-lang/linter/blob/master/example/all.yaml +# # - always_declare_return_types +# # - always_specify_types +# # - annotate_overrides +# # - avoid_as +# - avoid_empty_else +# - avoid_init_to_null +# - avoid_return_types_on_setters +# - avoid_web_libraries_in_flutter +# - await_only_futures +# - camel_case_types +# - cancel_subscriptions +# - close_sinks +# # - comment_references # we do not presume as to what people want to reference in their dartdocs +# # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 +# - control_flow_in_finally +# - empty_constructor_bodies +# - empty_statements +# - hash_and_equals +# - implementation_imports +# # - invariant_booleans +# # - iterable_contains_unrelated_type +# - library_names +# # - library_prefixes +# # - list_remove_unrelated_type +# # - literal_only_boolean_expressions +# - non_constant_identifier_names +# # - one_member_abstracts +# # - only_throw_errors +# # - overridden_fields +## - package_api_docs +# - package_names +# - package_prefixed_library_names +# - prefer_is_not_empty +# # - prefer_mixin # https://github.com/dart-lang/language/issues/32 +# - public_member_api_docs +# - slash_for_doc_comments +# # - sort_constructors_first +# # - sort_unnamed_constructors_first +# # - super_goes_last # no longer needed w/ Dart 2 +# - test_types_in_equals +# - throw_in_finally +# # - type_annotate_public_apis # subset of always_specify_types +# - type_init_formals +# # - unawaited_futures +# - unnecessary_brace_in_string_interps +# - unnecessary_getters_setters +# - unnecessary_statements - unrelated_type_equality_checks - valid_regexps diff --git a/example/ios/Flutter/.last_build_id b/example/ios/Flutter/.last_build_id index 20c7c514..f57e1e1f 100644 --- a/example/ios/Flutter/.last_build_id +++ b/example/ios/Flutter/.last_build_id @@ -1 +1 @@ -c3e639ccf9b069e37a7d1345194e6b99 \ No newline at end of file +c770358a10272b4ee1cdc2eb432445f6 \ No newline at end of file diff --git a/example/lib/advanced_options_page.dart b/example/lib/advanced_options_page.dart index e07afefd..483e0902 100644 --- a/example/lib/advanced_options_page.dart +++ b/example/lib/advanced_options_page.dart @@ -3,12 +3,12 @@ import 'dart:io'; import 'package:example/stream_version.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 'choose_user_page.dart'; import 'main.dart'; import 'notifications_service.dart'; -import 'choose_user_page.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class AdvancedOptionsPage extends StatefulWidget { @override @@ -34,20 +34,23 @@ class _AdvancedOptionsPageState extends State { @override Widget build(BuildContext context) { return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, resizeToAvoidBottomPadding: false, appBar: AppBar( - backgroundColor: StreamChatTheme.of(context).primaryColor, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, elevation: 1, centerTitle: true, + brightness: Theme.of(context).brightness, title: Text( 'Advanced Options', - style: Theme.of(context).textTheme.subtitle1.copyWith( - fontWeight: FontWeight.bold, - ), + style: StreamChatTheme.of(context) + .textTheme + .headlineBold + .copyWith(color: StreamChatTheme.of(context).colorTheme.black), ), leading: IconButton( icon: StreamSvgIcon.left( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), onPressed: () { Navigator.pop(context); @@ -87,7 +90,7 @@ class _AdvancedOptionsPageState extends State { }, style: TextStyle( fontSize: 14, - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), decoration: InputDecoration( errorStyle: TextStyle(fontSize: 0), @@ -95,14 +98,18 @@ class _AdvancedOptionsPageState extends State { fontSize: 14, fontWeight: FontWeight.bold, color: _apiKeyError != null - ? Color(0xffff3742) - : Colors.black.withOpacity(.5), + ? StreamChatTheme.of(context).colorTheme.accentRed + : StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), - fillColor: Color(0xffF5F5F5), + fillColor: + StreamChatTheme.of(context).colorTheme.whiteSmoke, filled: true, labelText: 'Chat API Key ${_apiKeyError != null ? ':$_apiKeyError' : ''}', @@ -131,7 +138,7 @@ class _AdvancedOptionsPageState extends State { }, style: TextStyle( fontSize: 14, - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), textInputAction: TextInputAction.next, decoration: InputDecoration( @@ -140,14 +147,18 @@ class _AdvancedOptionsPageState extends State { fontWeight: FontWeight.bold, fontSize: 14, color: _userIdError != null - ? Color(0xffff3742) - : Colors.black.withOpacity(.5), + ? StreamChatTheme.of(context).colorTheme.accentRed + : StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), - fillColor: Color(0xffF5F5F5), + fillColor: + StreamChatTheme.of(context).colorTheme.whiteSmoke, filled: true, labelText: 'User ID ${_userIdError != null ? ':$_userIdError' : ''}', @@ -176,7 +187,7 @@ class _AdvancedOptionsPageState extends State { }, style: TextStyle( fontSize: 14, - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), textInputAction: TextInputAction.next, decoration: InputDecoration( @@ -185,14 +196,18 @@ class _AdvancedOptionsPageState extends State { fontWeight: FontWeight.bold, fontSize: 14, color: _userTokenError != null - ? Color(0xffff3742) - : Colors.black.withOpacity(.5), + ? StreamChatTheme.of(context).colorTheme.accentRed + : StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), - fillColor: Color(0xffF5F5F5), + fillColor: + StreamChatTheme.of(context).colorTheme.whiteSmoke, filled: true, labelText: 'User Token ${_userTokenError != null ? ':$_userTokenError' : ''}', @@ -208,13 +223,17 @@ class _AdvancedOptionsPageState extends State { labelStyle: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, - color: Colors.black.withOpacity(.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), - fillColor: Color(0xffF5F5F5), + fillColor: + StreamChatTheme.of(context).colorTheme.whiteSmoke, filled: true, labelText: 'Username (optional)', ), @@ -224,13 +243,14 @@ class _AdvancedOptionsPageState extends State { child: Align( alignment: Alignment.bottomCenter, child: FlatButton( - color: StreamChatTheme.of(context).accentColor, + color: + StreamChatTheme.of(context).colorTheme.accentBlue, minWidth: double.infinity, height: 48, child: Text( 'Login', style: TextStyle( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, fontSize: 16, ), ), @@ -255,7 +275,9 @@ class _AdvancedOptionsPageState extends State { child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), - color: Colors.white, + color: StreamChatTheme.of(context) + .colorTheme + .white, ), height: 100, width: 100, diff --git a/example/lib/chips_input_text_field.dart b/example/lib/chips_input_text_field.dart index 2ef308f8..7291ef03 100644 --- a/example/lib/chips_input_text_field.dart +++ b/example/lib/chips_input_text_field.dart @@ -66,7 +66,7 @@ class ChipInputTextFieldState extends State> { onTap: _pauseItemAddition ? resumeItemAddition : null, child: Material( elevation: 1, - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, child: Container( child: Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 16), @@ -78,10 +78,14 @@ class ChipInputTextFieldState extends State> { padding: const EdgeInsets.symmetric(vertical: 4.0), child: Text( 'TO:', - style: TextStyle( - fontSize: 12, - color: Colors.black.withOpacity(0.5), - ), + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), ), ), SizedBox(width: 12), @@ -111,10 +115,14 @@ class ChipInputTextFieldState extends State> { disabledBorder: InputBorder.none, contentPadding: const EdgeInsets.only(top: 4.0), hintText: widget.hint, - hintStyle: TextStyle( - color: Colors.black.withOpacity(0.5), - fontSize: 14, - ), + hintStyle: StreamChatTheme.of(context) + .textTheme + .body + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), ), ), ], @@ -126,11 +134,17 @@ class ChipInputTextFieldState extends State> { child: IconButton( icon: _chips.isEmpty ? StreamSvgIcon.user( - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), size: 24, ) : StreamSvgIcon.userAdd( - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), size: 24, ), onPressed: resumeItemAddition, diff --git a/example/lib/choose_user_page.dart b/example/lib/choose_user_page.dart index 22582be9..ca28b1b3 100644 --- a/example/lib/choose_user_page.dart +++ b/example/lib/choose_user_page.dart @@ -1,5 +1,3 @@ -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'; @@ -60,6 +58,7 @@ class ChooseUserPage extends StatelessWidget { (_, value) => value..extraData['image'] = getRandomPicUrl(value)); return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, body: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -80,19 +79,12 @@ class ChooseUserPage extends StatelessWidget { padding: const EdgeInsets.only(bottom: 13.0), child: Text( 'Welcome to Stream Chat', - style: TextStyle( - fontSize: 22, - color: Colors.black, - fontWeight: FontWeight.bold, - ), + style: StreamChatTheme.of(context).textTheme.title, ), ), Text( 'Select a user to try the Flutter SDK:', - style: TextStyle( - fontSize: 14.5, - color: Colors.black, - ), + style: StreamChatTheme.of(context).textTheme.body, ), Expanded( child: Padding( @@ -120,7 +112,9 @@ class ChooseUserPage extends StatelessWidget { child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), - color: Colors.white, + color: StreamChatTheme.of(context) + .colorTheme + .white, ), height: 100, width: 100, @@ -169,9 +163,20 @@ class ChooseUserPage extends StatelessWidget { ), title: Text( user.name, - style: TextStyle(fontWeight: FontWeight.bold), + style: + StreamChatTheme.of(context).textTheme.bodyBold, + ), + subtitle: Text( + 'Stream test account', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .grey, + ), ), - subtitle: Text('Stream test account'), trailing: SvgPicture.asset( 'assets/icon_arrow_right.svg', height: 24, @@ -185,16 +190,26 @@ class ChooseUserPage extends StatelessWidget { }, leading: CircleAvatar( child: StreamSvgIcon.settings( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), - backgroundColor: - StreamChatTheme.of(context).secondaryColor, + backgroundColor: StreamChatTheme.of(context) + .colorTheme + .greyWhisper, ), title: Text( 'Advanced Options', - style: TextStyle(fontWeight: FontWeight.bold), + style: StreamChatTheme.of(context).textTheme.bodyBold, + ), + subtitle: Text( + 'Custom settings', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: + StreamChatTheme.of(context).colorTheme.grey, + ), ), - subtitle: Text('Custom settings'), trailing: SvgPicture.asset( 'assets/icon_arrow_right.svg', height: 24, diff --git a/example/lib/custom_theme.dart b/example/lib/custom_theme.dart index 21db1c6c..90635993 100644 --- a/example/lib/custom_theme.dart +++ b/example/lib/custom_theme.dart @@ -50,9 +50,10 @@ class MyApp extends StatelessWidget { client: client, streamChatThemeData: StreamChatThemeData.fromTheme(theme).copyWith( ownMessageTheme: MessageTheme( - messageBackgroundColor: Colors.black, + messageBackgroundColor: + StreamChatTheme.of(context).colorTheme.black, messageText: TextStyle( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, ), avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(8), diff --git a/example/lib/customize_channel_preview.dart b/example/lib/customize_channel_preview.dart index 3809f780..29d22a82 100644 --- a/example/lib/customize_channel_preview.dart +++ b/example/lib/customize_channel_preview.dart @@ -84,10 +84,12 @@ class ChannelListPage extends StatelessWidget { channel: channel, ), title: ChannelName( - channel: channel, textStyle: StreamChatTheme.of(context).channelPreviewTheme.title.copyWith( - color: Colors.black.withOpacity(opacity), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(opacity), ), ), subtitle: Text(subtitle), diff --git a/example/lib/customize_message_widget.dart b/example/lib/customize_message_widget.dart index fc614125..a3713b69 100644 --- a/example/lib/customize_message_widget.dart +++ b/example/lib/customize_message_widget.dart @@ -127,7 +127,6 @@ class ChannelPage extends StatelessWidget { showTimestamp: !details.isNextUser, showUsername: !details.isNextUser, showReactions: false, - showReplyIndicator: false, ); } } diff --git a/example/lib/group_chat_details_screen.dart b/example/lib/group_chat_details_screen.dart index c6d4331d..03b4877b 100644 --- a/example/lib/group_chat_details_screen.dart +++ b/example/lib/group_chat_details_screen.dart @@ -59,15 +59,16 @@ class _GroupChatDetailsScreenState extends State { return false; }, child: Scaffold( - backgroundColor: Color.fromRGBO(252, 252, 252, 1), + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, appBar: AppBar( + brightness: Theme.of(context).brightness, elevation: 1, - backgroundColor: Colors.white, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, leading: const StreamBackButton(), title: Text( 'Name of Group Chat', style: TextStyle( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, fontSize: 16, ), ), @@ -82,7 +83,10 @@ class _GroupChatDetailsScreenState extends State { 'NAME', style: TextStyle( fontSize: 12, - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), ), SizedBox(width: 16), @@ -99,7 +103,11 @@ class _GroupChatDetailsScreenState extends State { contentPadding: const EdgeInsets.all(0), hintText: 'Choose a group chat name', hintStyle: TextStyle( - fontSize: 14, color: Colors.black.withOpacity(.5)), + fontSize: 14, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), ), ), ), @@ -113,7 +121,9 @@ class _GroupChatDetailsScreenState extends State { padding: const EdgeInsets.all(0), icon: StreamSvgIcon.check( size: 24, - color: _isGroupNameEmpty ? Colors.grey : Color(0xFF006CFF), + color: _isGroupNameEmpty + ? Colors.grey + : StreamChatTheme.of(context).colorTheme.accentBlue, ), onPressed: _isGroupNameEmpty ? null @@ -144,7 +154,16 @@ class _GroupChatDetailsScreenState extends State { children: [ Container( width: double.maxFinite, - color: Colors.grey.shade50, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + StreamChatTheme.of(context).colorTheme.whiteSmoke, + StreamChatTheme.of(context).colorTheme.whiteSnow, + ], + ), + ), child: Padding( padding: const EdgeInsets.symmetric( vertical: 8, @@ -167,16 +186,28 @@ class _GroupChatDetailsScreenState extends State { separatorBuilder: (_, __) => Container( height: 1, color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(0.1) - : Colors.black.withOpacity(0.1), + ? StreamChatTheme.of(context) + .colorTheme + .white + .withOpacity(0.1) + : StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.1), ), itemBuilder: (_, index) { if (index == _selectedUsers.length) { return Container( height: 1, color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(0.1) - : Colors.black.withOpacity(0.1), + ? StreamChatTheme.of(context) + .colorTheme + .white + .withOpacity(0.1) + : StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.1), ); } final user = _selectedUsers[index]; @@ -200,7 +231,7 @@ class _GroupChatDetailsScreenState extends State { trailing: IconButton( icon: Icon( Icons.clear_rounded, - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), padding: const EdgeInsets.all(0), splashRadius: 24, diff --git a/example/lib/main.dart b/example/lib/main.dart index 7ef4a7d7..22baea68 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,8 +1,7 @@ +import 'dart:async'; import 'dart:io'; import 'package:example/choose_user_page.dart'; -import 'package:example/new_chat_screen.dart'; -import 'package:example/new_group_chat_screen.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -13,7 +12,6 @@ import 'notifications_service.dart'; import 'routes/app_routes.dart'; import 'routes/routes.dart'; import 'search_text_field.dart'; -import 'dart:async'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -28,7 +26,7 @@ void main() async { logLevel: Level.INFO, showLocalNotification: (!kIsWeb && Platform.isAndroid) ? showLocalNotification : null, - persistenceEnabled: true, + persistenceEnabled: false, ); if (userId != null) { @@ -52,18 +50,20 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return StreamChat( - client: client, - child: MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData.light(), - darkTheme: ThemeData.dark(), - //TODO change to system once dark theme is implemented - themeMode: ThemeMode.light, - onGenerateRoute: AppRoutes.generateRoute, - initialRoute: - client.state.user == null ? Routes.CHOOSE_USER : Routes.HOME, - ), + return MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + child: child, + ); + }, + debugShowCheckedModeBanner: false, + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + themeMode: ThemeMode.dark, + onGenerateRoute: AppRoutes.generateRoute, + initialRoute: + client.state.user == null ? Routes.CHOOSE_USER : Routes.HOME, ); } } @@ -85,7 +85,9 @@ class _HomePageState extends State { overflow: Overflow.visible, children: [ StreamSvgIcon.message( - color: _isSelected(0) ? Colors.black : Colors.grey, + color: _isSelected(0) + ? StreamChatTheme.of(context).colorTheme.black + : Colors.grey, ), Positioned( top: -3, @@ -101,7 +103,9 @@ class _HomePageState extends State { overflow: Overflow.visible, children: [ StreamSvgIcon.mentions( - color: _isSelected(1) ? Colors.black : Colors.grey, + color: _isSelected(1) + ? StreamChatTheme.of(context).colorTheme.black + : Colors.grey, ), ], ), @@ -114,6 +118,7 @@ class _HomePageState extends State { Widget build(BuildContext context) { final user = StreamChat.of(context).user; return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, appBar: ChannelListHeader( onNewChatButtonTap: () { Navigator.pushNamed(context, Routes.NEW_CHAT); @@ -122,10 +127,14 @@ class _HomePageState extends State { drawer: _buildDrawer(context, user), drawerEdgeDragWidth: 50, bottomNavigationBar: BottomNavigationBar( + backgroundColor: StreamChatTheme.of(context).colorTheme.white, currentIndex: _currentIndex, items: _navBarItems, + selectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold, + unselectedLabelStyle: + StreamChatTheme.of(context).textTheme.footnoteBold, type: BottomNavigationBarType.fixed, - selectedItemColor: Colors.black, + selectedItemColor: StreamChatTheme.of(context).colorTheme.black, unselectedItemColor: Colors.grey, onTap: (index) { setState(() => _currentIndex = index); @@ -143,104 +152,116 @@ class _HomePageState extends State { Drawer _buildDrawer(BuildContext context, User user) { return Drawer( - child: SafeArea( - 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: Container( + color: StreamChatTheme.of(context).colorTheme.white, + child: SafeArea( + 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, + ), + ), + ), + ], + ), ), - child: Row( - children: [ - UserAvatar( - user: user, - showOnlineStatus: false, - constraints: BoxConstraints.tight(Size.fromRadius(20)), + ListTile( + leading: StreamSvgIcon.penWrite( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + onTap: () { + Navigator.popAndPushNamed( + context, + Routes.NEW_CHAT, + ); + }, + title: Text( + 'New direct message', + style: TextStyle( + fontSize: 14.5, ), - Padding( - padding: const EdgeInsets.only(left: 16.0), - child: Text( - user.name, + ), + ), + ListTile( + leading: StreamSvgIcon.contacts( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + onTap: () { + Navigator.popAndPushNamed( + context, + Routes.NEW_GROUP_CHAT, + ); + }, + title: Text( + 'New group', + style: TextStyle( + fontSize: 14.5, + ), + ), + ), + Expanded( + child: Container( + alignment: Alignment.bottomCenter, + child: ListTile( + onTap: () async { + Navigator.pop(context); + + final secureStorage = FlutterSecureStorage(); + await secureStorage.deleteAll(); + + StreamChat.of(context).client.disconnect( + clearUser: true, + ); + + await Navigator.pushReplacementNamed( + context, + Routes.CHOOSE_USER, + ); + }, + leading: StreamSvgIcon.user( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + title: Text( + 'Sign out', style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + fontSize: 14.5, ), ), ), - ], - ), - ), - ListTile( - leading: StreamSvgIcon.penWrite( - color: Colors.black.withOpacity(.5), - ), - onTap: () { - Navigator.popAndPushNamed( - context, - Routes.NEW_CHAT, - ); - }, - title: Text( - 'New direct message', - style: TextStyle( - fontSize: 14.5, ), ), - ), - ListTile( - leading: StreamSvgIcon.contacts( - color: Colors.black.withOpacity(.5), - ), - onTap: () { - Navigator.popAndPushNamed( - context, - Routes.NEW_GROUP_CHAT, - ); - }, - title: Text( - 'New group', - style: TextStyle( - fontSize: 14.5, - ), - ), - ), - Expanded( - child: Container( - alignment: Alignment.bottomCenter, - child: ListTile( - onTap: () async { - Navigator.pop(context); - - final secureStorage = FlutterSecureStorage(); - await secureStorage.deleteAll(); - - StreamChat.of(context).client.disconnect( - clearUser: true, - ); - - await Navigator.pushReplacementNamed( - context, - Routes.CHOOSE_USER, - ); - }, - leading: StreamSvgIcon.user( - color: Colors.black.withOpacity(.5), - ), - title: Text( - 'Sign out', - style: TextStyle( - fontSize: 14.5, - ), - ), - ), - ), - ), - ], + ], + ), ), ), ), @@ -436,7 +457,7 @@ class _ChannelPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Color.fromRGBO(252, 252, 252, 1), + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, appBar: ChannelHeader( showTypingIndicator: false, ), @@ -463,7 +484,10 @@ class _ChannelPageState extends State { right: 0, child: Container( alignment: Alignment.centerLeft, - color: Color(0xffFCFCFC).withOpacity(.9), + color: StreamChatTheme.of(context) + .colorTheme + .whiteSnow + .withOpacity(.9), child: TypingIndicator( alignment: Alignment.centerLeft, padding: const EdgeInsets.symmetric( @@ -501,7 +525,7 @@ class ThreadPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Color.fromRGBO(252, 252, 252, 1), + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, appBar: ThreadHeader( parent: parent, ), diff --git a/example/lib/new_chat_screen.dart b/example/lib/new_chat_screen.dart index f29b1483..ddebe848 100644 --- a/example/lib/new_chat_screen.dart +++ b/example/lib/new_chat_screen.dart @@ -5,7 +5,6 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'chips_input_text_field.dart'; import 'main.dart'; -import 'new_group_chat_screen.dart'; import 'routes/routes.dart'; class NewChatScreen extends StatefulWidget { @@ -37,8 +36,6 @@ class _NewChatScreenState extends State { bool _showUserList = true; - bool _channelExisted = false; - void _userNameListener() { if (_debounce?.isActive ?? false) _debounce.cancel(); _debounce = Timer(const Duration(milliseconds: 350), () { @@ -122,17 +119,18 @@ class _NewChatScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Color.fromRGBO(252, 252, 252, 1), + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, appBar: AppBar( + brightness: Theme.of(context).brightness, elevation: 0, - backgroundColor: Colors.white, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, leading: const StreamBackButton(), title: Text( 'New Chat', - style: TextStyle( - color: Colors.black, - fontSize: 16, - ), + style: StreamChatTheme.of(context) + .textTheme + .headlineBold + .copyWith(color: StreamChatTheme.of(context).colorTheme.black), ), centerTitle: true, ), @@ -157,7 +155,10 @@ class _NewChatScreenState extends State { children: [ Container( decoration: BoxDecoration( - color: Colors.black.withOpacity(0.05), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.05), borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.only(left: 24), @@ -165,7 +166,10 @@ class _NewChatScreenState extends State { padding: const EdgeInsets.fromLTRB(8, 4, 12, 4), child: Text( user.name, - style: TextStyle(color: Colors.black), + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black), ), ), ), @@ -182,7 +186,7 @@ class _NewChatScreenState extends State { ), Positioned( child: StreamSvgIcon.close( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, ), ), ], @@ -212,7 +216,9 @@ class _NewChatScreenState extends State { StreamNeumorphicButton( child: Center( child: StreamSvgIcon.contacts( - color: Color(0xFF006CFF), + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, size: 24, ), ), @@ -220,10 +226,7 @@ class _NewChatScreenState extends State { SizedBox(width: 8), Text( 'Create a Group', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), + style: StreamChatTheme.of(context).textTheme.bodyBold, ), ], ), @@ -238,8 +241,14 @@ class _NewChatScreenState extends State { begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ - Colors.black.withOpacity(0.02), - Colors.white.withOpacity(0.05), + StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.02), + StreamChatTheme.of(context) + .colorTheme + .white + .withOpacity(0.05), ], stops: [0, 1], ), @@ -250,13 +259,17 @@ class _NewChatScreenState extends State { horizontal: 8, ), child: Text( - _isSearchActive - ? "Matches for \"$_userNameQuery\"" - : 'On the platform', - style: TextStyle( - color: Colors.black.withOpacity(0.5), - ), - ), + _isSearchActive + ? "Matches for \"$_userNameQuery\"" + : 'On the platform', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5))), ), ), Expanded( @@ -316,7 +329,17 @@ class _NewChatScreenState extends State { ), ), Text( - 'No user matches these keywords...'), + 'No user matches these keywords...', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of( + context) + .colorTheme + .black + .withOpacity(.5)), + ), ], ), ), @@ -340,7 +363,10 @@ class _NewChatScreenState extends State { 'No chats here yet...', style: TextStyle( fontSize: 12, - color: Colors.black.withOpacity(.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), ), ), ); diff --git a/example/lib/new_group_chat_screen.dart b/example/lib/new_group_chat_screen.dart index ff77e44d..505db69c 100644 --- a/example/lib/new_group_chat_screen.dart +++ b/example/lib/new_group_chat_screen.dart @@ -3,9 +3,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'group_chat_details_screen.dart'; -import 'search_text_field.dart'; import 'routes/routes.dart'; +import 'search_text_field.dart'; class NewGroupChatScreen extends StatefulWidget { @override @@ -51,23 +50,24 @@ class _NewGroupChatScreenState extends State { @override Widget build(BuildContext context) { - return SafeArea( - child: Scaffold( - backgroundColor: Color.fromRGBO(252, 252, 252, 1), - body: NestedScrollView( + 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: Colors.white, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, leading: const StreamBackButton(), title: Text( 'Add Group Members', style: TextStyle( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, fontSize: 16, ), ), @@ -76,7 +76,8 @@ class _NewGroupChatScreenState extends State { if (_selectedUsers.isNotEmpty) IconButton( icon: StreamSvgIcon.arrow_right( - color: Color(0xFF006CFF), + color: + StreamChatTheme.of(context).colorTheme.accentBlue, ), onPressed: () async { final updatedList = await Navigator.pushNamed( @@ -137,18 +138,21 @@ class _NewGroupChatScreenState extends State { }, child: Container( decoration: BoxDecoration( - color: Colors.white, + color: StreamChatTheme.of(context) + .colorTheme + .white, shape: BoxShape.circle, border: Border.all( - color: Colors.grey.shade100, + color: StreamChatTheme.of(context) + .colorTheme + .whiteSnow, ), ), - child: Padding( - padding: const EdgeInsets.all(0.0), - child: StreamSvgIcon.close( - color: Colors.black, - size: 24, - ), + child: StreamSvgIcon.close( + color: StreamChatTheme.of(context) + .colorTheme + .black, + size: 24, ), ), ), @@ -180,8 +184,14 @@ class _NewGroupChatScreenState extends State { begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ - Colors.black.withOpacity(0.02), - Colors.white.withOpacity(0.05), + StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.02), + StreamChatTheme.of(context) + .colorTheme + .white + .withOpacity(0.05), ], stops: [0, 1], ), @@ -196,7 +206,10 @@ class _NewGroupChatScreenState extends State { ? 'Matches for \"$_userNameQuery\"' : 'On the platform', style: TextStyle( - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), ), ), @@ -261,7 +274,17 @@ class _NewGroupChatScreenState extends State { color: Colors.grey, ), ), - Text('No user matches these keywords...'), + Text( + 'No user matches these keywords...', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), + ), ], ), ), @@ -292,7 +315,7 @@ class _HeaderDelegate extends SliverPersistentHeaderDelegate { Widget build( BuildContext context, double shrinkOffset, bool overlapsContent) { return Container( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, child: child, ); } diff --git a/example/lib/search_text_field.dart b/example/lib/search_text_field.dart index e32bb88b..ff34e8a9 100644 --- a/example/lib/search_text_field.dart +++ b/example/lib/search_text_field.dart @@ -22,9 +22,9 @@ class SearchTextField extends StatelessWidget { return Container( height: 36, decoration: BoxDecoration( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, border: Border.all( - color: Colors.grey.shade300, + color: StreamChatTheme.of(context).colorTheme.greyWhisper, ), borderRadius: BorderRadius.circular(24), ), @@ -40,6 +40,7 @@ class SearchTextField extends StatelessWidget { controller: controller, onChanged: onChanged, decoration: InputDecoration( + prefixText: ' ', prefixIconConstraints: BoxConstraints.tight(Size(40, 24)), prefixIcon: Padding( padding: const EdgeInsets.only( @@ -47,15 +48,16 @@ class SearchTextField extends StatelessWidget { right: 8, ), child: StreamSvgIcon.search( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, size: 24, ), ), hintText: hintText, - hintStyle: TextStyle( - color: Colors.black.withOpacity(0.5), - fontSize: 14, - ), + hintStyle: StreamChatTheme.of(context).textTheme.body.copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), contentPadding: const EdgeInsets.all(0), border: OutlineInputBorder( borderSide: BorderSide.none, diff --git a/example/lib/stream_version.dart b/example/lib/stream_version.dart index 113e36c0..a3d52ab6 100644 --- a/example/lib/stream_version.dart +++ b/example/lib/stream_version.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:yaml/yaml.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class StreamVersion extends StatelessWidget { const StreamVersion({ @@ -28,7 +29,8 @@ class StreamVersion extends StatelessWidget { 'Stream SDK v ${streamChatDep}', style: TextStyle( fontSize: 14.5, - color: Colors.black.withOpacity(.13), + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(.13), ), ); }, diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 175cdaeb..944be796 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.94+97 +version: 1.0.102+105 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/attachment_error.dart b/lib/src/attachment_error.dart index e08867b8..d24423c4 100644 --- a/lib/src/attachment_error.dart +++ b/lib/src/attachment_error.dart @@ -3,6 +3,8 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import '../stream_chat_flutter.dart'; + class AttachmentError extends StatelessWidget { final Attachment attachment; final Size size; @@ -23,12 +25,12 @@ class AttachmentError extends StatelessWidget { return Center( child: Container( width: size?.width, - height: size?.height, - color: Color(0xffd0021B).withOpacity(.1), + height: size?.height ?? 200, + color: StreamChatTheme.of(context).colorTheme.accentRed.withOpacity(.1), child: Center( child: Icon( Icons.error_outline, - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.black, ), ), ), diff --git a/lib/src/attachment_title.dart b/lib/src/attachment_title.dart index ab9cd2da..7ed42a59 100644 --- a/lib/src/attachment_title.dart +++ b/lib/src/attachment_title.dart @@ -32,7 +32,7 @@ class AttachmentTitle extends StatelessWidget { attachment.title, overflow: TextOverflow.ellipsis, style: messageTheme.messageText.copyWith( - color: StreamChatTheme.of(context).accentColor, + color: StreamChatTheme.of(context).colorTheme.accentBlue, fontWeight: FontWeight.bold, ), ), diff --git a/lib/src/back_button.dart b/lib/src/back_button.dart index 10b177d2..52adeade 100644 --- a/lib/src/back_button.dart +++ b/lib/src/back_button.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/unread_indicator.dart'; +import '../stream_chat_flutter.dart'; + class StreamBackButton extends StatelessWidget { const StreamBackButton({ Key key, @@ -35,9 +37,7 @@ class StreamBackButton extends StatelessWidget { }, child: StreamSvgIcon.left( size: 24, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), ), ), diff --git a/lib/src/channel_bottom_sheet.dart b/lib/src/channel_bottom_sheet.dart index 2d16bf6d..f4894b02 100644 --- a/lib/src/channel_bottom_sheet.dart +++ b/lib/src/channel_bottom_sheet.dart @@ -73,7 +73,7 @@ class ChannelBottomSheet extends StatelessWidget { ListTile( leading: StreamSvgIcon.userRemove( size: 24, - color: Color(0xff7A7A7A), + color: StreamChatTheme.of(context).colorTheme.grey, ), title: Text( 'Leave Group', @@ -106,13 +106,13 @@ class ChannelBottomSheet extends StatelessWidget { ?.role)) ListTile( leading: StreamSvgIcon.delete( - color: Color(0xFFFF3742), + color: StreamChatTheme.of(context).colorTheme.accentRed, size: 24, ), title: Text( 'Delete chat', style: TextStyle( - color: Color(0xFFFF3742), + color: StreamChatTheme.of(context).colorTheme.accentRed, ), ), onTap: () async { @@ -124,7 +124,7 @@ class ChannelBottomSheet extends StatelessWidget { 'Are you sure you want to delete this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.delete( - color: Colors.red, + color: StreamChatTheme.of(context).colorTheme.accentRed, ), ); var channel = StreamChannel.of(context).channel; diff --git a/lib/src/channel_image.dart b/lib/src/channel_image.dart index c3d76202..2f5862f3 100644 --- a/lib/src/channel_image.dart +++ b/lib/src/channel_image.dart @@ -52,6 +52,9 @@ class ChannelImage extends StatelessWidget { this.onTap, this.showOnlineStatus = true, this.borderRadius, + this.selected = false, + this.selectionColor, + this.selectionThickness = 4, }) : super(key: key); final BorderRadius borderRadius; @@ -67,6 +70,12 @@ class ChannelImage extends StatelessWidget { final bool showOnlineStatus; + final bool selected; + + final Color selectionColor; + + final double selectionThickness; + @override Widget build(BuildContext context) { final streamChat = StreamChat.of(context); @@ -94,11 +103,11 @@ class ChannelImage extends StatelessWidget { .channelPreviewTheme .avatarTheme .constraints, - onTap: onTap != null - ? (_) { - onTap(); - } - : null, + onTap: onTap != null ? (_) => onTap() : null, + selected: selected, + selectionColor: selectionColor ?? + StreamChatTheme.of(context).colorTheme.accentBlue, + selectionThickness: selectionThickness, ); }); } else { @@ -111,16 +120,21 @@ class ChannelImage extends StatelessWidget { .toList(); return GroupImage( images: images, + borderRadius: borderRadius, constraints: constraints ?? StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme .constraints, onTap: onTap, + selected: selected, + selectionColor: selectionColor ?? + StreamChatTheme.of(context).colorTheme.accentBlue, + selectionThickness: selectionThickness, ); } - return ClipRRect( + Widget child = ClipRRect( borderRadius: borderRadius ?? StreamChatTheme.of(context) .channelPreviewTheme @@ -133,7 +147,7 @@ class ChannelImage extends StatelessWidget { .avatarTheme .constraints, decoration: BoxDecoration( - color: StreamChatTheme.of(context).accentColor, + color: StreamChatTheme.of(context).colorTheme.accentBlue, ), child: Stack( alignment: Alignment.center, @@ -149,7 +163,9 @@ class ChannelImage extends StatelessWidget { ? snapshot.data['name'][0] : '', style: TextStyle( - color: Colors.white, + color: StreamChatTheme.of(context) + .colorTheme + .white, fontWeight: FontWeight.bold, ), ), @@ -169,6 +185,30 @@ class ChannelImage extends StatelessWidget { ), ), ); + if (selected) { + child = ClipRRect( + borderRadius: (borderRadius ?? + StreamChatTheme.of(context) + .ownMessageTheme + .avatarTheme + .borderRadius) + + BorderRadius.circular(selectionThickness), + child: Container( + constraints: constraints ?? + StreamChatTheme.of(context) + .ownMessageTheme + .avatarTheme + .constraints, + color: selectionColor ?? + StreamChatTheme.of(context).colorTheme.accentBlue, + child: Padding( + padding: EdgeInsets.all(selectionThickness), + child: child, + ), + ), + ); + } + return child; }); } } diff --git a/lib/src/channel_info.dart b/lib/src/channel_info.dart index eda58f43..b1ab568d 100644 --- a/lib/src/channel_info.dart +++ b/lib/src/channel_info.dart @@ -134,7 +134,7 @@ class ChannelInfo extends StatelessWidget { child: Text( 'Try Again', style: textStyle.copyWith( - color: Color(0xFF006CFF), + color: StreamChatTheme.of(context).colorTheme.accentBlue, ), ), ), diff --git a/lib/src/channel_list_header.dart b/lib/src/channel_list_header.dart index 0a01733b..34f3cc76 100644 --- a/lib/src/channel_list_header.dart +++ b/lib/src/channel_list_header.dart @@ -99,7 +99,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { var color; switch (status) { case ConnectionStatus.connected: - color = Color(0xFF006CFF); + color = StreamChatTheme.of(context).colorTheme.accentBlue; break; case ConnectionStatus.connecting: color = Colors.grey; @@ -144,13 +144,8 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { Widget _buildConnectedTitleState(BuildContext context) => Text( 'Stream Chat', - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .title - .copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, + style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith( + color: StreamChatTheme.of(context).colorTheme.black, ), ); @@ -210,7 +205,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { .copyWith( fontSize: 16, fontWeight: FontWeight.bold, - color: Color(0xFF006CFF), + color: StreamChatTheme.of(context).colorTheme.accentBlue, ), ), ), diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 5dbc9f90..777fae57 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -74,6 +74,8 @@ class ChannelListView extends StatefulWidget { this.onStartChatPressed, this.swipeToAction = false, this.pullToRefresh = true, + this.crossAxisCount = 1, + this.selectedChannels = const [], }) : super(key: key); /// The builder that will be used in case of error @@ -134,6 +136,11 @@ class ChannelListView extends StatefulWidget { /// Callback used in the default empty list widget final VoidCallback onStartChatPressed; + /// The number of children in the cross axis. + final int crossAxisCount; + + final List selectedChannels; + @override _ChannelListViewState createState() => _ChannelListViewState(); } @@ -203,16 +210,18 @@ class _ChannelListViewState extends State padding: const EdgeInsets.all(8.0), child: StreamSvgIcon.message( size: 136, - color: Color(0xffDBDBDB), + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, ), ), Padding( padding: const EdgeInsets.all(8.0), child: Text( 'Let’s start chatting!', - style: TextStyle( - fontSize: 16, - ), + style: StreamChatTheme.of(context) + .textTheme + .headline, ), ), Padding( @@ -223,10 +232,14 @@ class _ChannelListViewState extends State child: Text( 'How about sending your first message to a friend?', textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - color: Color(0xff7A7A7A), - ), + style: StreamChatTheme.of(context) + .textTheme + .body + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .grey, + ), ), ), ], @@ -242,11 +255,14 @@ class _ChannelListViewState extends State onPressed: widget.onStartChatPressed, child: Text( 'Start a chat', - style: TextStyle( - color: - StreamChatTheme.of(context).accentColor, - fontWeight: FontWeight.bold, - ), + style: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, + ), ), ), ), @@ -259,22 +275,33 @@ class _ChannelListViewState extends State } if (channels.isNotEmpty) { - child = ListView.custom( - physics: AlwaysScrollableScrollPhysics(), - controller: _scrollController, - childrenDelegate: SliverChildBuilderDelegate( - (context, i) { - return _itemBuilder(context, i, channels); + if (widget.crossAxisCount > 1) { + child = GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: widget.crossAxisCount), + itemCount: channels.length, + physics: AlwaysScrollableScrollPhysics(), + controller: _scrollController, + itemBuilder: (context, index) { + return _gridItemBuilder(context, index, channels); }, - childCount: (channels.length * 2) + 1, - findChildIndexCallback: (key) { - final ValueKey valueKey = key; - final index = channels.indexWhere( - (channel) => 'CHANNEL-${channel.id}' == valueKey.value); - return index != -1 ? (index * 2) : null; + ); + } else { + child = ListView.separated( + physics: AlwaysScrollableScrollPhysics(), + itemCount: + channels.isNotEmpty ? channels.length + 1 : channels.length, + separatorBuilder: (_, index) { + if (widget.separatorBuilder != null) { + return widget.separatorBuilder(context, index); + } + return _separatorBuilder(context, index); }, - ), - ); + itemBuilder: (context, index) { + return _listItemBuilder(context, index, channels); + }, + ); + } } } @@ -292,11 +319,13 @@ class _ChannelListViewState extends State children: List.generate( 25, (i) { - if (i % 2 != 0) { - if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, i); + if (widget.crossAxisCount == 1) { + if (i % 2 != 0) { + if (widget.separatorBuilder != null) { + return widget.separatorBuilder(context, i); + } + return _separatorBuilder(context, i); } - return _separatorBuilder(context, i); } return _buildLoadingItem(); }, @@ -305,63 +334,96 @@ class _ChannelListViewState extends State } Shimmer _buildLoadingItem() { - return Shimmer.fromColors( - baseColor: Color(0xffE5E5E5), - highlightColor: Color(0xffffffff), - child: ListTile( - leading: Container( - decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - constraints: BoxConstraints.tightFor( - height: 40, - width: 40, - ), + if (widget.crossAxisCount > 1) { + return Shimmer.fromColors( + baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, + highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke, + child: Column( + children: [ + SizedBox( + height: 4.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + for (int i = 0; i < widget.crossAxisCount; i++) + Container( + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + constraints: BoxConstraints.tightFor( + height: 70, + width: 70, + ), + ), + ], + ), + SizedBox( + height: 16.0, + ), + ], ), - title: Align( - alignment: Alignment.centerLeft, - child: Container( + ); + } else { + return Shimmer.fromColors( + baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, + highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke, + child: ListTile( + leading: Container( decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(11), + color: StreamChatTheme.of(context).colorTheme.white, + shape: BoxShape.circle, ), constraints: BoxConstraints.tightFor( - height: 16, - width: 82, + height: 40, + width: 40, ), ), - ), - subtitle: Row( - children: [ - Align( - alignment: Alignment.centerLeft, - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(11), - ), - constraints: BoxConstraints.tightFor( - height: 16, - width: 238, - ), - ), - ), - Container( - margin: const EdgeInsets.only(left: 16), + title: Align( + alignment: Alignment.centerLeft, + child: Container( decoration: BoxDecoration( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, borderRadius: BorderRadius.circular(11), ), constraints: BoxConstraints.tightFor( height: 16, - width: 42, + width: 82, ), ), - ], + ), + subtitle: Row( + children: [ + Align( + alignment: Alignment.centerLeft, + child: Container( + decoration: BoxDecoration( + color: StreamChatTheme.of(context).colorTheme.white, + borderRadius: BorderRadius.circular(11), + ), + constraints: BoxConstraints.tightFor( + height: 16, + width: 238, + ), + ), + ), + Container( + margin: const EdgeInsets.only(left: 16), + decoration: BoxDecoration( + color: StreamChatTheme.of(context).colorTheme.white, + borderRadius: BorderRadius.circular(11), + ), + constraints: BoxConstraints.tightFor( + height: 16, + width: 42, + ), + ), + ], + ), ), - ), - ); + ); + } } Widget _buildErrorWidget( @@ -428,20 +490,10 @@ class _ChannelListViewState extends State ); } - Widget _itemBuilder(context, int i, List channels) { - if (i % 2 != 0) { - if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, i); - } - return _separatorBuilder(context, i); - } - - i = i ~/ 2; - + Widget _listItemBuilder(BuildContext context, int i, List channels) { final channelsProvider = ChannelsBloc.of(context); if (i < channels.length) { final channel = channels[i]; - ChannelTapCallback onTap; if (widget.onChannelTap != null) { onTap = widget.onChannelTap; @@ -493,9 +545,7 @@ class _ChannelListViewState extends State ); } else { final backgroundColor = - Theme.of(context).brightness == Brightness.light - ? Color(0xffEBEBEB) - : Color(0xff141414); + StreamChatTheme.of(context).colorTheme.whiteSmoke; child = Slidable( enabled: widget.swipeToAction, actionPane: SlidableBehindActionPane(), @@ -535,7 +585,7 @@ class _ChannelListViewState extends State IconSlideAction( color: backgroundColor, iconWidget: StreamSvgIcon.delete( - color: Color(0xFFFF3742), + color: StreamChatTheme.of(context).colorTheme.accentRed, ), onTap: () async { final res = await showConfirmationDialog( @@ -546,7 +596,9 @@ class _ChannelListViewState extends State 'Are you sure you want to delete this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.delete( - color: Color(0xFFFF3742), + color: StreamChatTheme.of(context) + .colorTheme + .accentRed, ), ); if (res == true) { @@ -556,7 +608,7 @@ class _ChannelListViewState extends State ), ], child: Container( - color: StreamChatTheme.of(context).backgroundColor, + color: StreamChatTheme.of(context).colorTheme.whiteSnow, child: ChannelPreview( onLongPress: widget.onChannelLongPress, channel: channel, @@ -581,6 +633,47 @@ class _ChannelListViewState extends State } } + Widget _gridItemBuilder(BuildContext context, int i, List channels) { + var channel = channels[i]; + + var selected = widget.selectedChannels.contains(channel); + + return Container( + key: ValueKey('CHANNEL-${channel.id}'), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ChannelImage( + channel: channel, + borderRadius: BorderRadius.circular(32), + selected: selected, + constraints: BoxConstraints.tightFor( + width: 64, + height: 64, + ), + onTap: () { + widget.onChannelTap(channel, null); + }, + ), + SizedBox(height: 7), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: StreamChannel( + child: ChannelName( + textStyle: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + channel: channel, + ), + ), + ], + ), + ); + } + Widget _buildQueryProgressIndicator( context, ChannelsBlocState channelsProvider, @@ -591,7 +684,10 @@ class _ChannelListViewState extends State builder: (context, snapshot) { if (snapshot.hasError) { return Container( - color: Color(0xffd0021B).withAlpha(26), + color: StreamChatTheme.of(context) + .colorTheme + .accentRed + .withOpacity(.2), child: Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: Center( @@ -613,9 +709,7 @@ class _ChannelListViewState extends State Widget _separatorBuilder(context, i) { return Container( height: 1, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(0.1) - : Colors.black.withOpacity(0.1), + color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.08), ); } diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index 5f58e254..7b86bf56 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -50,6 +50,9 @@ class ChannelPreview extends StatelessWidget { return Opacity( opacity: snapshot.data ? 0.5 : 1, child: ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 8, + ), onTap: () { if (onTap != null) { onTap(channel); @@ -122,12 +125,16 @@ class ChannelPreview extends StatelessWidget { padding: const EdgeInsets.only(right: 4.0), child: SendingIndicator( message: channel.state.lastMessage, - allRead: channel.state.read + size: StreamChatTheme.of(context) + .channelPreviewTheme + .lastMessageAt + .fontSize, + isMessageRead: channel.state.read .where((element) => element.lastRead .isAfter(channel .state.lastMessage.createdAt)) .length == - (channel.memberCount ?? 0), + ((channel.memberCount ?? 0) - 1), ), ); } diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index 8d291f38..3a9c25ee 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -1,4 +1,3 @@ -import 'package:emojis/emojis.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; @@ -21,7 +20,7 @@ class _ChatInfoScreenState extends State { Widget build(BuildContext context) { final channel = StreamChannel.of(context).channel; return Scaffold( - backgroundColor: Color(0xFFe6e6e6), + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, body: ListView( children: [ _buildUserHeader(), @@ -47,7 +46,7 @@ class _ChatInfoScreenState extends State { Widget _buildUserHeader() { return Material( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.whiteSnow, child: SafeArea( child: Stack( children: [ @@ -81,7 +80,11 @@ class _ChatInfoScreenState extends State { child: Text( widget.user.name, style: TextStyle( - color: Colors.black.withOpacity(0.5), fontSize: 16.0), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + fontSize: 16.0), ), ), onTap: () {}, @@ -92,7 +95,9 @@ class _ChatInfoScreenState extends State { top: 21, left: 16, child: InkWell( - child: StreamSvgIcon.left(), + child: StreamSvgIcon.left( + color: StreamChatTheme.of(context).colorTheme.black, + ), onTap: () { Navigator.of(context).pop(); }, @@ -113,7 +118,7 @@ class _ChatInfoScreenState extends State { // title: 'Notifications', // leading: StreamSvgIcon.Icon_notification( // size: 24.0, - // color: Colors.black.withOpacity(0.5), + // color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), // ), // trailing: CupertinoSwitch( // value: true, @@ -128,7 +133,10 @@ class _ChatInfoScreenState extends State { title: 'Mute user', leading: StreamSvgIcon.mute( size: 23.0, - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), trailing: snapshot.data == null ? CircularProgressIndicator() @@ -149,7 +157,7 @@ class _ChatInfoScreenState extends State { // title: 'Block User', // leading: StreamSvgIcon.Icon_user_delete( // size: 24.0, - // color: Colors.black.withOpacity(0.5), + // color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), // ), // trailing: CupertinoSwitch( // value: widget.user.banned, @@ -167,7 +175,8 @@ class _ChatInfoScreenState extends State { title: 'Photos & Videos', leading: StreamSvgIcon.pictures( size: 32.0, - color: Colors.black.withOpacity(0.5), + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), ), trailing: StreamSvgIcon.right(), onTap: () { @@ -179,7 +188,8 @@ class _ChatInfoScreenState extends State { title: 'Files', leading: StreamSvgIcon.files( size: 32.0, - color: Colors.black.withOpacity(0.5), + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), ), trailing: StreamSvgIcon.right(), onTap: () { @@ -191,7 +201,8 @@ class _ChatInfoScreenState extends State { title: 'Shared groups', leading: StreamSvgIcon.Icon_group( size: 24.0, - color: Colors.black.withOpacity(0.5), + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), ), trailing: StreamSvgIcon.right(), onTap: () { @@ -208,7 +219,7 @@ class _ChatInfoScreenState extends State { Widget _buildDeleteListTile() { return _OptionListTile( - title: 'Delete', + title: 'Delete conversation', leading: StreamSvgIcon.delete( color: Colors.red, size: 24.0, @@ -248,12 +259,20 @@ class _ChatInfoScreenState extends State { if (otherMember.online) { alternativeWidget = Text( 'Online', - style: TextStyle(color: Colors.black.withOpacity(0.5)), + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), ); } else { alternativeWidget = Text( 'Last seen ${Jiffy(otherMember.lastActive).fromNow()}', - style: TextStyle(color: Colors.black.withOpacity(0.5)), + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), ); } } @@ -272,10 +291,10 @@ class _ChatInfoScreenState extends State { ), child: Material( shape: CircleBorder(), - color: Color(0xff20E070), + color: StreamChatTheme.of(context).colorTheme.accentGreen, ), ), - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, ), alternativeWidget, ], @@ -303,11 +322,11 @@ class _OptionListTile extends StatelessWidget { return Column( children: [ Container( - color: Color(0xffe6e6e6), + color: StreamChatTheme.of(context).colorTheme.white, height: 2.0, ), Material( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.whiteSnow, child: Container( height: 56.0, child: InkWell( @@ -365,14 +384,16 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { var chat = StreamChat.of(context); return Scaffold( - backgroundColor: Colors.white, + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, appBar: AppBar( brightness: Theme.of(context).brightness, elevation: 1, centerTitle: true, title: Text( 'Shared Groups', - style: TextStyle(color: Colors.black, fontSize: 16.0), + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.black, + fontSize: 16.0), ), leading: Center( child: InkWell( @@ -381,7 +402,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { }, child: Container( child: StreamSvgIcon.left( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, size: 24.0, ), width: 24.0, @@ -389,7 +410,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { ), ), ), - backgroundColor: StreamChatTheme.of(context).primaryColor, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, ), body: FutureBuilder>( future: chat.client.queryChannels( @@ -488,7 +509,11 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { padding: const EdgeInsets.all(8.0), child: Text( '${channel.memberCount} members', - style: TextStyle(color: Colors.black.withOpacity(0.5)), + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), ), ) ], @@ -496,7 +521,8 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { ), Container( height: 1.0, - color: Color(0xffe6e6e6), + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), ), ], ); @@ -509,14 +535,16 @@ class _MediaDisplayScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.white, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, appBar: AppBar( brightness: Theme.of(context).brightness, elevation: 1, centerTitle: true, title: Text( 'Photos & Videos', - style: TextStyle(color: Colors.black, fontSize: 16.0), + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.black, + fontSize: 16.0), ), leading: Center( child: InkWell( @@ -525,7 +553,7 @@ class _MediaDisplayScreen extends StatelessWidget { }, child: Container( child: StreamSvgIcon.left( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, size: 24.0, ), width: 24.0, @@ -533,7 +561,7 @@ class _MediaDisplayScreen extends StatelessWidget { ), ), ), - backgroundColor: StreamChatTheme.of(context).primaryColor, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, ), ); } @@ -543,14 +571,16 @@ class _FileDisplayScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.white, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, appBar: AppBar( brightness: Theme.of(context).brightness, elevation: 1, centerTitle: true, title: Text( 'Files', - style: TextStyle(color: Colors.black, fontSize: 16.0), + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.black, + fontSize: 16.0), ), leading: Center( child: InkWell( @@ -559,7 +589,7 @@ class _FileDisplayScreen extends StatelessWidget { }, child: Container( child: StreamSvgIcon.left( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, size: 24.0, ), width: 24.0, @@ -567,7 +597,7 @@ class _FileDisplayScreen extends StatelessWidget { ), ), ), - backgroundColor: StreamChatTheme.of(context).primaryColor, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, ), ); } diff --git a/lib/src/date_divider.dart b/lib/src/date_divider.dart index 8fbe5f77..12070e51 100644 --- a/lib/src/date_divider.dart +++ b/lib/src/date_divider.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; /// It shows a date divider depending on the date difference class DateDivider extends StatelessWidget { @@ -39,21 +40,17 @@ class DateDivider extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( - color: (Theme.of(context).brightness == Brightness.light - ? Colors.black - : Colors.white) - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), borderRadius: BorderRadius.circular( 8, ), ), child: Text( dayInfo, - style: TextStyle( - fontSize: 12, - color: Colors.white, - fontWeight: FontWeight.bold, - ), + style: StreamChatTheme.of(context).textTheme.footnoteBold.copyWith( + color: StreamChatTheme.of(context).colorTheme.white, + fontWeight: FontWeight.bold, + ), ), ), ); diff --git a/lib/src/deleted_message.dart b/lib/src/deleted_message.dart index 9181d985..5921df43 100644 --- a/lib/src/deleted_message.dart +++ b/lib/src/deleted_message.dart @@ -41,8 +41,14 @@ class DeletedMessage extends StatelessWidget { side: borderSide ?? BorderSide( color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withAlpha(24) - : Colors.black.withAlpha(24), + ? StreamChatTheme.of(context) + .colorTheme + .white + .withAlpha(24) + : StreamChatTheme.of(context) + .colorTheme + .black + .withAlpha(24), ), ), child: Padding( @@ -58,8 +64,8 @@ class DeletedMessage extends StatelessWidget { style: messageTheme.messageText.copyWith( fontStyle: FontStyle.italic, color: (Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black) + ? StreamChatTheme.of(context).colorTheme.white + : StreamChatTheme.of(context).colorTheme.black) .withOpacity(.5), ), ), diff --git a/lib/src/file_attachment.dart b/lib/src/file_attachment.dart index 4e99b557..ca51bf76 100644 --- a/lib/src/file_attachment.dart +++ b/lib/src/file_attachment.dart @@ -9,6 +9,7 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; import 'package:video_compress/video_compress.dart'; import 'package:video_player/video_player.dart'; + import 'media_utils.dart'; enum FileAttachmentType { local, online } @@ -61,13 +62,13 @@ class _FileAttachmentState extends State { child: Container( width: widget.size?.width ?? 100, height: 56.0, - margin: widget.trailing != null ? EdgeInsets.only(top: 4.0) : null, decoration: BoxDecoration( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, borderRadius: widget.trailing != null ? BorderRadius.circular(16.0) : null, border: widget.trailing != null - ? Border.fromBorderSide(BorderSide(color: Color(0xFFE6E6E6))) + ? Border.fromBorderSide(BorderSide( + color: StreamChatTheme.of(context).colorTheme.greyWhisper)) : null, ), child: Row( @@ -88,10 +89,7 @@ class _FileAttachmentState extends State { children: [ Text( widget.attachment?.title ?? 'File', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14.0, - ), + style: StreamChatTheme.of(context).textTheme.bodyBold, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -100,10 +98,11 @@ class _FileAttachmentState extends State { ), Text( '${getSizeText(widget.attachment.extraData['file_size'])}', - style: TextStyle( - color: Colors.black.withOpacity(0.5), - fontSize: 14.0, - ), + style: StreamChatTheme.of(context).textTheme.body.copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), ), ], ), @@ -113,7 +112,7 @@ class _FileAttachmentState extends State { widget.trailing ?? IconButton( icon: StreamSvgIcon.cloud_download( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), onPressed: () { launchURL(context, widget.attachment.assetUrl); @@ -148,7 +147,8 @@ class _FileAttachmentState extends State { width: 20.0, height: 20.0, child: CircularProgressIndicator( - backgroundColor: StreamChatTheme.of(context).accentColor, + backgroundColor: + StreamChatTheme.of(context).colorTheme.accentBlue, ), ), ); diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index 3e29b2f1..cc1a319f 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -112,7 +112,10 @@ class GiphyAttachment extends StatelessWidget { bottom: 16, left: 16, child: Material( - color: Colors.black.withOpacity(.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), @@ -124,13 +127,16 @@ class GiphyAttachment extends StatelessWidget { child: Row( children: [ StreamSvgIcon.lightning( - color: Colors.white, + color: + StreamChatTheme.of(context).colorTheme.white, size: 16, ), Text( 'GIPHY', style: TextStyle( - color: Colors.white, + color: StreamChatTheme.of(context) + .colorTheme + .white, fontWeight: FontWeight.bold, fontSize: 11, ), @@ -202,7 +208,10 @@ class GiphyAttachment extends StatelessWidget { height: 4.0, ), Container( - color: Colors.black.withOpacity(0.2), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.2), width: double.infinity, height: 0.5, ), @@ -220,15 +229,24 @@ class GiphyAttachment extends StatelessWidget { }, child: Text( 'Cancel', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.black.withOpacity(0.5)), + style: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + ), ), ), ), Container( width: 0.5, - color: Colors.black.withOpacity(0.2), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.2), height: 50.0, ), Expanded( @@ -242,7 +260,9 @@ class GiphyAttachment extends StatelessWidget { child: Text( 'Send', style: TextStyle( - color: StreamChatTheme.of(context).accentColor, + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, fontWeight: FontWeight.bold), ), ), @@ -263,7 +283,10 @@ class GiphyAttachment extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ StreamSvgIcon.eye( - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), size: 16.0, ), SizedBox( @@ -271,10 +294,14 @@ class GiphyAttachment extends StatelessWidget { ), Text( 'Only visible to you', - style: TextStyle( - color: Colors.black.withOpacity(0.5), - fontSize: 12.0, - ), + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), ), ], ), @@ -331,7 +358,10 @@ class GiphyAttachment extends StatelessWidget { bottom: 8, left: 8, child: Material( - color: Colors.black.withOpacity(.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), @@ -343,13 +373,13 @@ class GiphyAttachment extends StatelessWidget { child: Row( children: [ StreamSvgIcon.lightning( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, size: 16, ), Text( 'GIPHY', style: TextStyle( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, fontWeight: FontWeight.bold, fontSize: 11, ), diff --git a/lib/src/group_image.dart b/lib/src/group_image.dart index 96e25f74..4cfd655a 100644 --- a/lib/src/group_image.dart +++ b/lib/src/group_image.dart @@ -9,21 +9,33 @@ class GroupImage extends StatelessWidget { @required this.images, this.constraints, this.onTap, + this.borderRadius, + this.selected = false, + this.selectionColor, + this.selectionThickness = 4, }) : super(key: key); final List images; final BoxConstraints constraints; final VoidCallback onTap; + final bool selected; + final BorderRadius borderRadius; + final Color selectionColor; + final double selectionThickness; @override Widget build(BuildContext context) { - return GestureDetector( + var avatar; + final streamChatTheme = StreamChatTheme.of(context); + + avatar = GestureDetector( onTap: onTap, child: ClipRRect( - borderRadius: StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - .borderRadius, + borderRadius: borderRadius ?? + StreamChatTheme.of(context) + .ownMessageTheme + .avatarTheme + .borderRadius, child: Container( constraints: constraints ?? StreamChatTheme.of(context) @@ -31,7 +43,7 @@ class GroupImage extends StatelessWidget { .avatarTheme .constraints, decoration: BoxDecoration( - color: StreamChatTheme.of(context).accentColor, + color: StreamChatTheme.of(context).colorTheme.accentBlue, ), child: Flex( direction: Axis.vertical, @@ -91,5 +103,25 @@ class GroupImage extends StatelessWidget { ), ), ); + + if (selected) { + avatar = ClipRRect( + borderRadius: (borderRadius ?? + streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) + + BorderRadius.circular(selectionThickness), + child: Container( + color: selectionColor ?? + StreamChatTheme.of(context).colorTheme.accentBlue, + height: 64.0, + width: 64.0, + child: Padding( + padding: EdgeInsets.all(selectionThickness), + child: avatar, + ), + ), + ); + } + + return avatar; } } diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index fc81635c..168581c0 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -1,13 +1,14 @@ import 'dart:typed_data'; import 'dart:ui'; +import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:dio/dio.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; -import '../stream_chat_flutter.dart'; import 'package:path_provider/path_provider.dart'; +import '../stream_chat_flutter.dart'; + class ImageActionsModal extends StatelessWidget { final Message message; final String userName; @@ -66,11 +67,12 @@ class ImageActionsModal extends StatelessWidget { padding: const EdgeInsets.only(top: 8.0), child: Text( userName, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w700, - color: Colors.white, - ), + style: StreamChatTheme.of(context) + .textTheme + .headlineBold + .copyWith( + color: + StreamChatTheme.of(context).colorTheme.white), ), ), Text( @@ -78,14 +80,18 @@ class ImageActionsModal extends StatelessWidget { style: StreamChatTheme.of(context) .channelPreviewTheme .subtitle - .copyWith(color: Colors.white.withOpacity(0.5)), + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .white + .withOpacity(0.5)), ), ], ), IconButton( icon: StreamSvgIcon.close( size: 24.0, - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, ), onPressed: () { Navigator.pop(context); @@ -104,10 +110,10 @@ class ImageActionsModal extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), - color: Color(0xffe5e5e5), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: ListTile.divideTiles( + color: StreamChatTheme.of(context).colorTheme.greyWhisper, context: context, tiles: [ _buildButton( @@ -115,7 +121,10 @@ class ImageActionsModal extends StatelessWidget { 'Reply', StreamSvgIcon.Icon_curve_line_left_up( size: 24.0, - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), () {}), _buildButton( @@ -123,7 +132,8 @@ class ImageActionsModal extends StatelessWidget { 'Show in Chat', StreamSvgIcon.eye( size: 24.0, - color: Colors.black, + color: + StreamChatTheme.of(context).colorTheme.black, ), () { Navigator.pop(context); Navigator.pop(context); @@ -133,7 +143,10 @@ class ImageActionsModal extends StatelessWidget { 'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}', StreamSvgIcon.Icon_save( size: 24.0, - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), () async { var url = urls[currentIndex].imageUrl ?? urls[currentIndex].assetUrl ?? @@ -153,7 +166,9 @@ class ImageActionsModal extends StatelessWidget { 'Delete', StreamSvgIcon.delete( size: 24.0, - color: Color(0xffFF3742), + color: StreamChatTheme.of(context) + .colorTheme + .accentRed, ), () { Navigator.pop(context); @@ -163,7 +178,9 @@ class ImageActionsModal extends StatelessWidget { StreamChannel.of(context).channel.cid, ); }, - color: Color(0xffFF3742), + color: StreamChatTheme.of(context) + .colorTheme + .accentRed, ), ], ).toList(), @@ -182,10 +199,11 @@ class ImageActionsModal extends StatelessWidget { {Color color}) { var titleStyle = TextStyle( fontSize: 14.5, - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ); return Material( + color: StreamChatTheme.of(context).colorTheme.white, child: InkWell( onTap: onTap, child: ListTile( diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 3c37a66a..eff6110a 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -5,10 +5,10 @@ import 'dart:typed_data'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:chewie/chewie.dart'; +import 'package:dio/dio.dart'; import 'package:esys_flutter_share/esys_flutter_share.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:dio/dio.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:path_provider/path_provider.dart'; import 'package:stream_chat/stream_chat.dart'; @@ -16,8 +16,6 @@ import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'stream_channel.dart'; - class ImageFooter extends StatefulWidget { /// Callback to call when pressing the back button. /// By default it calls [Navigator.pop] @@ -64,11 +62,11 @@ class _ImageFooterState extends State { bool _userSearchMode = false; TextEditingController _searchController; TextEditingController _messageController = TextEditingController(); + FocusNode _messageFocusNode = FocusNode(); - String _userNameQuery; - bool _isSearchActive = false; + String _channelNameQuery; - Set _selectedUsers = {}; + List _selectedChannels = []; bool _loading = false; Timer _debounce; @@ -80,8 +78,7 @@ class _ImageFooterState extends State { _debounce = Timer(const Duration(milliseconds: 350), () { if (mounted && modalSetStateCallback != null) { modalSetStateCallback(() { - _userNameQuery = _searchController.text; - _isSearchActive = _userNameQuery.isNotEmpty; + _channelNameQuery = _searchController.text; }); } }); @@ -91,6 +88,9 @@ class _ImageFooterState extends State { void initState() { super.initState(); _searchController = TextEditingController()..addListener(_userNameListener); + _messageFocusNode.addListener(() { + setState(() {}); + }); } @override @@ -114,7 +114,7 @@ class _ImageFooterState extends State { IconButton( icon: StreamSvgIcon.icon_SHARE( size: 24.0, - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), onPressed: () { _buildShareModal(context); @@ -130,11 +130,7 @@ class _ImageFooterState extends State { children: [ Text( '${widget.currentPage + 1} of ${widget.totalPages}', - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w700, - color: Colors.black, - ), + style: StreamChatTheme.of(context).textTheme.headlineBold, ), ], ), @@ -142,7 +138,7 @@ class _ImageFooterState extends State { ), IconButton( icon: StreamSvgIcon.Icon_grid( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), onPressed: () { _buildPhotosModal(context); @@ -170,7 +166,7 @@ class _ImageFooterState extends State { children: [ Container( decoration: BoxDecoration( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, borderRadius: BorderRadius.only( topRight: Radius.circular(16.0), topLeft: Radius.circular(16.0), @@ -182,11 +178,8 @@ class _ImageFooterState extends State { padding: const EdgeInsets.all(16.0), child: Text( 'Photos', - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w700, - color: Colors.black, - ), + style: + StreamChatTheme.of(context).textTheme.headlineBold, ), ), ), @@ -194,7 +187,7 @@ class _ImageFooterState extends State { alignment: Alignment.centerRight, child: IconButton( icon: StreamSvgIcon.close( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), onPressed: () { Navigator.pop(context); @@ -205,7 +198,7 @@ class _ImageFooterState extends State { ), ), Container( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, child: GridView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), @@ -258,7 +251,7 @@ class _ImageFooterState extends State { ); } - Widget _buildShareModal(context) { + void _buildShareModal(context) { showDialog( context: context, builder: (context) { @@ -266,7 +259,7 @@ class _ImageFooterState extends State { modalSetStateCallback = modalSetState; return Padding( padding: EdgeInsets.only( - top: _userSearchMode + top: _userSearchMode || _messageFocusNode.hasFocus ? 16.0 : MediaQuery.of(context).size.height / 2, left: 8.0, @@ -282,80 +275,89 @@ class _ImageFooterState extends State { child: Column( children: [ _buildTextInputSection(modalSetState), + if (_userSearchMode) + SizedBox( + height: 22.0, + ), Expanded( - child: UserListView( - selectedUsers: _selectedUsers, - onUserTap: (user, _) { - _searchController.clear(); - if (!_selectedUsers.contains(user)) { - modalSetState(() { - _selectedUsers.add(user); - }); - } else { - modalSetState(() { - _selectedUsers.remove(user); - }); - } - }, - crossAxisCount: 4, - pagination: PaginationParams( - limit: 25, - ), - filter: { - if (_searchController.text.isNotEmpty) - 'name': { - r'$autocomplete': _userNameQuery, - }, - 'id': { - r'$ne': StreamChat.of(context).user.id, + child: ChannelsBloc( + child: ChannelListView( + selectedChannels: _selectedChannels, + onChannelTap: (channel, _) { + _searchController.clear(); + if (!_selectedChannels.contains(channel)) { + modalSetState(() { + _selectedChannels.add(channel); + }); + } else { + modalSetState(() { + _selectedChannels.remove(channel); + }); + } }, - }, - sort: [ - SortOption( - 'name', - direction: 1, + crossAxisCount: 4, + pagination: PaginationParams( + limit: 25, ), - ], - 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, + filter: { + if (_channelNameQuery?.trim()?.isNotEmpty == true) + 'name': { + r'$autocomplete': _channelNameQuery, + }, + 'id': { + r'$ne': StreamChat.of(context).user.id, + }, + }, + sort: [ + SortOption( + 'name', + direction: SortOption.ASC, + ), + ], + 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...'), - ], + Text( + 'No chat matches these keywords...'), + ], + ), ), ), - ), - ); - }, - ); - }, + ); + }, + ); + }, + ), ), ), - if (_selectedUsers.isNotEmpty) + if (_selectedChannels.isNotEmpty) _buildShareTextInputSection(modalSetState), - if (!_userSearchMode && _selectedUsers.isEmpty) + if (!_userSearchMode && _selectedChannels.isEmpty) Align( alignment: Alignment.bottomCenter, child: Container( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, height: 48.0, child: Material( + color: + StreamChatTheme.of(context).colorTheme.white, child: InkWell( onTap: () async { var url = widget @@ -385,7 +387,8 @@ class _ImageFooterState extends State { 'Save to Photos', style: TextStyle( color: StreamChatTheme.of(context) - .accentColor, + .colorTheme + .accentBlue, fontWeight: FontWeight.bold, ), ), @@ -422,7 +425,7 @@ class _ImageFooterState extends State { Expanded( child: TextField( controller: _searchController, - cursorColor: Colors.black, + cursorColor: StreamChatTheme.of(context).colorTheme.black, autofocus: true, decoration: InputDecoration( isDense: true, @@ -432,19 +435,25 @@ class _ImageFooterState extends State { padding: const EdgeInsets.symmetric( vertical: 2.0, horizontal: 6.0), child: StreamSvgIcon.search( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), ), hintText: 'Search', border: OutlineInputBorder( borderRadius: BorderRadius.circular(32.0), - borderSide: - BorderSide(color: Colors.black.withOpacity(0.08)), + borderSide: BorderSide( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.08)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(32.0), - borderSide: - BorderSide(color: Colors.black.withOpacity(0.08)), + borderSide: BorderSide( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.08)), ), contentPadding: EdgeInsets.zero, ), @@ -455,7 +464,10 @@ class _ImageFooterState extends State { ), IconButton( icon: StreamSvgIcon.close_small( - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), onPressed: () { modalSetState(() { @@ -476,7 +488,7 @@ class _ImageFooterState extends State { padding: const EdgeInsets.symmetric(horizontal: 6.0), child: IconButton( icon: StreamSvgIcon.search( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), iconSize: 24.0, onPressed: () { @@ -490,18 +502,14 @@ class _ImageFooterState extends State { padding: const EdgeInsets.all(16.0), child: Text( 'Select a Chat to Share', - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w700, - color: Colors.black, - ), + style: StreamChatTheme.of(context).textTheme.headlineBold, ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 6.0), child: IconButton( icon: StreamSvgIcon.share_arrow( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), onPressed: () async { var url = @@ -528,7 +536,7 @@ class _ImageFooterState extends State { return Align( alignment: Alignment.bottomCenter, child: Container( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, height: 56.0, child: _loading ? Center( @@ -544,6 +552,7 @@ class _ImageFooterState extends State { padding: const EdgeInsets.only(left: 8.0), child: TextField( controller: _messageController, + focusNode: _messageFocusNode, onChanged: (val) { modalSetState(() {}); }, @@ -558,13 +567,19 @@ class _ImageFooterState extends State { border: OutlineInputBorder( borderRadius: BorderRadius.circular(32.0), borderSide: BorderSide( - color: Colors.black.withOpacity(0.16), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.16), ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(32.0), borderSide: BorderSide( - color: Colors.black.withOpacity(0.16), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.16), )), contentPadding: EdgeInsets.symmetric(vertical: 12.0), ), @@ -591,7 +606,9 @@ class _ImageFooterState extends State { child: Transform.rotate( angle: -pi / 2, child: StreamSvgIcon.Icon_send_message( - color: StreamChatTheme.of(context).accentColor, + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, ), ), ), @@ -612,27 +629,16 @@ class _ImageFooterState extends State { _messageController.clear(); - final client = StreamChat.of(context).client; - - for (var user in _selectedUsers) { - var c = client.channel('messaging', extraData: { - 'members': [ - user.id, - StreamChat.of(context).user.id, - ], - }); - - await c.watch(); - + for (var channel in _selectedChannels) { final message = Message( text: text, attachments: [attachments[widget.currentPage]], ); - await c.sendMessage(message); + await channel.sendMessage(message); } - _selectedUsers.clear(); + _selectedChannels.clear(); Navigator.pop(context); } diff --git a/lib/src/image_group.dart b/lib/src/image_group.dart index 44c15623..7bce22f9 100644 --- a/lib/src/image_group.dart +++ b/lib/src/image_group.dart @@ -83,7 +83,9 @@ class ImageGroup extends StatelessWidget { child: Text( '${images.length - 4} +', style: TextStyle( - color: Colors.white, + color: StreamChatTheme.of(context) + .colorTheme + .white, fontSize: 28, ), ), diff --git a/lib/src/image_header.dart b/lib/src/image_header.dart index 62c3f8ec..a9f1173f 100644 --- a/lib/src/image_header.dart +++ b/lib/src/image_header.dart @@ -50,7 +50,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { leading: showBackButton ? IconButton( icon: StreamSvgIcon.close( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, size: 24.0, ), onPressed: onBackPressed, @@ -61,7 +61,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { actions: [ IconButton( icon: StreamSvgIcon.Icon_menu_point_v( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), onPressed: () { _showMessageActionModalBottomSheet(context); @@ -81,11 +81,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { children: [ Text( userName, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w700, - color: Colors.black, - ), + style: StreamChatTheme.of(context).textTheme.headlineBold, ), Text( sentAt, diff --git a/lib/src/media_list_view.dart b/lib/src/media_list_view.dart index 5f52e9b5..08ebfcc7 100644 --- a/lib/src/media_list_view.dart +++ b/lib/src/media_list_view.dart @@ -1,10 +1,13 @@ +import 'dart:ui' as ui; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; -import 'dart:ui' as ui; + +import '../stream_chat_flutter.dart'; extension on Duration { String format() { @@ -80,7 +83,10 @@ class _MediaListViewState extends State { ? 1.0 : 0.0, child: Container( - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), alignment: Alignment.topRight, padding: const EdgeInsets.only( top: 8, @@ -88,10 +94,12 @@ class _MediaListViewState extends State { ), child: CircleAvatar( radius: 12, - backgroundColor: Colors.white, + backgroundColor: + StreamChatTheme.of(context).colorTheme.white, child: StreamSvgIcon.check( size: 24, - color: Colors.black, + color: + StreamChatTheme.of(context).colorTheme.black, ), ), ), @@ -113,7 +121,7 @@ class _MediaListViewState extends State { child: Text( media.videoDuration.format(), style: TextStyle( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, ), ), ), @@ -141,7 +149,15 @@ class _MediaListViewState extends State { void _getMedia() async { final assetList = await PhotoManager.getAssetPathList( hasAll: true, - ).then((value) => value.singleWhere((element) => element.isAll)); + ).then((value) { + if (value?.isNotEmpty == true) { + return value.singleWhere((element) => element.isAll); + } + }); + + if (assetList == null) { + return; + } final media = await assetList.getAssetListPaged(_currentPage, 50); diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart index afdea14e..a57096db 100644 --- a/lib/src/message_actions_modal.dart +++ b/lib/src/message_actions_modal.dart @@ -23,6 +23,7 @@ class MessageActionsModal extends StatelessWidget { final bool showDeleteMessage; final bool showCopyMessage; final bool showEditMessage; + final bool showResendMessage; final bool showReply; final bool showThreadReply; final bool reverse; @@ -40,6 +41,7 @@ class MessageActionsModal extends StatelessWidget { this.onThreadReplyTap, this.showCopyMessage = true, this.showReply = true, + this.showResendMessage = true, this.showThreadReply = true, this.showUserAvatar = DisplayWidget.show, this.editMessageInputBuilder, @@ -73,7 +75,7 @@ class MessageActionsModal extends StatelessWidget { sigmaY: 10.8731, ), child: Container( - color: Colors.black.withOpacity(0.1), + color: Colors.black.withOpacity(.2), ), ), ), @@ -122,7 +124,10 @@ class MessageActionsModal extends StatelessWidget { showUserAvatar: showUserAvatar, showTimestamp: false, translateUserAvatar: false, - showReactionPickerIndicator: true, + showReactionPickerIndicator: showReactions && + (message.status == + MessageSendingStatus.SENT || + message.status == null), showInChannelIndicator: false, showSendingIndicator: DisplayWidget.gone, shape: messageShape, @@ -130,9 +135,7 @@ class MessageActionsModal extends StatelessWidget { ), ); }), - SizedBox( - height: 8, - ), + SizedBox(height: 8), TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: Duration(milliseconds: 300), @@ -148,6 +151,9 @@ class MessageActionsModal extends StatelessWidget { horizontal: 48.0, ), child: Material( + color: StreamChatTheme.of(context) + .colorTheme + .whiteSnow, clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -166,10 +172,12 @@ class MessageActionsModal extends StatelessWidget { _buildReplyButton(context), if (showThreadReply && (message.status == - MessageSendingStatus.SENT || + MessageSendingStatus.SENT || message.status == null) && message.parentId == null) _buildThreadReplyButton(context), + if (showResendMessage) + _buildResendMessage(context), if (showEditMessage) _buildEditMessage(context), if (showDeleteMessage) @@ -212,9 +220,10 @@ class MessageActionsModal extends StatelessWidget { } Widget _buildDeleteButton(BuildContext context) { + final isDeleteFailed = message.status == MessageSendingStatus.FAILED_DELETE; return ListTile( title: Text( - 'Delete message', + isDeleteFailed ? 'Retry deleting message' : 'Delete message', style: Theme.of(context).textTheme.headline6.copyWith(color: Colors.red), ), @@ -263,6 +272,29 @@ class MessageActionsModal extends StatelessWidget { ); } + Widget _buildResendMessage(BuildContext context) { + final isUpdateFailed = message.status == MessageSendingStatus.FAILED_UPDATE; + return ListTile( + title: Text( + isUpdateFailed ? 'Resend edited message' : 'Resend', + style: Theme.of(context).textTheme.headline6, + ), + leading: StreamSvgIcon.circle_up( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + ), + onTap: () { + Navigator.pop(context); + final client = StreamChat.of(context).client; + final channel = StreamChannel.of(context).channel; + if (isUpdateFailed) { + client.updateMessage(message, channel.cid); + } else { + channel.sendMessage(message); + } + }, + ); + } + void _showEditBottomSheet(BuildContext context) { final channel = StreamChannel.of(context).channel; showModalBottomSheet( @@ -270,6 +302,7 @@ class MessageActionsModal extends StatelessWidget { elevation: 2, clipBehavior: Clip.hardEdge, isScrollControlled: true, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(32), @@ -330,9 +363,10 @@ class MessageActionsModal extends StatelessWidget { ? editMessageInputBuilder(context, message) : MessageInput( editMessage: message, - onMessageSent: (_) { + preMessageSending: (m) { FocusScope.of(context).unfocus(); Navigator.pop(context); + return m; }, ), ), diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 38814c2a..c9496a45 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -305,7 +305,10 @@ class MessageInputState extends State { border: _sendAsDm ? null : Border.all( - color: Colors.black.withOpacity(.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), width: 2, ), borderRadius: BorderRadius.circular(3), @@ -314,8 +317,8 @@ class MessageInputState extends State { child: Material( borderRadius: BorderRadius.circular(3), color: _sendAsDm - ? StreamChatTheme.of(context).accentColor - : Colors.white, + ? StreamChatTheme.of(context).colorTheme.accentBlue + : StreamChatTheme.of(context).colorTheme.white, child: InkWell( onTap: () { setState(() { @@ -330,7 +333,7 @@ class MessageInputState extends State { : CrossFadeState.showSecond, firstChild: StreamSvgIcon.check( size: 16.0, - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, ), secondChild: SizedBox( height: 16, @@ -376,7 +379,7 @@ class MessageInputState extends State { }); }, icon: StreamSvgIcon.emptyCircleLeft( - color: StreamChatTheme.of(context).accentColor, + color: StreamChatTheme.of(context).colorTheme.accentBlue, ), padding: const EdgeInsets.all(0), constraints: BoxConstraints.tightFor( @@ -409,7 +412,7 @@ class MessageInputState extends State { decoration: BoxDecoration( borderRadius: BorderRadius.circular(24.0), border: Border.all( - color: Colors.black.withOpacity(0.16), + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ), ), padding: _attachments.isEmpty ? null : EdgeInsets.all(6.0), @@ -458,8 +461,9 @@ class MessageInputState extends State { padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Chip( - backgroundColor: - StreamChatTheme.of(context).accentColor, + backgroundColor: StreamChatTheme.of(context) + .colorTheme + .accentBlue, padding: EdgeInsets.zero, labelPadding: EdgeInsets.symmetric(horizontal: 8.0), @@ -468,13 +472,20 @@ class MessageInputState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ StreamSvgIcon.lightning( - color: Colors.white, + color: StreamChatTheme.of(context) + .colorTheme + .white, size: 16.0, ), Text( _chosenCommand?.name?.toUpperCase() ?? '', - style: TextStyle( - color: Colors.white, fontSize: 12.0), + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .white), ), ], ), @@ -643,12 +654,12 @@ class MessageInputState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), - color: StreamChatTheme.of(context).primaryColor, + color: StreamChatTheme.of(context).colorTheme.white, clipBehavior: Clip.antiAlias, child: Container( constraints: BoxConstraints.loose(Size.fromHeight(400)), decoration: BoxDecoration( - color: StreamChatTheme.of(context).primaryColor, + color: StreamChatTheme.of(context).colorTheme.white, borderRadius: BorderRadius.circular(8.0)), child: ListView( padding: const EdgeInsets.all(0), @@ -666,13 +677,17 @@ class MessageInputState extends State { ), child: StreamSvgIcon.lightning( color: StreamChatTheme.of(context) - .accentColor, + .colorTheme + .accentBlue, ), ), Text( 'Instant Commands', style: TextStyle( - color: Colors.black.withOpacity(.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), ), ) ], @@ -699,15 +714,6 @@ class MessageInputState extends State { ], ), ), - trailing: CircleAvatar( - backgroundColor: - StreamChatTheme.of(context).accentColor, - child: StreamSvgIcon.lightning( - color: Colors.white, - size: 12.5, - ), - maxRadius: 12, - ), //subtitle: Text(c.description), onTap: () { _setCommand(c); @@ -734,27 +740,36 @@ class MessageInputState extends State { switch (index) { case 0: return _attachments.isEmpty - ? StreamChatTheme.of(context).accentColor + ? StreamChatTheme.of(context).colorTheme.accentBlue : (!_attachmentContainsFile - ? StreamChatTheme.of(context).accentColor - : Colors.black.withOpacity(0.2)); + ? StreamChatTheme.of(context).colorTheme.accentBlue + : StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.2)); break; case 1: return _attachmentContainsFile - ? StreamChatTheme.of(context).accentColor + ? StreamChatTheme.of(context).colorTheme.accentBlue : (_attachments.isEmpty - ? Colors.black.withOpacity(0.5) - : Colors.black.withOpacity(0.2)); + ? StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5) + : StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.2)); break; case 2: return _attachmentContainsFile && _attachments.isNotEmpty - ? Colors.black.withOpacity(0.2) - : Colors.black.withOpacity(0.5); + ? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) + : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); break; case 3: return _attachmentContainsFile && _attachments.isNotEmpty - ? Colors.black.withOpacity(0.2) - : Colors.black.withOpacity(0.5); + ? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) + : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); break; } } @@ -763,7 +778,7 @@ class MessageInputState extends State { duration: _animateContainer ? Duration(milliseconds: 300) : Duration.zero, height: _openFilePickerSection ? _filePickerSize : 0, child: Material( - color: Color(0xFFF2F2F2), + color: StreamChatTheme.of(context).colorTheme.whiteSmoke, child: Column( children: [ Row( @@ -830,7 +845,7 @@ class MessageInputState extends State { }, child: Container( decoration: BoxDecoration( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0), @@ -845,7 +860,8 @@ class MessageInputState extends State { width: 40.0, height: 4.0, decoration: BoxDecoration( - color: Color(0xFFF2F2F2), + color: + StreamChatTheme.of(context).colorTheme.whiteSmoke, borderRadius: BorderRadius.circular(4.0), ), ), @@ -858,7 +874,7 @@ class MessageInputState extends State { Expanded( child: Container( decoration: BoxDecoration( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, borderRadius: BorderRadius.circular(8.0), ), child: _buildPickerSection(), @@ -893,11 +909,12 @@ class MessageInputState extends State { }, child: Container( constraints: BoxConstraints.expand(), - color: Color(0xfff2f2f2), + color: StreamChatTheme.of(context).colorTheme.whiteSmoke, child: Text( 'Add more files', style: TextStyle( - color: StreamChatTheme.of(context).accentColor, + color: + StreamChatTheme.of(context).colorTheme.accentBlue, fontWeight: FontWeight.bold, ), ), @@ -926,7 +943,7 @@ class MessageInputState extends State { PhotoManager.openSetting(); }, child: Container( - color: Color(0xFFF2F2F2), + color: StreamChatTheme.of(context).colorTheme.whiteSmoke, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -935,16 +952,20 @@ class MessageInputState extends State { 'svgs/icon_picture_empty_state.svg', package: 'stream_chat_flutter', height: 140, - color: StreamChatTheme.of(context).accentColor, + color: + StreamChatTheme.of(context).colorTheme.accentBlue, ), Center( child: Text( 'Allow access to your gallery', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: StreamChatTheme.of(context).accentColor, - ), + style: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, + ), ), ), ], @@ -1060,7 +1081,7 @@ class MessageInputState extends State { CircleAvatar _buildGiphyIcon() { if (kIsWeb) { return CircleAvatar( - backgroundColor: Colors.black, + backgroundColor: StreamChatTheme.of(context).colorTheme.black, child: Image.asset( 'images/giphy_icon.png', package: 'stream_chat_flutter', @@ -1121,7 +1142,7 @@ class MessageInputState extends State { child: Card( margin: EdgeInsets.all(8.0), elevation: 2.0, - color: StreamChatTheme.of(context).primaryColor, + color: StreamChatTheme.of(context).colorTheme.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), @@ -1129,7 +1150,7 @@ class MessageInputState extends State { child: Container( constraints: BoxConstraints.loose(Size.fromHeight(400)), decoration: BoxDecoration( - color: StreamChatTheme.of(context).primaryColor, + color: StreamChatTheme.of(context).colorTheme.white, ), child: FutureBuilder>( future: queryMembers ?? Future.value(members), @@ -1157,7 +1178,8 @@ class MessageInputState extends State { subtitle: Text('@${m.userId}'), trailing: StreamSvgIcon.mentions( color: StreamChatTheme.of(context) - .accentColor, + .colorTheme + .accentBlue, ), onTap: () { _mentionedUsers.add(m.user); @@ -1222,7 +1244,7 @@ class MessageInputState extends State { child: Card( margin: EdgeInsets.all(8.0), elevation: 2.0, - color: StreamChatTheme.of(context).primaryColor, + color: StreamChatTheme.of(context).colorTheme.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), @@ -1237,7 +1259,7 @@ class MessageInputState extends State { offset: Offset(0, -4), ), ], - color: StreamChatTheme.of(context).primaryColor, + color: StreamChatTheme.of(context).colorTheme.white, ), child: ListView.builder( padding: const EdgeInsets.all(0), @@ -1253,14 +1275,19 @@ class MessageInputState extends State { padding: const EdgeInsets.symmetric(horizontal: 8.0), child: StreamSvgIcon.smile( - color: StreamChatTheme.of(context).accentColor, + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, ), ), Flexible( child: Text( 'Emoji matching "$query"', style: TextStyle( - color: Colors.black.withOpacity(.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), ), ), ) @@ -1373,10 +1400,15 @@ class MessageInputState extends State { child: InkWell( child: CircleAvatar( backgroundColor: - Colors.black.withOpacity(0.2), + StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.6), maxRadius: 12.0, child: StreamSvgIcon.close( - color: Colors.white, + color: StreamChatTheme.of(context) + .colorTheme + .white, ), ), onTap: () { @@ -1461,11 +1493,11 @@ class MessageInputState extends State { _attachments.remove(attachment); }); }, - fillColor: Colors.black.withOpacity(.5), + fillColor: StreamChatTheme.of(context).colorTheme.black.withOpacity(.5), child: Center( child: StreamSvgIcon.close( size: 24, - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, ), ), ), @@ -1553,8 +1585,8 @@ class MessageInputState extends State { child: IconButton( icon: StreamSvgIcon.lightning( color: _commandsOverlay != null - ? StreamChatTheme.of(context).accentColor - : Color(0xFF000000).withAlpha(128), + ? StreamChatTheme.of(context).colorTheme.accentBlue + : StreamChatTheme.of(context).colorTheme.grey, ), padding: const EdgeInsets.all(0), constraints: BoxConstraints.tightFor( @@ -1594,8 +1626,8 @@ class MessageInputState extends State { child: IconButton( icon: StreamSvgIcon.attach( color: _openFilePickerSection - ? StreamChatTheme.of(context).accentColor - : Color(0xFF000000).withAlpha(128), + ? StreamChatTheme.of(context).colorTheme.accentBlue + : StreamChatTheme.of(context).colorTheme.grey, ), padding: const EdgeInsets.all(0), constraints: BoxConstraints.tightFor( @@ -1924,7 +1956,7 @@ class MessageInputState extends State { padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), child: StreamSvgIcon( assetName: _getIdleSendIcon(), - color: Colors.grey, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ), ); } @@ -1943,7 +1975,7 @@ class MessageInputState extends State { ), icon: StreamSvgIcon( assetName: _getSendIcon(), - color: StreamChatTheme.of(context).accentColor, + color: StreamChatTheme.of(context).colorTheme.accentBlue, ), ), ); @@ -2043,10 +2075,6 @@ class MessageInputState extends State { return sendingFuture.then((resp) { if (widget.onMessageSent != null) { widget.onMessageSent(resp.message); - } else { - if (widget.editMessage != null) { - Navigator.pop(context); - } } }); } diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index cac699d9..f5545e50 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -259,16 +259,19 @@ class _MessageListViewState extends State { } final messageList = snapshot.data?.reversed?.toList() ?? []; - if (messageList.isEmpty) { if (_upToDate) { return Center( child: Text( 'No chats here yet...', - style: TextStyle( - fontSize: 12, - color: Colors.black.withOpacity(.5), - ), + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), ), ); } @@ -355,8 +358,12 @@ class _MessageListViewState extends State { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Color(0XFFF7F7F7), - Color(0XFFFCFCFC), + StreamChatTheme.of(context) + .colorTheme + .whiteSmoke, + StreamChatTheme.of(context) + .colorTheme + .whiteSnow, ], ), ), @@ -536,9 +543,9 @@ class _MessageListViewState extends State { clipBehavior: Clip.none, children: [ FloatingActionButton( - backgroundColor: Colors.white, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, child: StreamSvgIcon.down( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), onPressed: () { if (unreadCount > 0) { @@ -598,7 +605,10 @@ class _MessageListViewState extends State { builder: (context, snapshot) { if (snapshot.hasError) { return Container( - color: Color(0xffd0021B).withAlpha(26), + color: StreamChatTheme.of(context) + .colorTheme + .accentRed + .withOpacity(.2), child: Center( child: Text('Error loading messages'), ), @@ -867,7 +877,7 @@ class _MessageListViewState extends State { } if (event.message.user.id == streamChannel.channel.client.state.user.id) { WidgetsBinding.instance.addPostFrameCallback((_) { - _scrollController.jumpTo( + _scrollController?.jumpTo( index: 0, ); }); diff --git a/lib/src/message_reactions_modal.dart b/lib/src/message_reactions_modal.dart index e5043e62..232f6f84 100644 --- a/lib/src/message_reactions_modal.dart +++ b/lib/src/message_reactions_modal.dart @@ -60,7 +60,7 @@ class MessageReactionsModal extends StatelessWidget { sigmaY: 10.8731, ), child: Container( - color: Colors.black.withOpacity(0.1), + color: Colors.black.withOpacity(0.2), ), ), ), @@ -110,14 +110,16 @@ class MessageReactionsModal extends StatelessWidget { translateUserAvatar: false, showSendingIndicator: DisplayWidget.gone, shape: messageShape, - showReactionPickerIndicator: true, + showInChannelIndicator: false, + showReactionPickerIndicator: showReactions && + (message.status == + MessageSendingStatus.SENT || + message.status == null), ), ), ); }), - SizedBox( - height: 16, - ), + SizedBox(height: 8), if (message.latestReactions?.isNotEmpty == true) _buildReactionCard(context), ], @@ -137,6 +139,7 @@ class MessageReactionsModal extends StatelessWidget { horizontal: 8.0, ), child: Card( + color: StreamChatTheme.of(context).colorTheme.white, clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), diff --git a/lib/src/message_search_item.dart b/lib/src/message_search_item.dart index 35b31aa8..851659e1 100644 --- a/lib/src/message_search_item.dart +++ b/lib/src/message_search_item.dart @@ -126,8 +126,6 @@ class MessageSearchItem extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( - color: - StreamChatTheme.of(context).channelPreviewTheme.subtitle.color, fontStyle: (message.isSystem || message.isDeleted) ? FontStyle.italic : FontStyle.normal, diff --git a/lib/src/message_search_list_view.dart b/lib/src/message_search_list_view.dart index 087ae839..d59ae4a0 100644 --- a/lib/src/message_search_list_view.dart +++ b/lib/src/message_search_list_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/message_search_item.dart'; +import '../stream_chat_flutter.dart'; import 'lazy_load_scroll_view.dart'; import 'message_search_bloc.dart'; @@ -127,8 +128,8 @@ class _MessageSearchListViewState extends State { return Container( height: 1, color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(0.1) - : Colors.black.withOpacity(0.1), + ? StreamChatTheme.of(context).colorTheme.white.withOpacity(0.1) + : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.1), ); } @@ -151,7 +152,10 @@ class _MessageSearchListViewState extends State { builder: (context, snapshot) { if (snapshot.hasError) { return Container( - color: Color(0xffd0021B).withAlpha(26), + color: StreamChatTheme.of(context) + .colorTheme + .accentRed + .withOpacity(.2), child: Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: Center( @@ -313,8 +317,14 @@ class _MessageSearchListViewState extends State { begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ - Colors.black.withOpacity(0.02), - Colors.white.withOpacity(0.05), + StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.02), + StreamChatTheme.of(context) + .colorTheme + .white + .withOpacity(0.05), ], stops: [0, 1], ), @@ -327,7 +337,10 @@ class _MessageSearchListViewState extends State { child: Text( '${items.length} results', style: TextStyle( - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), ), ), diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index affcc351..b4472261 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -120,6 +120,9 @@ class MessageWidget extends StatefulWidget { /// Used in [MessageReactionsModal] and [MessageActionsModal] final bool showReactionPickerIndicator; + /// If true the widget will show the resendMessage indicator + final bool showResendMessage; + final List readList; /// If true show the users username next to the timestamp of the message @@ -165,6 +168,7 @@ class MessageWidget extends StatefulWidget { this.editMessageInputBuilder, this.textBuilder, Map customAttachmentBuilders, + this.showResendMessage = true, this.readList, this.padding, this.textPadding = const EdgeInsets.symmetric( @@ -232,13 +236,29 @@ class _MessageWidgetState extends State { bool get showTimeStamp => widget.message.createdAt != null && widget.showTimestamp; - bool get showReadList => widget.readList?.isNotEmpty == true; + bool get isMessageRead => widget.readList?.isNotEmpty == true; bool get showInChannel => widget.showInChannelIndicator && widget.message?.showInChannel == true; bool get _hasReplyToMessage => widget.message?.replyToMessage != null; + bool get isSendFailed => widget.message.status == MessageSendingStatus.FAILED; + + bool get isUpdateFailed => + widget.message.status == MessageSendingStatus.FAILED_UPDATE; + + bool get isDeleteFailed => + widget.message.status == MessageSendingStatus.FAILED_DELETE; + + bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed; + + bool get showBottomRow => + showThreadReplyIndicator || + showUsername || + showTimeStamp || + showInChannel; + @override Widget build(BuildContext context) { var leftPadding = widget.showUserAvatar != DisplayWidget.gone @@ -273,6 +293,7 @@ class _MessageWidgetState extends State { mainAxisSize: MainAxisSize.min, children: [ Stack( + clipBehavior: Clip.none, alignment: AlignmentDirectional.bottomStart, children: [ Column( @@ -317,97 +338,90 @@ class _MessageWidgetState extends State { : 0, ) : EdgeInsets.zero, - child: - (widget.message.isDeleted && - widget.message.status != - MessageSendingStatus - .FAILED_DELETE) - ? Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY( - widget.reverse ? pi : 0), - child: DeletedMessage( - reverse: widget.reverse, - borderRadiusGeometry: widget - .borderRadiusGeometry, - borderSide: - widget.borderSide, - shape: widget.shape, - messageTheme: - widget.messageTheme, - ), - ) - : GestureDetector( - onTap: () => - retryMessage(context), - onLongPress: () => - onLongPress(context), - child: Material( - clipBehavior: - Clip.antiAlias, - shape: widget.shape ?? - RoundedRectangleBorder( - side: isOnlyEmoji && - !_hasReplyToMessage - ? BorderSide.none - : widget.borderSide ?? - BorderSide( - color: Theme.of(context).brightness == - Brightness - .dark - ? Colors - .white - .withAlpha( - 24) - : Colors - .black - .withAlpha(24), - ), - borderRadius: widget - .borderRadiusGeometry ?? - BorderRadius.zero, - ), - color: - _getBackgroundColor(), - child: Padding( - padding: EdgeInsets.all( - hasFiles ? 2.0 : 0.0), - child: Column( - crossAxisAlignment: - CrossAxisAlignment - .start, - mainAxisSize: - MainAxisSize.min, - children: [ - if (_hasReplyToMessage) - ReplyToMessageWidget( - message: widget - .message - .replyToMessage, - messageTheme: isMyMessage + child: (widget.message.isDeleted && + !isFailedState) + ? Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY( + widget.reverse ? pi : 0), + child: DeletedMessage( + reverse: widget.reverse, + borderRadiusGeometry: + widget.borderRadiusGeometry, + borderSide: widget.borderSide, + shape: widget.shape, + messageTheme: + widget.messageTheme, + ), + ) + : GestureDetector( + onLongPress: () => + onLongPress(context), + child: Material( + clipBehavior: Clip.antiAlias, + shape: widget.shape ?? + RoundedRectangleBorder( + side: isOnlyEmoji + ? BorderSide.none + : widget.borderSide ?? + BorderSide( + color: Theme.of(context) + .brightness == + Brightness + .dark ? StreamChatTheme.of( context) - .otherMessageTheme + .colorTheme + .white + .withAlpha( + 24) : StreamChatTheme.of( context) - .ownMessageTheme, - reverse: widget - .reverse, + .colorTheme + .black + .withAlpha( + 24), ), - ..._parseAttachments( - context), - if (widget - .message.text - .trim() - .isNotEmpty && - !isGiphy) - _buildTextBubble( - context), - ], - ), - ), + borderRadius: widget + .borderRadiusGeometry ?? + BorderRadius.zero, ), + color: _getBackgroundColor(), + child: Padding( + padding: EdgeInsets.all( + hasFiles ? 2.0 : 0.0), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisSize: + MainAxisSize.min, + children: [ + if (_hasReplyToMessage) + ReplyToMessageWidget( + message: widget + .message + .replyToMessage, + messageTheme: isMyMessage + ? StreamChatTheme.of( + context) + .otherMessageTheme + : StreamChatTheme.of( + context) + .ownMessageTheme, + reverse: widget + .reverse, + ), + ..._parseAttachments( + context), + if (widget.message.text + .trim() + .isNotEmpty && + !isGiphy) + _buildTextBubble(context), + ], ), + ), + ),), ), if (widget.showReactionPickerIndicator) Positioned( @@ -432,18 +446,23 @@ class _MessageWidgetState extends State { ), ], ), - if (showThreadReplyIndicator || - showUsername || - showTimeStamp || - showInChannel) - SizedBox(height: 20.0), + if (showBottomRow) SizedBox(height: 20.0), ], ), - if (showThreadReplyIndicator || - showUsername || - showTimeStamp || - showInChannel) - _buildBottomRow(leftPadding) + if (showBottomRow) _buildBottomRow(leftPadding), + if (isFailedState) + Positioned( + left: widget.reverse ? -3 : null, + right: widget.reverse ? null : -9, + bottom: showBottomRow ? 20 : 0, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: StreamSvgIcon.error(size: 20), + ), + ), ], ), ], @@ -463,16 +482,18 @@ class _MessageWidgetState extends State { mainAxisSize: MainAxisSize.min, children: [ StreamSvgIcon.eye( - color: Colors.black.withOpacity(0.5), + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), size: 16.0, ), SizedBox(width: 8.0), Text( 'Only visible to you', - style: TextStyle( - color: Colors.black.withOpacity(0.5), - fontSize: 12.0, - ), + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), ), ], ), @@ -510,14 +531,14 @@ class _MessageWidgetState extends State { children.addAll([ if (showSendingIndicator) _buildSendingIndicator(), - if (showReadList) - SizedBox.fromSize( - size: Size((widget.readList.length * 10.0) + 10, 17), - child: Padding( - padding: const EdgeInsets.only(left: 4.0), - child: _buildReadIndicator(), - ), - ), + // if (showReadList) + // SizedBox.fromSize( + // size: Size((widget.readList.length * 10.0) + 10, 17), + // child: Padding( + // padding: const EdgeInsets.only(left: 4.0), + // child: _buildReadIndicator(), + // ), + // ), if (showThreadReplyIndicator) InkWell( onTap: widget.onThreadTap != null ? onThreadTap : null, @@ -551,6 +572,7 @@ class _MessageWidgetState extends State { child: CustomPaint( size: const Size(16, 32), painter: _ThreadReplyPainter( + context: context, color: widget.messageTheme.replyThreadColor, ), ), @@ -599,7 +621,7 @@ class _MessageWidgetState extends State { bottom: 0, top: 0, child: Material( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, clipBehavior: Clip.antiAlias, shape: CircleBorder(), child: Padding( @@ -669,15 +691,20 @@ class _MessageWidgetState extends State { editMessageInputBuilder: widget.editMessageInputBuilder, onReplyTap: widget.onReplyTap, onThreadReplyTap: widget.onThreadTap, - showCopyMessage: widget.message.text?.trim()?.isNotEmpty == true, - showEditMessage: widget.showEditMessage && + showResendMessage: + widget.showResendMessage && (isSendFailed || isUpdateFailed), + showCopyMessage: !isFailedState && + widget.message.text?.trim()?.isNotEmpty == true, showEditMessage: widget.showEditMessage && + !isDeleteFailed && widget.message.attachments ?.any((element) => element.type == 'giphy') != true, showReactions: widget.showReactions, - showReply: widget.showReplyIndicator, + showReply: widget.showReplyIndicator && + !isFailedState && + widget.onReplyTap != null, showThreadReply: - widget.showThreadReplyIndicator && widget.onThreadTap != null, + widget.showThreadReplyIndicator && widget.onThreadTap != null, ), ); }); @@ -714,8 +741,8 @@ class _MessageWidgetState extends State { widget.borderSide ?? BorderSide( color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withAlpha(24) - : Colors.black.withAlpha(24), + ? StreamChatTheme.of(context).colorTheme.white.withAlpha(24) + : StreamChatTheme.of(context).colorTheme.black.withAlpha(24), ), borderRadius: widget.attachmentBorderRadiusGeometry ?? widget.borderRadiusGeometry ?? @@ -780,32 +807,22 @@ class _MessageWidgetState extends State { final attachmentShape = widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context); return Material( - color: _getBackgroundColor(), - clipBehavior: Clip.antiAlias, - shape: attachmentShape, - child: Padding( - padding: widget.attachmentPadding, - child: Material( - clipBehavior: Clip.hardEdge, - shape: attachmentShape, - type: MaterialType.transparency, - child: Transform( - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - alignment: Alignment.center, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - getFailedMessageWidget( - context, - padding: const EdgeInsets.all(8.0), - ), - attachmentWidget, - ], + color: _getBackgroundColor(), + clipBehavior: Clip.antiAlias, + shape: attachmentShape, + child: Padding( + padding: widget.attachmentPadding, + child: Material( + clipBehavior: Clip.hardEdge, + shape: attachmentShape, + type: MaterialType.transparency, + child: Transform( + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + alignment: Alignment.center, + child: attachmentWidget, ), ), ), - ), ); } @@ -824,14 +841,27 @@ class _MessageWidgetState extends State { } Widget _buildSendingIndicator() { - return Container( - height: widget.messageTheme.createdAt.fontSize + 2, - width: widget.messageTheme.createdAt.fontSize + 2, - child: SendingIndicator( - message: widget.message, - allRead: widget.allRead, - ), + final style = widget.messageTheme.createdAt; + Widget child = SendingIndicator( + message: widget.message, + isMessageRead: isMessageRead, + size: style.fontSize, ); + if (isMessageRead) { + child = Row( + children: [ + Text( + widget.readList.length.toString(), + style: style.copyWith( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + ), + ), + SizedBox(width: 2), + child, + ], + ); + } + return child; } Widget _buildUserAvatar() => Transform( @@ -856,55 +886,6 @@ class _MessageWidgetState extends State { ), ); - Widget getFailedMessageWidget( - BuildContext context, { - EdgeInsetsGeometry padding, - }) { - Widget failedWidget; - if (widget.message.status == MessageSendingStatus.FAILED) { - failedWidget = Text( - 'MESSAGE FAILED · CLICK TO TRY AGAIN', - style: widget.messageTheme.messageText.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(.5) - : Colors.black.withOpacity(.5), - fontSize: 11, - ), - ); - } - if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) { - failedWidget = Text( - 'MESSAGE UPDATE FAILED · CLICK TO TRY AGAIN', - style: widget.messageTheme.messageText.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(.5) - : Colors.black.withOpacity(.5), - fontSize: 11, - ), - ); - } - if (widget.message.status == MessageSendingStatus.FAILED_DELETE) { - failedWidget = Text( - 'MESSAGE DELETE FAILED · CLICK TO TRY AGAIN', - style: widget.messageTheme.messageText.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(.5) - : Colors.black.withOpacity(.5), - fontSize: 11, - ), - ); - } - - if (failedWidget != null) { - return Padding( - padding: padding ?? EdgeInsets.zero, - child: failedWidget, - ); - } - - return SizedBox(); - } - Widget _buildTextBubble(BuildContext context) { return Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), @@ -914,26 +895,20 @@ class _MessageWidgetState extends State { children: [ Padding( padding: widget.textPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - getFailedMessageWidget(context), - widget.textBuilder != null - ? widget.textBuilder(context, widget.message) - : MessageText( - onLinkTap: widget.onLinkTap, - message: widget.message, - onMentionTap: widget.onMentionTap, - messageTheme: isOnlyEmoji - ? widget.messageTheme.copyWith( - messageText: - widget.messageTheme.messageText.copyWith( - fontSize: 40, - )) - : widget.messageTheme, - ), - ], - ), + child: widget.textBuilder != null + ? widget.textBuilder(context, widget.message) + : MessageText( + onLinkTap: widget.onLinkTap, + message: widget.message, + onMentionTap: widget.onMentionTap, + messageTheme: isOnlyEmoji + ? widget.messageTheme.copyWith( + messageText: + widget.messageTheme.messageText.copyWith( + fontSize: 40, + )) + : widget.messageTheme, + ), ), if (widget.message.attachments ?.any((element) => element.ogScrapeUrl != null) == @@ -954,16 +929,10 @@ class _MessageWidgetState extends State { return widget.messageTheme.messageBackgroundColor; } - if ((widget.message.status == MessageSendingStatus.FAILED || - widget.message.status == MessageSendingStatus.FAILED_UPDATE || - widget.message.status == MessageSendingStatus.FAILED_DELETE)) { - return Color(0xffd0021B).withOpacity(.1); - } - if (widget.message.attachments ?.any((element) => element.ogScrapeUrl != null) == true) { - return Color(0xFFE9F2FF); + return StreamChatTheme.of(context).colorTheme.blueAlice; } if (isOnlyEmoji) { @@ -1003,13 +972,14 @@ class _MessageWidgetState extends State { class _ThreadReplyPainter extends CustomPainter { final Color color; + final BuildContext context; - const _ThreadReplyPainter({@required this.color}); + const _ThreadReplyPainter({this.context, @required this.color}); @override void paint(Canvas canvas, Size size) { final paint = Paint() - ..color = color ?? Color(0XFFDBDBDB) + ..color = color ?? StreamChatTheme.of(context).colorTheme.greyGainsboro ..style = PaintingStyle.stroke ..strokeWidth = 1 ..strokeCap = StrokeCap.round; diff --git a/lib/src/reaction_bubble.dart b/lib/src/reaction_bubble.dart index 87dc7a92..d8f8c623 100644 --- a/lib/src/reaction_bubble.dart +++ b/lib/src/reaction_bubble.dart @@ -107,16 +107,22 @@ class ReactionBubble extends StatelessWidget { height: 16, color: (!highlightOwnReactions || reaction.user.id == StreamChat.of(context).user.id) - ? StreamChatTheme.of(context).accentColor - : Colors.black.withOpacity(.5), + ? StreamChatTheme.of(context).colorTheme.accentBlue + : StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), ) : Icon( Icons.help_outline_rounded, size: 16, color: (!highlightOwnReactions || reaction.user.id == StreamChat.of(context).user.id) - ? StreamChatTheme.of(context).accentColor - : Colors.black.withOpacity(.5), + ? StreamChatTheme.of(context).colorTheme.accentBlue + : StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), ), ); } diff --git a/lib/src/reaction_picker.dart b/lib/src/reaction_picker.dart index b019ee79..5892a66d 100644 --- a/lib/src/reaction_picker.dart +++ b/lib/src/reaction_picker.dart @@ -89,7 +89,9 @@ class _ReactionPickerState extends State height: animations[index].value * 24.0, width: animations[index].value * 24.0, color: ownReactionIndex != -1 - ? StreamChatTheme.of(context).accentColor + ? StreamChatTheme.of(context) + .colorTheme + .accentBlue : Theme.of(context) .iconTheme .color diff --git a/lib/src/sending_indicator.dart b/lib/src/sending_indicator.dart index ce8915c3..e3a9df06 100644 --- a/lib/src/sending_indicator.dart +++ b/lib/src/sending_indicator.dart @@ -4,27 +4,29 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Used to show the sending status of the message class SendingIndicator extends StatelessWidget { final Message message; - final bool allRead; + final bool isMessageRead; + final double size; const SendingIndicator({ Key key, this.message, - this.allRead = false, + this.isMessageRead = false, + this.size = 12, }) : super(key: key); @override Widget build(BuildContext context) { - if (allRead) { + if (isMessageRead) { return Icon( - Icons.done_all, - size: 8, - color: StreamChatTheme.of(context).accentColor, + Icons.done_all_rounded, + size: size, + color: StreamChatTheme.of(context).colorTheme.accentBlue, ); } if (message.status == MessageSendingStatus.SENT || message.status == null) { return Icon( Icons.done, - size: 8, + size: size, color: IconTheme.of(context).color.withOpacity(0.5), ); } @@ -32,18 +34,9 @@ class SendingIndicator extends StatelessWidget { message.status == MessageSendingStatus.UPDATING) { return Icon( Icons.access_time, - size: 8, + size: size, ); } - if (message.status == MessageSendingStatus.FAILED || - message.status == MessageSendingStatus.FAILED_UPDATE || - message.status == MessageSendingStatus.FAILED_DELETE) { - return Icon( - Icons.error_outline, - size: 8, - ); - } - return SizedBox(); } } diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 2763db37..6866b0dd 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -76,8 +76,8 @@ class StreamChatState extends State with WidgetsBindingObserver { return Theme( data: materialTheme.copyWith( primaryIconTheme: streamTheme.primaryIconTheme, - accentColor: streamTheme.accentColor, - scaffoldBackgroundColor: streamTheme.backgroundColor, + accentColor: streamTheme.colorTheme.accentBlue, + scaffoldBackgroundColor: streamTheme.colorTheme.white, ), child: widget.child, ); @@ -92,11 +92,9 @@ class StreamChatState extends State with WidgetsBindingObserver { ) { final defaultTheme = StreamChatThemeData.getDefaultTheme(Theme.of(context)); final theme = defaultTheme.copyWith( - primaryColor: themeData?.primaryColor, defaultChannelImage: themeData?.defaultChannelImage, primaryIconTheme: themeData?.primaryIconTheme, defaultUserImage: themeData?.defaultUserImage, - backgroundColor: themeData?.backgroundColor, channelTheme: defaultTheme.channelTheme.copyWith( channelHeaderTheme: defaultTheme.channelTheme.channelHeaderTheme.copyWith( @@ -151,8 +149,6 @@ class StreamChatState extends State with WidgetsBindingObserver { reactionsBackgroundColor: themeData?.otherMessageTheme?.reactionsBackgroundColor, ), - accentColor: themeData?.accentColor, - secondaryColor: themeData?.secondaryColor, channelPreviewTheme: defaultTheme.channelPreviewTheme.copyWith( avatarTheme: defaultTheme.channelPreviewTheme.avatarTheme.copyWith( constraints: themeData?.channelPreviewTheme?.avatarTheme?.constraints, diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index a0f552c4..60e37b80 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -42,17 +42,11 @@ class StreamChatTheme extends InheritedWidget { /// Theme data class StreamChatThemeData { - /// Primary color of the chat widgets - final Color primaryColor; + /// The text themes used in the widgets + final TextTheme textTheme; - /// Secondary color of the chat widgets - final Color secondaryColor; - - /// Accent color of the chat widgets - final Color accentColor; - - /// Background color of the chat widgets - final Color backgroundColor; + /// The text themes used in the widgets + final ColorTheme colorTheme; /// Theme of the [ChannelPreview] final ChannelPreviewTheme channelPreviewTheme; @@ -80,10 +74,8 @@ class StreamChatThemeData { /// Create a theme from scratch StreamChatThemeData({ - this.primaryColor, - this.secondaryColor, - this.accentColor, - this.backgroundColor, + this.textTheme, + this.colorTheme, this.channelPreviewTheme, this.channelTheme, this.otherMessageTheme, @@ -99,11 +91,7 @@ class StreamChatThemeData { final defaultTheme = getDefaultTheme(theme); return defaultTheme.copyWith( - accentColor: theme.accentColor, primaryIconTheme: theme.primaryIconTheme, - primaryColor: theme.colorScheme.primary, - secondaryColor: theme.colorScheme.secondary, - backgroundColor: theme.scaffoldBackgroundColor, channelTheme: defaultTheme.channelTheme.copyWith( inputGradient: LinearGradient(colors: [ theme.accentColor.withOpacity(.5), @@ -131,10 +119,8 @@ class StreamChatThemeData { /// Creates a copy of [StreamChatThemeData] with specified attributes overridden. StreamChatThemeData copyWith({ - Color primaryColor, - Color secondaryColor, - Color accentColor, - Color backgroundColor, + TextTheme textTheme, + ColorTheme colorTheme, ChannelPreviewTheme channelPreviewTheme, ChannelTheme channelTheme, MessageTheme ownMessageTheme, @@ -145,24 +131,35 @@ class StreamChatThemeData { List reactionIcons, }) => StreamChatThemeData( - primaryColor: primaryColor ?? this.primaryColor, - secondaryColor: secondaryColor ?? this.secondaryColor, + 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, + ), primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme, - accentColor: accentColor ?? this.accentColor, defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage, defaultUserImage: defaultUserImage ?? this.defaultUserImage, - backgroundColor: backgroundColor ?? this.backgroundColor, - channelPreviewTheme: channelPreviewTheme?.copyWith( - title: - channelPreviewTheme.title ?? this.channelPreviewTheme.title, - subtitle: channelPreviewTheme.subtitle ?? - this.channelPreviewTheme.subtitle, - lastMessageAt: channelPreviewTheme.lastMessageAt ?? - this.channelPreviewTheme.lastMessageAt, - avatarTheme: channelPreviewTheme.avatarTheme ?? - this.channelPreviewTheme.avatarTheme, - ) ?? - this.channelPreviewTheme, + 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, @@ -222,14 +219,13 @@ class StreamChatThemeData { static StreamChatThemeData getDefaultTheme(ThemeData theme) { final accentColor = Color(0xff006cff); final isDark = theme.brightness == Brightness.dark; + final textTheme = isDark ? TextTheme.dark() : TextTheme.light(); + final colorTheme = isDark ? ColorTheme.dark() : ColorTheme.light(); return StreamChatThemeData( - secondaryColor: Color(0xffEAEAEA), - accentColor: accentColor, - primaryColor: isDark ? Colors.black : Colors.white, - primaryIconTheme: IconThemeData( - color: isDark ? Colors.white : Colors.black.withOpacity(.5)), + textTheme: textTheme, + colorTheme: colorTheme, + primaryIconTheme: IconThemeData(color: colorTheme.black.withOpacity(.5)), defaultChannelImage: (context, channel) => SizedBox(), - backgroundColor: isDark ? Colors.black : Colors.white, defaultUserImage: (context, user) => Center( child: CachedNetworkImage( filterQuality: FilterQuality.high, @@ -238,7 +234,7 @@ class StreamChatThemeData { ), ), channelPreviewTheme: ChannelPreviewTheme( - unreadCounterColor: Color(0xffff3742), + unreadCounterColor: colorTheme.accentRed, avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), constraints: BoxConstraints.tightFor( @@ -246,19 +242,12 @@ class StreamChatThemeData { width: 40, ), ), - title: TextStyle( - fontSize: 14, - color: isDark ? Colors.white : Colors.black, - fontWeight: FontWeight.bold), - subtitle: TextStyle( - fontSize: 12.5, - color: (isDark ? Colors.white : Colors.black).withOpacity(0.5), + title: textTheme.bodyBold, + subtitle: textTheme.footnote.copyWith( + color: Color(0xff7A7A7A), ), - lastMessageAt: TextStyle( - fontSize: 11, - color: isDark - ? Colors.white.withOpacity(.5) - : Colors.black.withOpacity(.5), + lastMessageAt: textTheme.footnote.copyWith( + color: colorTheme.black.withOpacity(.5), ), ), channelTheme: ChannelTheme( @@ -273,20 +262,17 @@ class StreamChatThemeData { width: 40, ), ), - color: isDark ? Colors.black : Colors.white, + color: colorTheme.white, title: TextStyle( fontSize: 14, - color: isDark ? Colors.white : Colors.black, + color: colorTheme.black, ), lastMessageAt: TextStyle( fontSize: 11, - color: isDark - ? Colors.white.withOpacity(.5) - : Colors.black.withOpacity(.5), + color: colorTheme.black.withOpacity(.5), ), ), - inputBackground: - isDark ? Colors.black.withAlpha(12) : Colors.white.withAlpha(12), + inputBackground: colorTheme.white.withAlpha(12), inputGradient: LinearGradient(colors: [ Color(0xFF00AEFF), Color(0xFF0076FF), @@ -295,12 +281,10 @@ class StreamChatThemeData { ownMessageTheme: MessageTheme( messageText: TextStyle( fontSize: 14.5, - color: isDark ? Colors.white : Colors.black, + color: colorTheme.black, ), createdAt: TextStyle( - color: isDark - ? Colors.white.withOpacity(.5) - : Colors.black.withOpacity(.5), + color: colorTheme.black.withOpacity(.5), fontSize: 12, ), replies: TextStyle( @@ -308,10 +292,10 @@ class StreamChatThemeData { fontWeight: FontWeight.w600, fontSize: 12, ), - messageBackgroundColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA), - reactionsBackgroundColor: isDark ? Colors.black : Colors.white, - reactionsBorderColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA), - replyThreadColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA), + messageBackgroundColor: colorTheme.greyGainsboro, + reactionsBackgroundColor: colorTheme.white, + reactionsBorderColor: colorTheme.greyWhisper, + replyThreadColor: colorTheme.greyWhisper, avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), constraints: BoxConstraints.tightFor( @@ -324,17 +308,14 @@ class StreamChatThemeData { ), ), otherMessageTheme: MessageTheme( - reactionsBackgroundColor: - isDark ? Color(0xff191919) : Color(0xffEAEAEA), - reactionsBorderColor: isDark ? Colors.black : Colors.white, + reactionsBackgroundColor: colorTheme.greyGainsboro, + reactionsBorderColor: colorTheme.white, messageText: TextStyle( fontSize: 14.5, - color: isDark ? Colors.white : Colors.black, + color: colorTheme.black, ), createdAt: TextStyle( - color: isDark - ? Colors.white.withOpacity(.5) - : Colors.black.withOpacity(.5), + color: colorTheme.black.withOpacity(.5), fontSize: 12, ), replies: TextStyle( @@ -345,9 +326,8 @@ class StreamChatThemeData { messageLinks: TextStyle( color: accentColor, ), - messageBackgroundColor: isDark ? Colors.black : Colors.white, - replyThreadColor: - isDark ? Colors.white.withAlpha(24) : Colors.black.withAlpha(24), + messageBackgroundColor: colorTheme.white, + replyThreadColor: colorTheme.black.withAlpha(24), avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), constraints: BoxConstraints.tightFor( @@ -382,6 +362,230 @@ class StreamChatThemeData { } } +enum TextThemeType { + light, + dark, +} + +class TextTheme { + final TextStyle title; + final TextStyle headlineBold; + final TextStyle headline; + final TextStyle bodyBold; + final TextStyle body; + final TextStyle footnoteBold; + final TextStyle footnote; + final TextStyle captionBold; + + TextTheme.light({ + this.title = const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + this.headlineBold = const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + this.headline = const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + this.bodyBold = const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + this.body = const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + this.footnoteBold = const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + this.footnote = const TextStyle( + fontSize: 12, + color: Colors.black, + ), + this.captionBold = const TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + }); + + TextTheme.dark({ + this.title = const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + this.headlineBold = const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + this.headline = const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + this.bodyBold = const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + this.body = const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + this.footnoteBold = const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + this.footnote = const TextStyle( + fontSize: 12, + color: Colors.white, + ), + this.captionBold = const TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + }); + + TextTheme copyWith({ + TextThemeType type = TextThemeType.light, + TextStyle body, + TextStyle title, + TextStyle headlineBold, + TextStyle headline, + TextStyle bodyBold, + TextStyle footnoteBold, + TextStyle footnote, + TextStyle captionBold, + }) { + return type == TextThemeType.light + ? TextTheme.light( + body: body ?? this.body, + title: title ?? this.title, + headlineBold: headlineBold ?? this.headlineBold, + headline: headline ?? this.headline, + bodyBold: bodyBold ?? this.bodyBold, + footnoteBold: footnoteBold ?? this.footnoteBold, + footnote: footnote ?? this.footnote, + captionBold: captionBold ?? this.captionBold, + ) + : TextTheme.dark( + body: body ?? this.body, + title: title ?? this.title, + headlineBold: headlineBold ?? this.headlineBold, + headline: headline ?? this.headline, + bodyBold: bodyBold ?? this.bodyBold, + footnoteBold: footnoteBold ?? this.footnoteBold, + footnote: footnote ?? this.footnote, + captionBold: captionBold ?? this.captionBold, + ); + } +} + +enum ColorThemeType { + light, + dark, +} + +class ColorTheme { + final Color black; + final Color grey; + final Color greyGainsboro; + final Color greyWhisper; + final Color whiteSmoke; + final Color whiteSnow; + final Color white; + final Color blueAlice; + final Color accentBlue; + final Color accentRed; + final Color accentGreen; + + ColorTheme.light({ + this.black = const Color(0xff000000), + this.grey = const Color(0xff7a7a7a), + this.greyGainsboro = const Color(0xffdbdbdb), + this.greyWhisper = const Color(0xffecebeb), + this.whiteSmoke = const Color(0xfff2f2f2), + this.whiteSnow = const Color(0xfffcfcfc), + this.white = const Color(0xffffffff), + this.blueAlice = const Color(0xffe9f2ff), + this.accentBlue = const Color(0xff005FFF), + this.accentRed = const Color(0xffFF3742), + this.accentGreen = const Color(0xff20E070), + }); + + ColorTheme.dark({ + this.black = const Color(0xffffffff), + this.grey = const Color(0xff7a7a7a), + this.greyGainsboro = const Color(0xff2d2f2f), + this.greyWhisper = const Color(0xff1c1e22), + this.whiteSmoke = const Color(0xff13151b), + this.whiteSnow = const Color(0xff070A0D), + this.white = const Color(0xff101418), + this.blueAlice = const Color(0xff00193D), + this.accentBlue = const Color(0xff005FFF), + this.accentRed = const Color(0xffFF3742), + this.accentGreen = const Color(0xff20E070), + }); + + ColorTheme copyWith({ + ColorThemeType type = ColorThemeType.light, + Color black, + Color grey, + Color greyGainsboro, + Color greyWhisper, + Color whiteSmoke, + Color whiteSnow, + Color white, + Color blueAlice, + Color accentBlue, + Color accentRed, + Color accentGreen, + }) { + return type == ColorThemeType.light + ? ColorTheme.light( + black: black ?? this.black, + grey: grey ?? this.grey, + greyGainsboro: greyGainsboro ?? this.greyGainsboro, + greyWhisper: greyWhisper ?? this.greyWhisper, + whiteSmoke: whiteSmoke ?? this.whiteSmoke, + whiteSnow: whiteSnow ?? this.whiteSnow, + white: white ?? this.white, + blueAlice: blueAlice ?? this.blueAlice, + accentBlue: accentBlue ?? this.accentBlue, + accentRed: accentRed ?? this.accentRed, + accentGreen: accentGreen ?? this.accentGreen, + ) + : ColorTheme.dark( + black: black ?? this.black, + grey: grey ?? this.grey, + greyGainsboro: greyGainsboro ?? this.greyGainsboro, + greyWhisper: greyWhisper ?? this.greyWhisper, + whiteSmoke: whiteSmoke ?? this.whiteSmoke, + whiteSnow: whiteSnow ?? this.whiteSnow, + white: white ?? this.white, + blueAlice: blueAlice ?? this.blueAlice, + accentBlue: accentBlue ?? this.accentBlue, + accentRed: accentRed ?? this.accentRed, + accentGreen: accentGreen ?? this.accentGreen, + ); + } +} + /// Channel theme data class ChannelTheme { /// Theme of the [ChannelHeader] widget diff --git a/lib/src/stream_svg_icon.dart b/lib/src/stream_svg_icon.dart index bfeaa5da..3f456db0 100644 --- a/lib/src/stream_svg_icon.dart +++ b/lib/src/stream_svg_icon.dart @@ -769,4 +769,28 @@ class StreamSvgIcon extends StatelessWidget { height: size, ); } + + factory StreamSvgIcon.error({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_error.svg', + color: color, + width: size, + height: size, + ); + } + + factory StreamSvgIcon.circle_up({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_circle_up.svg', + color: color, + width: size, + height: size, + ); + } } diff --git a/lib/src/url_attachment.dart b/lib/src/url_attachment.dart index ad314455..1909fc33 100644 --- a/lib/src/url_attachment.dart +++ b/lib/src/url_attachment.dart @@ -51,17 +51,21 @@ class UrlAttachment extends StatelessWidget { ), child: Text( hostDisplayName, - style: TextStyle( - fontWeight: FontWeight.w700, - color: Color(0xFF006CFF), - ), + style: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, + ), ), ), decoration: BoxDecoration( borderRadius: BorderRadius.only( topRight: Radius.circular(16.0), ), - color: Color(0xFFE9F2FF), + color: StreamChatTheme.of(context).colorTheme.blueAlice, ), ), ), @@ -81,18 +85,18 @@ class UrlAttachment extends StatelessWidget { urlAttachment.title.trim(), maxLines: 1, overflow: TextOverflow.ellipsis, - style: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 12.0, - ), + style: StreamChatTheme.of(context) + .textTheme + .body + .copyWith(fontWeight: FontWeight.w700), ), if (urlAttachment.text != null) Text( urlAttachment.text, - style: TextStyle( - fontWeight: FontWeight.w400, - fontSize: 12.0, - ), + style: StreamChatTheme.of(context) + .textTheme + .body + .copyWith(fontWeight: FontWeight.w400), ), ], ), diff --git a/lib/src/user_avatar.dart b/lib/src/user_avatar.dart index 756252cc..688eff77 100644 --- a/lib/src/user_avatar.dart +++ b/lib/src/user_avatar.dart @@ -16,7 +16,7 @@ class UserAvatar extends StatelessWidget { this.borderRadius, this.onlineIndicatorAlignment = Alignment.topRight, this.selected = false, - this.selectionColor = const Color(0xFF006CFF), + this.selectionColor, this.selectionThickness = 4, }) : super(key: key); @@ -47,7 +47,7 @@ class UserAvatar extends StatelessWidget { constraints: constraints ?? streamChatTheme.ownMessageTheme.avatarTheme.constraints, decoration: BoxDecoration( - color: streamChatTheme.accentColor, + color: streamChatTheme.colorTheme.accentBlue, ), child: hasImage ? CachedNetworkImage( @@ -61,13 +61,17 @@ class UserAvatar extends StatelessWidget { : streamChatTheme.defaultUserImage(context, user), ), ); + if (selected) { avatar = ClipRRect( borderRadius: (borderRadius ?? streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) + BorderRadius.circular(selectionThickness), child: Container( - color: selectionColor, + constraints: constraints ?? + streamChatTheme.ownMessageTheme.avatarTheme.constraints, + color: selectionColor ?? + StreamChatTheme.of(context).colorTheme.accentBlue, child: Padding( padding: EdgeInsets.all(selectionThickness), child: avatar, @@ -96,10 +100,10 @@ class UserAvatar extends StatelessWidget { ), child: Material( shape: CircleBorder(), - color: Color(0xff20E070), + color: streamChatTheme.colorTheme.accentGreen, ), ), - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, ), ), ), diff --git a/lib/src/user_item.dart b/lib/src/user_item.dart index 4c1af658..50a8b7a7 100644 --- a/lib/src/user_item.dart +++ b/lib/src/user_item.dart @@ -74,24 +74,30 @@ class UserItem extends StatelessWidget { ), trailing: selected ? CircleAvatar( + backgroundColor: + StreamChatTheme.of(context).colorTheme.accentBlue, child: StreamSvgIcon.check( size: 20, - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, ), radius: 10, ) : null, title: Text( user.name, - style: TextStyle(fontWeight: FontWeight.bold), + style: StreamChatTheme.of(context).textTheme.bodyBold, ), subtitle: showLastOnline ? _buildLastActive(context) : null, ); } Widget _buildLastActive(context) { - return user.online == true - ? Text('Online') - : Text('Last online ${Jiffy(user.lastActive).fromNow()}'); + return Text( + user.online == true + ? 'Online' + : 'Last online ${Jiffy(user.lastActive).fromNow()}', + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context).colorTheme.black.withOpacity(.5)), + ); } } diff --git a/lib/src/user_list_view.dart b/lib/src/user_list_view.dart index 59ba7f67..35692841 100644 --- a/lib/src/user_list_view.dart +++ b/lib/src/user_list_view.dart @@ -356,15 +356,19 @@ class _UserListViewState extends State headerItem: (header) { return Container( key: ValueKey('HEADER-$header'), - color: Colors.black.withOpacity(0.05), + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6), child: Text( header, style: TextStyle( fontWeight: FontWeight.bold, - fontSize: 12, - color: Colors.black.withOpacity(0.3)), + fontSize: 14.5, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.3)), ), ), ); @@ -449,7 +453,10 @@ class _UserListViewState extends State builder: (context, snapshot) { if (snapshot.hasError) { return Container( - color: Color(0xffd0021B).withAlpha(26), + color: StreamChatTheme.of(context) + .colorTheme + .accentRed + .withOpacity(.2), child: Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: Center( @@ -472,8 +479,8 @@ class _UserListViewState extends State return Container( height: 1, color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(0.1) - : Colors.black.withOpacity(0.1), + ? StreamChatTheme.of(context).colorTheme.white.withOpacity(0.1) + : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.1), ); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 2bf09f91..f573b77b 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -27,7 +27,7 @@ Future showConfirmationDialog( String cancelText, }) { return showModalBottomSheet( - backgroundColor: Colors.white, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, context: context, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -47,7 +47,7 @@ Future showConfirmationDialog( ), Text( title, - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0), + style: StreamChatTheme.of(context).textTheme.headlineBold, ), SizedBox( height: 7.0, @@ -57,7 +57,8 @@ Future showConfirmationDialog( height: 36.0, ), Container( - color: Color(0xffe6e6e6), + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), height: 1.0, ), Row( @@ -67,7 +68,10 @@ Future showConfirmationDialog( child: Text( cancelText, style: TextStyle( - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), fontWeight: FontWeight.w400), ), onPressed: () {