Merge branch 'feature/new-ui' of github.com:GetStream/stream-chat-flutter into feat/messg-reply

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