Merge pull request #193 from GetStream/qa-fixes

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