Merge branch 'feature/new-ui' of https://github.com/GetStream/stream-chat-flutter into fix/channel-list

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