Merge branch 'feature/new-ui' into feature/new-chat-screens

This commit is contained in:
Salvatore Giordano
2020-11-23 10:41:59 +01:00
8 changed files with 661 additions and 503 deletions
+2
View File
@@ -13,6 +13,8 @@ jobs:
channel: 'stable' channel: 'stable'
- name: Get dependencies - name: Get dependencies
run: flutter pub get run: flutter pub get
- name: Run formatter
run: flutter format -n --set-exit-if-changed .
- name: Coverage fix - name: Coverage fix
run: | run: |
file=test/coverage_helper_test.dart file=test/coverage_helper_test.dart
+105 -102
View File
@@ -94,127 +94,130 @@ class ChooseUserPage extends StatelessWidget {
), ),
), ),
Expanded( Expanded(
child: ListView.separated( child: Padding(
separatorBuilder: (context, i) { padding: const EdgeInsets.only(top: 32),
return Container( child: ListView.separated(
width: double.infinity, separatorBuilder: (context, i) {
color: Colors.black12, return Container(
height: 1, width: double.infinity,
); color: Colors.black12,
}, height: 1,
itemCount: users.length + 1, );
itemBuilder: (context, i) { },
return [ itemCount: users.length + 1,
...users.entries.map((entry) { itemBuilder: (context, i) {
final token = entry.key; return [
final user = entry.value; ...users.entries.map((entry) {
return ListTile( final token = entry.key;
onTap: () async { final user = entry.value;
showDialog( return ListTile(
barrierDismissible: false, onTap: () async {
context: context, showDialog(
builder: (context) => Center( barrierDismissible: false,
child: Container( context: context,
decoration: BoxDecoration( builder: (context) => Center(
borderRadius: BorderRadius.circular(16), child: Container(
color: Colors.white, decoration: BoxDecoration(
), borderRadius: BorderRadius.circular(16),
height: 100, color: Colors.white,
width: 100, ),
child: Center( height: 100,
child: CircularProgressIndicator(), width: 100,
child: Center(
child: CircularProgressIndicator(),
),
), ),
), ),
);
final secureStorage = FlutterSecureStorage();
final client = StreamChat.of(context).client;
await client.setUser(
user,
token,
);
secureStorage.write(
key: kStreamApiKey,
value: kDefaultStreamApiKey,
);
secureStorage.write(
key: kStreamUserId,
value: user.id,
);
secureStorage.write(
key: kStreamToken,
value: token,
);
if (!kIsWeb) {
initNotifications(client);
}
Navigator.pop(context);
await Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) {
return StreamChat(
client: client,
child: ChannelListPage(),
);
},
),
);
},
leading: UserAvatar(
user: user,
constraints: BoxConstraints.tight(
Size.fromRadius(20),
), ),
); ),
title: Text(
final secureStorage = FlutterSecureStorage(); user.name,
final client = StreamChat.of(context).client; style: TextStyle(fontWeight: FontWeight.bold),
),
await client.setUser( subtitle: Text('Stream test account'),
user, trailing: SvgPicture.asset(
token, 'assets/icon_arrow_right.svg',
); height: 24,
width: 24,
secureStorage.write( ),
key: kStreamApiKey, );
value: kDefaultStreamApiKey, }),
); ListTile(
secureStorage.write( onTap: () {
key: kStreamUserId, Navigator.push(
value: user.id,
);
secureStorage.write(
key: kStreamToken,
value: token,
);
if (!kIsWeb) {
initNotifications(client);
}
Navigator.pop(context);
await Navigator.pushReplacement(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) { builder: (context) => AdvancedOptionsPage(),
return StreamChat(
client: client,
child: ChannelListPage(),
);
},
), ),
); );
}, },
leading: UserAvatar( leading: CircleAvatar(
user: user, child: Icon(
constraints: BoxConstraints.tight( StreamIcons.settings,
Size.fromRadius(20), color: Colors.black,
), ),
backgroundColor:
StreamChatTheme.of(context).secondaryColor,
), ),
title: Text( title: Text(
user.name, 'Advanced Options',
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
subtitle: Text('Stream test account'), subtitle: Text('Custom settings'),
trailing: SvgPicture.asset( trailing: SvgPicture.asset(
'assets/icon_arrow_right.svg', 'assets/icon_arrow_right.svg',
height: 24, height: 24,
width: 24, width: 24,
clipBehavior: Clip.none,
), ),
);
}),
ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AdvancedOptionsPage(),
),
);
},
leading: CircleAvatar(
child: Icon(
StreamIcons.settings,
color: Colors.black,
),
backgroundColor:
StreamChatTheme.of(context).secondaryColor,
), ),
title: Text( ][i];
'Advanced Options', },
style: TextStyle(fontWeight: FontWeight.bold), ),
),
subtitle: Text('Custom settings'),
trailing: SvgPicture.asset(
'assets/icon_arrow_right.svg',
height: 24,
width: 24,
clipBehavior: Clip.none,
),
),
][i];
},
), ),
), ),
StreamVersion(), StreamVersion(),
+110 -99
View File
@@ -1,14 +1,14 @@
import 'dart:io'; import 'dart:io';
import 'package:example/choose_user_page.dart'; import 'package:example/choose_user_page.dart';
import 'package:example/new_chat_screen.dart';
import 'package:example/new_group_chat_screen.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'new_chat_screen.dart';
import 'new_group_chat_screen.dart';
import 'notifications_service.dart'; import 'notifications_service.dart';
void main() async { void main() async {
@@ -48,6 +48,7 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(), theme: ThemeData.light(),
darkTheme: ThemeData.dark(), darkTheme: ThemeData.dark(),
//TODO change to system once dark theme is implemented //TODO change to system once dark theme is implemented
@@ -75,105 +76,15 @@ class ChannelListPage extends StatelessWidget {
style: TextStyle(color: Colors.black), style: TextStyle(color: Colors.black),
), ),
), ),
drawer: Drawer( drawer: _buildDrawer(context, user),
child: SafeArea( drawerEdgeDragWidth: 50,
child: Padding(
padding: EdgeInsets.only(
top: MediaQuery.of(context).viewPadding.top + 8,
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(
bottom: 20.0,
left: 8,
),
child: Row(
children: [
UserAvatar(
user: user,
showOnlineStatus: false,
constraints: BoxConstraints.tight(Size.fromRadius(20)),
),
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Text(
user.name,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
ListTile(
onTap: () {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(builder: (_) => NewChatScreen()),
);
},
leading: Icon(StreamIcons.edit),
title: Text(
'New direct message',
style: TextStyle(
fontSize: 14.5,
),
),
),
ListTile(
onTap: () {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(builder: (_) => NewGroupChatScreen()),
);
},
leading: Icon(StreamIcons.group),
title: Text(
'New group',
style: TextStyle(
fontSize: 14.5,
),
),
),
Expanded(
child: Container(
alignment: Alignment.bottomCenter,
child: ListTile(
onTap: () async {
await StreamChat.of(context).client.disconnect();
final secureStorage = FlutterSecureStorage();
await secureStorage.deleteAll();
Navigator.pop(context);
await Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => ChooseUserPage(),
),
);
},
leading: Icon(StreamIcons.user),
title: Text(
'Sign out',
style: TextStyle(
fontSize: 14.5,
),
),
),
),
),
],
),
),
),
),
body: ChannelsBloc( body: ChannelsBloc(
child: ChannelListView( child: ChannelListView(
onStartChatPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return NewChatScreen();
}));
},
swipeToAction: true, swipeToAction: true,
filter: { filter: {
'members': { 'members': {
@@ -194,6 +105,106 @@ class ChannelListPage extends StatelessWidget {
), ),
); );
} }
Drawer _buildDrawer(BuildContext context, User user) {
return Drawer(
child: SafeArea(
child: Padding(
padding: EdgeInsets.only(
top: MediaQuery.of(context).viewPadding.top + 8,
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(
bottom: 20.0,
left: 8,
),
child: Row(
children: [
UserAvatar(
user: user,
showOnlineStatus: false,
constraints: BoxConstraints.tight(Size.fromRadius(20)),
),
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Text(
user.name,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
ListTile(
leading: Icon(StreamIcons.edit),
onTap: () {
Navigator.of(context)
..pop()
..push(MaterialPageRoute(builder: (context) {
return NewChatScreen();
}));
},
title: Text(
'New direct message',
style: TextStyle(
fontSize: 14.5,
),
),
),
ListTile(
leading: Icon(StreamIcons.group),
onTap: () {
Navigator.of(context)
..pop()
..push(MaterialPageRoute(builder: (context) {
return NewGroupChatScreen();
}));
},
title: Text(
'New group',
style: TextStyle(
fontSize: 14.5,
),
),
),
Expanded(
child: Container(
alignment: Alignment.bottomCenter,
child: ListTile(
onTap: () async {
await StreamChat.of(context).client.disconnect();
final secureStorage = FlutterSecureStorage();
await secureStorage.deleteAll();
Navigator.pop(context);
await Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => ChooseUserPage(),
),
);
},
leading: Icon(StreamIcons.user),
title: Text(
'Sign out',
style: TextStyle(
fontSize: 14.5,
),
),
),
),
),
],
),
),
),
);
}
} }
class ChannelPage extends StatelessWidget { class ChannelPage extends StatelessWidget {
+1 -1
View File
@@ -1,6 +1,6 @@
name: example name: example
description: A new Flutter project. description: A new Flutter project.
version: 1.0.65+67 version: 1.0.66+68
environment: environment:
sdk: ">=2.2.2 <3.0.0" sdk: ">=2.2.2 <3.0.0"
+253 -108
View File
@@ -4,6 +4,7 @@ import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/channels_bloc.dart'; import 'package:stream_chat_flutter/src/channels_bloc.dart';
import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/src/utils.dart';
@@ -69,6 +70,7 @@ class ChannelListView extends StatefulWidget {
this.errorBuilder, this.errorBuilder,
this.emptyBuilder, this.emptyBuilder,
this.onImageTap, this.onImageTap,
this.onStartChatPressed,
this.swipeToAction = false, this.swipeToAction = false,
this.pullToRefresh = true, this.pullToRefresh = true,
}) : super(key: key); }) : super(key: key);
@@ -128,6 +130,9 @@ class ChannelListView extends StatefulWidget {
/// Set it to false to disable the pull-to-refresh widget /// Set it to false to disable the pull-to-refresh widget
final bool pullToRefresh; final bool pullToRefresh;
/// Callback used in the default empty list widget
final VoidCallback onStartChatPressed;
@override @override
_ChannelListViewState createState() => _ChannelListViewState(); _ChannelListViewState createState() => _ChannelListViewState();
} }
@@ -163,127 +168,265 @@ class _ChannelListViewState extends State<ChannelListView>
return StreamBuilder<List<Channel>>( return StreamBuilder<List<Channel>>(
stream: channelsBlocState.channelsStream, stream: channelsBlocState.channelsStream,
builder: (context, snapshot) { builder: (context, snapshot) {
var child;
if (snapshot.hasError) { if (snapshot.hasError) {
if (snapshot.error is Error) { child = _buildErrorWidget(
print((snapshot.error as Error).stackTrace); snapshot,
context,
channelsBlocState,
);
} else if (!snapshot.hasData) {
child = _buildLoadingWidget();
} else {
final channels = snapshot.data;
if (channels.isEmpty && widget.emptyBuilder != null) {
child = widget.emptyBuilder(context);
} }
if (widget.errorBuilder != null) { if (channels.isEmpty && widget.emptyBuilder == null) {
return widget.errorBuilder(snapshot.error); child = LayoutBuilder(
} builder: (context, viewportConstraints) {
return SingleChildScrollView(
var message = snapshot.error.toString(); physics: AlwaysScrollableScrollPhysics(),
if (snapshot.error is DioError) { child: Stack(
final dioError = snapshot.error as DioError;
if (dioError.type == DioErrorType.RESPONSE) {
message = dioError.message;
} else {
message = 'Check your connection and retry';
}
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text.rich(
TextSpan(
children: [ children: [
WidgetSpan( ConstrainedBox(
child: Padding( constraints: BoxConstraints(
padding: const EdgeInsets.only( minHeight: viewportConstraints.maxHeight,
right: 2.0, ),
), child: Column(
child: Icon(Icons.error_outline), mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
StreamIcons.message,
size: 136,
color: Color(0xffDBDBDB),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Lets start chatting!',
style: TextStyle(
fontSize: 16,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 52,
),
child: Text(
'How about sending your first message to a friend?',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Color(0xff7A7A7A),
),
),
),
],
), ),
), ),
TextSpan(text: 'Error loading channels'), if (widget.onStartChatPressed != null)
Positioned(
right: 0,
left: 0,
bottom: 32,
child: Center(
child: FlatButton(
onPressed: widget.onStartChatPressed,
child: Text(
'Start a chat',
style: TextStyle(
color:
StreamChatTheme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
),
),
),
),
], ],
), ),
style: Theme.of(context).textTheme.headline6, );
), },
Padding( );
padding: const EdgeInsets.only( }
top: 16.0,
), if (channels.isNotEmpty) {
child: Text(message), child = ListView.custom(
), physics: AlwaysScrollableScrollPhysics(),
FlatButton( controller: _scrollController,
onPressed: () { childrenDelegate: SliverChildBuilderDelegate(
channelsBlocState.queryChannels( (context, i) {
filter: widget.filter, return _itemBuilder(context, i, channels);
sortOptions: widget.sort, },
paginationParams: widget.pagination, childCount: (channels.length * 2) + 1,
options: widget.options, findChildIndexCallback: (key) {
); final ValueKey<String> valueKey = key;
}, final index = channels.indexWhere(
child: Text('Retry'), (channel) => 'CHANNEL-${channel.id}' == valueKey.value);
), return index != -1 ? (index * 2) : null;
], },
), ),
); );
}
} }
if (!snapshot.hasData) { return AnimatedSwitcher(
return LayoutBuilder( child: child,
builder: (context, viewportConstraints) { duration: Duration(milliseconds: 500),
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: CircularProgressIndicator(),
),
),
);
},
);
}
final channels = snapshot.data;
if (channels.isEmpty && widget.emptyBuilder != null) {
return widget.emptyBuilder(context);
}
if (channels.isEmpty && widget.emptyBuilder == null) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Text('You have no channels currently'),
),
),
);
},
);
}
return ListView.custom(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
childrenDelegate: SliverChildBuilderDelegate(
(context, i) {
return _itemBuilder(context, i, channels);
},
childCount: (channels.length * 2) + 1,
findChildIndexCallback: (key) {
final ValueKey<String> valueKey = key;
final index = channels.indexWhere(
(channel) => 'CHANNEL-${channel.id}' == valueKey.value);
return index != -1 ? (index * 2) : null;
},
),
); );
}); });
} }
Widget _buildLoadingWidget() {
return ListView(
physics: AlwaysScrollableScrollPhysics(),
children: List.generate(
25,
(i) {
if (i % 2 != 0) {
if (widget.separatorBuilder != null) {
return widget.separatorBuilder(context, i);
}
return _separatorBuilder(context, i);
}
return _buildLoadingItem();
},
),
);
}
Shimmer _buildLoadingItem() {
return Shimmer.fromColors(
baseColor: Color(0xffE5E5E5),
highlightColor: Color(0xffffffff),
child: ListTile(
leading: Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
constraints: BoxConstraints.tightFor(
height: 40,
width: 40,
),
),
title: Align(
alignment: Alignment.centerLeft,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(11),
),
constraints: BoxConstraints.tightFor(
height: 16,
width: 82,
),
),
),
subtitle: Row(
children: [
Align(
alignment: Alignment.centerLeft,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(11),
),
constraints: BoxConstraints.tightFor(
height: 16,
width: 238,
),
),
),
Container(
margin: const EdgeInsets.only(left: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(11),
),
constraints: BoxConstraints.tightFor(
height: 16,
width: 42,
),
),
],
),
),
);
}
Widget _buildErrorWidget(
AsyncSnapshot<List<Channel>> snapshot,
BuildContext context,
ChannelsBlocState channelsBlocState,
) {
if (snapshot.error is Error) {
print((snapshot.error as Error).stackTrace);
}
if (widget.errorBuilder != null) {
return widget.errorBuilder(snapshot.error);
}
var message = snapshot.error.toString();
if (snapshot.error is DioError) {
final dioError = snapshot.error as DioError;
if (dioError.type == DioErrorType.RESPONSE) {
message = dioError.message;
} else {
message = 'Check your connection and retry';
}
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text.rich(
TextSpan(
children: [
WidgetSpan(
child: Padding(
padding: const EdgeInsets.only(
right: 2.0,
),
child: Icon(Icons.error_outline),
),
),
TextSpan(text: 'Error loading channels'),
],
),
style: Theme.of(context).textTheme.headline6,
),
Padding(
padding: const EdgeInsets.only(
top: 16.0,
),
child: Text(message),
),
FlatButton(
onPressed: () {
channelsBlocState.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
},
child: Text('Retry'),
),
],
),
);
}
Widget _itemBuilder(context, int i, List<Channel> channels) { Widget _itemBuilder(context, int i, List<Channel> channels) {
if (i % 2 != 0) { if (i % 2 != 0) {
if (widget.separatorBuilder != null) { if (widget.separatorBuilder != null) {
@@ -441,7 +584,9 @@ class _ChannelListViewState extends State<ChannelListView>
} }
Widget _buildQueryProgressIndicator( Widget _buildQueryProgressIndicator(
context, ChannelsBlocState channelsProvider) { context,
ChannelsBlocState channelsProvider,
) {
return StreamBuilder<bool>( return StreamBuilder<bool>(
stream: channelsProvider.queryChannelsLoading, stream: channelsProvider.queryChannelsLoading,
initialData: false, initialData: false,
+188 -193
View File
@@ -41,211 +41,206 @@ class GiphyAttachment extends StatelessWidget {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
DecoratedBox( Card(
decoration: BoxDecoration(), elevation: 2,
child: Card( clipBehavior: Clip.antiAlias,
elevation: 2, shape: RoundedRectangleBorder(
clipBehavior: Clip.antiAlias, borderRadius: BorderRadius.only(
shape: RoundedRectangleBorder( topRight: Radius.circular(16.0),
borderRadius: BorderRadius.only( bottomRight: Radius.circular(0.0),
topRight: Radius.circular(16.0), topLeft: Radius.circular(16.0),
bottomRight: Radius.circular(0.0), bottomLeft: Radius.circular(16.0),
topLeft: Radius.circular(16.0),
bottomLeft: Radius.circular(16.0),
),
), ),
child: Column( ),
mainAxisSize: MainAxisSize.min, child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min,
children: <Widget>[ crossAxisAlignment: CrossAxisAlignment.stretch,
Stack( children: <Widget>[
children: [ Stack(
Padding( children: [
padding: const EdgeInsets.all(8.0), Padding(
child: GestureDetector( padding: const EdgeInsets.all(8.0),
onTap: () { child: GestureDetector(
Navigator.push(context, onTap: () {
MaterialPageRoute(builder: (_) { Navigator.push(context, MaterialPageRoute(builder: (_) {
return FullScreenImage( return FullScreenImage(
url: attachment.imageUrl ?? url: attachment.imageUrl ??
attachment.assetUrl ?? attachment.assetUrl ??
attachment.thumbUrl, attachment.thumbUrl,
); );
})); }));
}, },
child: CachedNetworkImage( child: CachedNetworkImage(
height: size?.height, height: size?.height,
width: size?.width, width: size?.width,
placeholder: (_, __) { placeholder: (_, __) {
return Container( return Container(
width: size?.width, width: size?.width,
height: size?.height, height: size?.height,
child: Center(
child: CircularProgressIndicator(),
),
);
},
imageUrl: attachment.thumbUrl ??
attachment.imageUrl ??
attachment.assetUrl,
errorWidget: (context, url, error) => AttachmentError(
attachment: attachment,
size: size,
),
fit: BoxFit.cover,
),
),
),
Positioned(
left: 0,
top: 0,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(16.0),
)),
child: Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
top: 8.0,
bottom: 4.0,
),
child: Row(
children: [
Icon(
StreamIcons.lightning,
color: StreamChatTheme.of(context).accentColor,
size: 16.0,
),
Text(
'GIPHY',
style: TextStyle(
color:
StreamChatTheme.of(context).accentColor,
fontWeight: FontWeight.bold,
fontSize: 11.0,
),
),
],
),
),
),
),
],
),
if (attachment.title != null)
Container(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Card(
elevation: 2,
child: IconButton(
padding: const EdgeInsets.all(0),
constraints: BoxConstraints.tight(Size(32, 32)),
icon: Icon(
StreamIcons.left,
size: 24.0,
),
splashRadius: 16,
onPressed: () {
streamChannel.channel.sendAction(message, {
'image_action': 'shuffle',
});
},
),
shape: CircleBorder(),
),
Expanded(
child: Center( child: Center(
child: Text( child: CircularProgressIndicator(),
'"${attachment.title}"',
style: TextStyle(
fontStyle: FontStyle.italic,
),
),
), ),
), );
Card( },
elevation: 2, imageUrl: attachment.thumbUrl ??
child: IconButton( attachment.imageUrl ??
padding: const EdgeInsets.all(0), attachment.assetUrl,
constraints: BoxConstraints.tight(Size(32, 32)), errorWidget: (context, url, error) => AttachmentError(
icon: Icon( attachment: attachment,
StreamIcons.right, size: size,
size: 24.0, ),
), fit: BoxFit.cover,
splashRadius: 16,
onPressed: () {
streamChannel.channel.sendAction(message, {
'image_action': 'shuffle',
});
},
),
shape: CircleBorder(),
),
],
), ),
), ),
), ),
SizedBox( Positioned(
height: 4.0, left: 0,
), top: 0,
Container( child: Container(
color: Colors.black.withOpacity(0.2), decoration: BoxDecoration(
width: double.infinity, color: Colors.white,
height: 0.5, borderRadius: BorderRadius.only(
), bottomRight: Radius.circular(16.0),
Row( )),
mainAxisAlignment: MainAxisAlignment.start, child: Padding(
crossAxisAlignment: CrossAxisAlignment.center, padding: const EdgeInsets.only(
children: [ left: 8.0,
Expanded( right: 8.0,
child: FlatButton( top: 8.0,
height: 50, bottom: 4.0,
onPressed: () {
streamChannel.channel.sendAction(message, {
'image_action': 'cancel',
});
},
child: Text(
'Cancel',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black.withOpacity(0.5)),
), ),
), child: Row(
), children: [
Container( Icon(
width: 0.5, StreamIcons.lightning,
color: Colors.black.withOpacity(0.2),
height: 50.0,
),
Expanded(
child: FlatButton(
height: 50,
onPressed: () {
streamChannel.channel.sendAction(message, {
'image_action': 'send',
});
},
child: Text(
'Send',
style: TextStyle(
color: StreamChatTheme.of(context).accentColor, color: StreamChatTheme.of(context).accentColor,
fontWeight: FontWeight.bold), size: 16.0,
),
Text(
'GIPHY',
style: TextStyle(
color: StreamChatTheme.of(context).accentColor,
fontWeight: FontWeight.bold,
fontSize: 11.0,
),
),
],
), ),
), ),
), ),
], ),
],
),
if (attachment.title != null)
Container(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Card(
elevation: 2,
child: IconButton(
padding: const EdgeInsets.all(0),
constraints: BoxConstraints.tight(Size(32, 32)),
icon: Icon(
StreamIcons.left,
size: 24.0,
),
splashRadius: 16,
onPressed: () {
streamChannel.channel.sendAction(message, {
'image_action': 'shuffle',
});
},
),
shape: CircleBorder(),
),
Expanded(
child: Center(
child: Text(
'"${attachment.title}"',
style: TextStyle(
fontStyle: FontStyle.italic,
),
),
),
),
Card(
elevation: 2,
child: IconButton(
padding: const EdgeInsets.all(0),
constraints: BoxConstraints.tight(Size(32, 32)),
icon: Icon(
StreamIcons.right,
size: 24.0,
),
splashRadius: 16,
onPressed: () {
streamChannel.channel.sendAction(message, {
'image_action': 'shuffle',
});
},
),
shape: CircleBorder(),
),
],
),
),
), ),
], SizedBox(
), height: 4.0,
),
Container(
color: Colors.black.withOpacity(0.2),
width: double.infinity,
height: 0.5,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: FlatButton(
height: 50,
onPressed: () {
streamChannel.channel.sendAction(message, {
'image_action': 'cancel',
});
},
child: Text(
'Cancel',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black.withOpacity(0.5)),
),
),
),
Container(
width: 0.5,
color: Colors.black.withOpacity(0.2),
height: 50.0,
),
Expanded(
child: FlatButton(
height: 50,
onPressed: () {
streamChannel.channel.sendAction(message, {
'image_action': 'send',
});
},
child: Text(
'Send',
style: TextStyle(
color: StreamChatTheme.of(context).accentColor,
fontWeight: FontWeight.bold),
),
),
),
],
),
],
), ),
), ),
SizedBox( SizedBox(
+1
View File
@@ -619,6 +619,7 @@ class _MessageWidgetState extends State<MessageWidget> {
child: Material( child: Material(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
shape: attachmentShape, shape: attachmentShape,
type: MaterialType.transparency,
child: Transform( child: Transform(
transform: Matrix4.rotationY(widget.reverse ? pi : 0), transform: Matrix4.rotationY(widget.reverse ? pi : 0),
alignment: Alignment.center, alignment: Alignment.center,
+1
View File
@@ -19,6 +19,7 @@ dependencies:
flutter_svg: ^0.19.1 flutter_svg: ^0.19.1
flutter_portal: ^0.3.0 flutter_portal: ^0.3.0
cached_network_image: ^2.2.0+1 cached_network_image: ^2.2.0+1
shimmer: ^1.1.2
flutter_markdown: ^0.5.0 flutter_markdown: ^0.5.0
url_launcher: ^5.4.11 url_launcher: ^5.4.11
emojis: ^0.9.3 emojis: ^0.9.3