Merge pull request #177 from GetStream/feature/group-info-screen
Group Info Screen
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
import 'package:emojis/emojis.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
|
||||
import '../stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
import 'main.dart';
|
||||
import 'routes/routes.dart';
|
||||
|
||||
/// Detail screen for a 1:1 chat correspondence
|
||||
class ChatInfoScreen extends StatefulWidget {
|
||||
@@ -24,12 +28,14 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
||||
body: ListView(
|
||||
children: [
|
||||
_buildUserHeader(),
|
||||
SizedBox(
|
||||
Container(
|
||||
height: 8.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||
),
|
||||
_buildOptionListTiles(),
|
||||
SizedBox(
|
||||
Container(
|
||||
height: 8.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||
),
|
||||
if ([
|
||||
'admin',
|
||||
@@ -73,8 +79,9 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
||||
SizedBox(height: 7.0),
|
||||
_buildConnectedTitleState(),
|
||||
SizedBox(height: 15.0),
|
||||
_OptionListTile(
|
||||
OptionListTile(
|
||||
title: '@${widget.user.id}',
|
||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text(
|
||||
@@ -129,14 +136,19 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
||||
StreamBuilder<bool>(
|
||||
stream: StreamChannel.of(context).channel.isMutedStream,
|
||||
builder: (context, snapshot) {
|
||||
return _OptionListTile(
|
||||
return OptionListTile(
|
||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||
title: 'Mute user',
|
||||
leading: StreamSvgIcon.mute(
|
||||
size: 23.0,
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.black
|
||||
.withOpacity(0.5),
|
||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
||||
child: StreamSvgIcon.mute(
|
||||
size: 24.0,
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.black
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
trailing: snapshot.data == null
|
||||
? CircularProgressIndicator()
|
||||
@@ -171,40 +183,118 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
||||
// ),
|
||||
// onTap: () {},
|
||||
// ),
|
||||
_OptionListTile(
|
||||
OptionListTile(
|
||||
title: 'Photos & Videos',
|
||||
leading: StreamSvgIcon.pictures(
|
||||
size: 32.0,
|
||||
color:
|
||||
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: StreamSvgIcon.pictures(
|
||||
size: 36.0,
|
||||
color:
|
||||
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
trailing: StreamSvgIcon.right(
|
||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||
),
|
||||
trailing: StreamSvgIcon.right(),
|
||||
onTap: () {
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (context) => _MediaDisplayScreen()));
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: MessageSearchBloc(
|
||||
child: ChannelMediaDisplayScreen(
|
||||
sortOptions: [
|
||||
SortOption(
|
||||
'created_at',
|
||||
direction: SortOption.ASC,
|
||||
),
|
||||
],
|
||||
paginationParams: PaginationParams(limit: 20),
|
||||
onShowMessage: (m, c) async {
|
||||
final client = StreamChat.of(context).client;
|
||||
final message = m;
|
||||
final channel = client.channel(
|
||||
c.type,
|
||||
id: c.id,
|
||||
);
|
||||
if (channel.state == null) {
|
||||
await channel.watch();
|
||||
}
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.CHANNEL_PAGE,
|
||||
arguments: ChannelPageArgs(
|
||||
channel: channel,
|
||||
initialMessage: message,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
_OptionListTile(
|
||||
OptionListTile(
|
||||
title: 'Files',
|
||||
leading: StreamSvgIcon.files(
|
||||
size: 32.0,
|
||||
color:
|
||||
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: StreamSvgIcon.files(
|
||||
size: 32.0,
|
||||
color:
|
||||
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
trailing: StreamSvgIcon.right(
|
||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||
),
|
||||
trailing: StreamSvgIcon.right(),
|
||||
onTap: () {
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (context) => _FileDisplayScreen()));
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: MessageSearchBloc(
|
||||
child: ChannelFileDisplayScreen(
|
||||
sortOptions: [
|
||||
SortOption(
|
||||
'created_at',
|
||||
direction: SortOption.ASC,
|
||||
),
|
||||
],
|
||||
paginationParams: PaginationParams(limit: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
_OptionListTile(
|
||||
OptionListTile(
|
||||
title: 'Shared groups',
|
||||
leading: StreamSvgIcon.Icon_group(
|
||||
size: 24.0,
|
||||
color:
|
||||
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
||||
child: StreamSvgIcon.Icon_group(
|
||||
size: 24.0,
|
||||
color:
|
||||
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
trailing: StreamSvgIcon.right(
|
||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||
),
|
||||
trailing: StreamSvgIcon.right(),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
@@ -218,16 +308,23 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
||||
}
|
||||
|
||||
Widget _buildDeleteListTile() {
|
||||
return _OptionListTile(
|
||||
title: 'Delete conversation',
|
||||
leading: StreamSvgIcon.delete(
|
||||
color: Colors.red,
|
||||
size: 24.0,
|
||||
return OptionListTile(
|
||||
title: 'Delete Conversation',
|
||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body.copyWith(
|
||||
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||
),
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
||||
child: StreamSvgIcon.delete(
|
||||
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||
size: 24.0,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_showDeleteDialog();
|
||||
},
|
||||
titleColor: Colors.red,
|
||||
titleColor: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -239,7 +336,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
||||
question: 'Are you sure you want to delete this conversation?',
|
||||
cancelText: 'CANCEL',
|
||||
icon: StreamSvgIcon.delete(
|
||||
color: Colors.red,
|
||||
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||
),
|
||||
);
|
||||
var channel = StreamChannel.of(context).channel;
|
||||
@@ -302,72 +399,6 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
class _OptionListTile extends StatelessWidget {
|
||||
final String title;
|
||||
final StreamSvgIcon leading;
|
||||
final Widget trailing;
|
||||
final VoidCallback onTap;
|
||||
final Color titleColor;
|
||||
|
||||
_OptionListTile({
|
||||
this.title,
|
||||
this.leading,
|
||||
this.trailing,
|
||||
this.onTap,
|
||||
this.titleColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
color: StreamChatTheme.of(context).colorTheme.white,
|
||||
height: 2.0,
|
||||
),
|
||||
Material(
|
||||
color: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||
child: Container(
|
||||
height: 56.0,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
children: [
|
||||
if (leading != null)
|
||||
Expanded(
|
||||
child: Center(child: leading),
|
||||
),
|
||||
if (leading == null)
|
||||
SizedBox(
|
||||
width: 16.0,
|
||||
),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600, color: titleColor),
|
||||
)),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: trailing ?? Container(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SharedGroupsScreen extends StatefulWidget {
|
||||
final User mainUser;
|
||||
final User otherUser;
|
||||
@@ -430,12 +461,46 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
||||
},
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == null) {
|
||||
if (!snapshot.hasData) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
if (snapshot.data.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
StreamSvgIcon.message(
|
||||
size: 136.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||
),
|
||||
SizedBox(height: 16.0),
|
||||
Text(
|
||||
'No Shared Groups',
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.0),
|
||||
Text(
|
||||
'Group shared with User will appear here.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.black
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: snapshot.data.length,
|
||||
itemBuilder: (context, position) {
|
||||
@@ -530,75 +595,3 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MediaDisplayScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||
appBar: AppBar(
|
||||
brightness: Theme.of(context).brightness,
|
||||
elevation: 1,
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
'Photos & Videos',
|
||||
style: TextStyle(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
fontSize: 16.0),
|
||||
),
|
||||
leading: Center(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
child: StreamSvgIcon.left(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
size: 24.0,
|
||||
),
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FileDisplayScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||
appBar: AppBar(
|
||||
brightness: Theme.of(context).brightness,
|
||||
elevation: 1,
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
'Files',
|
||||
style: TextStyle(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
fontSize: 16.0),
|
||||
),
|
||||
leading: Center(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
child: StreamSvgIcon.left(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
size: 24.0,
|
||||
),
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:example/chat_info_screen.dart';
|
||||
import 'package:example/choose_user_page.dart';
|
||||
import 'package:example/group_info_screen.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -394,6 +396,32 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
||||
limit: 20,
|
||||
),
|
||||
channelWidget: ChannelPage(),
|
||||
onViewInfoTap: (channel) {
|
||||
if (channel.memberCount == 2 &&
|
||||
channel.isDistinct) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: ChatInfoScreen(
|
||||
user: channel.state.members.first.user,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: GroupInfoScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -434,6 +462,44 @@ class ChannelPage extends StatelessWidget {
|
||||
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||
appBar: ChannelHeader(
|
||||
showTypingIndicator: false,
|
||||
onImageTap: () async {
|
||||
var channel = StreamChannel.of(context).channel;
|
||||
|
||||
if (channel.memberCount == 2 && channel.isDistinct) {
|
||||
final currentUser = StreamChat.of(context).user;
|
||||
final otherUser = channel.state.members.firstWhere(
|
||||
(element) => element.user.id != currentUser.id,
|
||||
orElse: () => null,
|
||||
);
|
||||
if (otherUser != null) {
|
||||
final pop = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
child: ChatInfoScreen(
|
||||
user: otherUser.user,
|
||||
),
|
||||
channel: channel,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (pop == true) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
child: GroupInfoScreen(),
|
||||
channel: channel,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
@@ -449,6 +515,25 @@ class ChannelPage extends StatelessWidget {
|
||||
parent: parentMessage,
|
||||
);
|
||||
},
|
||||
onShowMessage: (m, c) async {
|
||||
final client = StreamChat.of(context).client;
|
||||
final message = m;
|
||||
final channel = client.channel(
|
||||
c.type,
|
||||
id: c.id,
|
||||
);
|
||||
if (channel.state == null) {
|
||||
await channel.watch();
|
||||
}
|
||||
Navigator.pushReplacementNamed(
|
||||
context,
|
||||
Routes.CHANNEL_PAGE,
|
||||
arguments: ChannelPageArgs(
|
||||
channel: channel,
|
||||
initialMessage: message,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
|
||||
@@ -7,6 +7,8 @@ import '../main.dart';
|
||||
import '../group_chat_details_screen.dart';
|
||||
import '../new_group_chat_screen.dart';
|
||||
import '../new_chat_screen.dart';
|
||||
import '../chat_info_screen.dart';
|
||||
import '../group_info_screen.dart';
|
||||
|
||||
class AppRoutes {
|
||||
/// Add entry for new route here
|
||||
@@ -63,6 +65,20 @@ class AppRoutes {
|
||||
selectedUsers: args,
|
||||
);
|
||||
});
|
||||
case Routes.CHAT_INFO_SCREEN:
|
||||
return MaterialPageRoute(
|
||||
settings: const RouteSettings(name: Routes.CHAT_INFO_SCREEN),
|
||||
builder: (_) {
|
||||
return ChatInfoScreen(
|
||||
user: args,
|
||||
);
|
||||
});
|
||||
case Routes.GROUP_INFO_SCREEN:
|
||||
return MaterialPageRoute(
|
||||
settings: const RouteSettings(name: Routes.GROUP_INFO_SCREEN),
|
||||
builder: (_) {
|
||||
return GroupInfoScreen();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,6 @@ class Routes {
|
||||
static const String NEW_CHAT = '/new_chat';
|
||||
static const String NEW_GROUP_CHAT = '/new_group_chat';
|
||||
static const String NEW_GROUP_CHAT_DETAILS = '/new_group_chat_details';
|
||||
static const String CHAT_INFO_SCREEN = '/chat_info_screen';
|
||||
static const String GROUP_INFO_SCREEN = '/group_info_screen';
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: example
|
||||
description: A new Flutter project.
|
||||
version: 1.0.106+109
|
||||
version: 1.0.107+110
|
||||
|
||||
environment:
|
||||
sdk: ">=2.2.2 <3.0.0"
|
||||
|
||||
@@ -129,16 +129,22 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
height: 24.0,
|
||||
),
|
||||
OptionListTile(
|
||||
leading: StreamSvgIcon.user(
|
||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: StreamSvgIcon.user(
|
||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||
),
|
||||
),
|
||||
title: 'View Info',
|
||||
onTap: widget.onViewInfoTap,
|
||||
),
|
||||
if (!channel.isDistinct)
|
||||
OptionListTile(
|
||||
leading: StreamSvgIcon.userRemove(
|
||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: StreamSvgIcon.userRemove(
|
||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||
),
|
||||
),
|
||||
title: 'Leave Group',
|
||||
onTap: () async {
|
||||
@@ -147,8 +153,11 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
),
|
||||
if (isOwner)
|
||||
OptionListTile(
|
||||
leading: StreamSvgIcon.delete(
|
||||
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: StreamSvgIcon.delete(
|
||||
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||
),
|
||||
),
|
||||
title: 'Delete Conversation',
|
||||
titleColor: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||
@@ -157,8 +166,11 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
},
|
||||
),
|
||||
OptionListTile(
|
||||
leading: StreamSvgIcon.close_small(
|
||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: StreamSvgIcon.close_small(
|
||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||
),
|
||||
),
|
||||
title: 'Cancel',
|
||||
onTap: () {
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
class ChannelFileDisplayScreen extends StatefulWidget {
|
||||
/// The sorting used for the channels matching the filters.
|
||||
/// Sorting is based on field and direction, multiple sorting options can be provided.
|
||||
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
|
||||
/// Direction can be ascending or descending.
|
||||
final List<SortOption> sortOptions;
|
||||
|
||||
/// Pagination parameters
|
||||
/// limit: the number of users to return (max is 30)
|
||||
/// offset: the offset (max is 1000)
|
||||
/// message_limit: how many messages should be included to each channel
|
||||
final PaginationParams paginationParams;
|
||||
|
||||
/// The builder used when the file list is empty.
|
||||
final WidgetBuilder emptyBuilder;
|
||||
|
||||
const ChannelFileDisplayScreen({
|
||||
this.sortOptions,
|
||||
this.paginationParams,
|
||||
this.emptyBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
_ChannelFileDisplayScreenState createState() =>
|
||||
_ChannelFileDisplayScreenState();
|
||||
}
|
||||
|
||||
class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||
messageSearchBloc.search(
|
||||
filter: {
|
||||
'cid': {
|
||||
r'$in': ['messaging:${StreamChannel.of(context).channel.id}']
|
||||
}
|
||||
},
|
||||
messageFilter: {
|
||||
'attachments.type': {
|
||||
r'$in': ['file'],
|
||||
},
|
||||
},
|
||||
sort: widget.sortOptions,
|
||||
pagination: widget.paginationParams,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||
appBar: AppBar(
|
||||
brightness: Theme.of(context).brightness,
|
||||
elevation: 1,
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
'Files',
|
||||
style: TextStyle(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
fontSize: 16.0),
|
||||
),
|
||||
leading: Center(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
child: StreamSvgIcon.left(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
size: 24.0,
|
||||
),
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||
),
|
||||
body: _buildMediaGrid(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMediaGrid() {
|
||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||
|
||||
return StreamBuilder<List<GetMessageResponse>>(
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == null) {
|
||||
return Center(
|
||||
child: const CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
if (snapshot.data.isEmpty) {
|
||||
if (widget.emptyBuilder != null) {
|
||||
return widget.emptyBuilder(context);
|
||||
}
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
StreamSvgIcon.files(
|
||||
size: 136.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||
),
|
||||
SizedBox(height: 16.0),
|
||||
Text(
|
||||
'No Files',
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.0),
|
||||
Text(
|
||||
'Files sent in this chat will appear here',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.black
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final media = <Attachment, Message>{};
|
||||
|
||||
for (var item in snapshot.data) {
|
||||
item.message.attachments.where((e) => e.type == 'file').forEach((e) {
|
||||
media[e] = item.message;
|
||||
});
|
||||
}
|
||||
|
||||
return LazyLoadScrollView(
|
||||
onEndOfPage: () => messageSearchBloc.search(
|
||||
filter: {
|
||||
'cid': {
|
||||
r'$in': ['messaging:${StreamChannel.of(context).channel.id}']
|
||||
}
|
||||
},
|
||||
messageFilter: {
|
||||
'attachments.type': {
|
||||
r'$in': ['file']
|
||||
},
|
||||
},
|
||||
sort: widget.sortOptions,
|
||||
pagination: widget.paginationParams.copyWith(
|
||||
offset: messageSearchBloc.messageResponses?.length ?? 0,
|
||||
),
|
||||
),
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, position) {
|
||||
var channel = StreamChannel.of(context).channel;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(1.0),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: FileAttachment(
|
||||
attachment: media.keys.toList()[position],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: media.length,
|
||||
),
|
||||
);
|
||||
},
|
||||
stream: messageSearchBloc.messagesStream,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
import '../stream_chat_flutter.dart';
|
||||
import './channel_name.dart';
|
||||
import 'channel_image.dart';
|
||||
import 'chat_info_screen.dart';
|
||||
import 'stream_channel.dart';
|
||||
|
||||
/// 
|
||||
@@ -99,33 +98,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
padding: const EdgeInsets.only(right: 10.0),
|
||||
child: Center(
|
||||
child: ChannelImage(
|
||||
onTap: onImageTap ??
|
||||
() async {
|
||||
if (channel.memberCount == 2 && channel.isDistinct) {
|
||||
final currentUser = StreamChat.of(context).user;
|
||||
final otherUser = channel.state.members.firstWhere(
|
||||
(element) => element.user.id != currentUser.id,
|
||||
orElse: () => null,
|
||||
);
|
||||
if (otherUser != null) {
|
||||
final pop = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: ChatInfoScreen(
|
||||
user: otherUser.user,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (pop == true) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onTap: onImageTap,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -22,6 +22,8 @@ typedef ChannelTapCallback = void Function(Channel, Widget);
|
||||
/// Builder used to create a custom [ChannelPreview] from a [Channel]
|
||||
typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel);
|
||||
|
||||
typedef ViewInfoCallback = void Function(Channel);
|
||||
|
||||
/// 
|
||||
/// 
|
||||
///
|
||||
@@ -75,7 +77,9 @@ class ChannelListView extends StatefulWidget {
|
||||
this.swipeToAction = false,
|
||||
this.pullToRefresh = true,
|
||||
this.crossAxisCount = 1,
|
||||
this.padding,
|
||||
this.selectedChannels = const [],
|
||||
this.onViewInfoTap,
|
||||
}) : super(key: key);
|
||||
|
||||
/// The builder that will be used in case of error
|
||||
@@ -139,8 +143,13 @@ class ChannelListView extends StatefulWidget {
|
||||
/// The number of children in the cross axis.
|
||||
final int crossAxisCount;
|
||||
|
||||
/// The amount of space by which to inset the children.
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
final List<Channel> selectedChannels;
|
||||
|
||||
final ViewInfoCallback onViewInfoTap;
|
||||
|
||||
@override
|
||||
_ChannelListViewState createState() => _ChannelListViewState();
|
||||
}
|
||||
@@ -277,6 +286,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
||||
if (channels.isNotEmpty) {
|
||||
if (widget.crossAxisCount > 1) {
|
||||
child = GridView.builder(
|
||||
padding: widget.padding,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: widget.crossAxisCount),
|
||||
itemCount: channels.length,
|
||||
@@ -288,6 +298,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
||||
);
|
||||
} else {
|
||||
child = ListView.separated(
|
||||
padding: widget.padding,
|
||||
physics: AlwaysScrollableScrollPhysics(),
|
||||
itemCount:
|
||||
channels.isNotEmpty ? channels.length + 1 : channels.length,
|
||||
@@ -315,6 +326,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
||||
|
||||
Widget _buildLoadingWidget() {
|
||||
return ListView(
|
||||
padding: widget.padding,
|
||||
physics: AlwaysScrollableScrollPhysics(),
|
||||
children: List.generate(
|
||||
25,
|
||||
@@ -340,9 +352,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
||||
highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 4.0,
|
||||
),
|
||||
SizedBox(height: 4.0),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
@@ -574,23 +584,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
||||
return StreamChannel(
|
||||
child: ChannelBottomSheet(
|
||||
onViewInfoTap: () {
|
||||
if (channel.memberCount == 2 &&
|
||||
channel.isDistinct) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: ChatInfoScreen(
|
||||
user:
|
||||
channel.state.members.first.user,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Add group screen
|
||||
widget.onViewInfoTap(channel);
|
||||
},
|
||||
),
|
||||
channel: channel,
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class ChannelMediaDisplayScreen extends StatefulWidget {
|
||||
/// The sorting used for the channels matching the filters.
|
||||
/// Sorting is based on field and direction, multiple sorting options can be provided.
|
||||
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
|
||||
/// Direction can be ascending or descending.
|
||||
final List<SortOption> sortOptions;
|
||||
|
||||
/// Pagination parameters
|
||||
/// limit: the number of users to return (max is 30)
|
||||
/// offset: the offset (max is 1000)
|
||||
/// message_limit: how many messages should be included to each channel
|
||||
final PaginationParams paginationParams;
|
||||
|
||||
/// The builder used when the file list is empty.
|
||||
final WidgetBuilder emptyBuilder;
|
||||
|
||||
final ShowMessageCallback onShowMessage;
|
||||
|
||||
const ChannelMediaDisplayScreen({
|
||||
this.sortOptions,
|
||||
this.paginationParams,
|
||||
this.emptyBuilder,
|
||||
this.onShowMessage,
|
||||
});
|
||||
|
||||
@override
|
||||
_ChannelMediaDisplayScreenState createState() =>
|
||||
_ChannelMediaDisplayScreenState();
|
||||
}
|
||||
|
||||
class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||
messageSearchBloc.search(
|
||||
filter: {
|
||||
'cid': {
|
||||
r'$in': ['messaging:${StreamChannel.of(context).channel.id}']
|
||||
}
|
||||
},
|
||||
messageFilter: {
|
||||
'attachments.type': {
|
||||
r'$in': ['image', 'video']
|
||||
},
|
||||
},
|
||||
sort: widget.sortOptions,
|
||||
pagination: widget.paginationParams,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||
appBar: AppBar(
|
||||
brightness: Theme.of(context).brightness,
|
||||
elevation: 1,
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
'Photos & Videos',
|
||||
style: TextStyle(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
leading: Center(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
child: StreamSvgIcon.left(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
size: 24.0,
|
||||
),
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||
),
|
||||
body: _buildMediaGrid(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMediaGrid() {
|
||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||
|
||||
return StreamBuilder<List<GetMessageResponse>>(
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == null) {
|
||||
return Center(
|
||||
child: const CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
if (snapshot.data.isEmpty) {
|
||||
if (widget.emptyBuilder != null) {
|
||||
return widget.emptyBuilder(context);
|
||||
}
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
StreamSvgIcon.pictures(
|
||||
size: 136.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||
),
|
||||
SizedBox(height: 16.0),
|
||||
Text(
|
||||
'No Media',
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.0),
|
||||
Text(
|
||||
'Photos or video sent in this chat will \nappear here',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.black
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final media = <_AssetPackage>[];
|
||||
|
||||
for (var item in snapshot.data) {
|
||||
item.message.attachments
|
||||
.where((e) => e.type == 'image' || e.type == 'video')
|
||||
.forEach((e) {
|
||||
VideoPlayerController controller;
|
||||
if (e.type == 'video') {
|
||||
controller = VideoPlayerController.network(e.assetUrl);
|
||||
controller.initialize();
|
||||
}
|
||||
media.add(_AssetPackage(e, item.message, controller));
|
||||
});
|
||||
}
|
||||
|
||||
return LazyLoadScrollView(
|
||||
onEndOfPage: () => messageSearchBloc.search(
|
||||
filter: {
|
||||
'cid': {
|
||||
r'$in': ['messaging:${StreamChannel.of(context).channel.id}']
|
||||
}
|
||||
},
|
||||
messageFilter: {
|
||||
'attachments.type': {
|
||||
r'$in': ['image', 'video']
|
||||
},
|
||||
},
|
||||
sort: widget.sortOptions,
|
||||
pagination: widget.paginationParams.copyWith(
|
||||
offset: messageSearchBloc.messageResponses?.length ?? 0,
|
||||
),
|
||||
),
|
||||
child: GridView.builder(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
|
||||
itemBuilder: (context, position) {
|
||||
var channel = StreamChannel.of(context).channel;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(1.0),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: FullScreenMedia(
|
||||
mediaAttachments:
|
||||
media.map((e) => e.attachment).toList(),
|
||||
startIndex: position,
|
||||
message: media[position].message,
|
||||
sentAt: media[position].message.createdAt,
|
||||
userName: media[position].message.user.name,
|
||||
onShowMessage: widget.onShowMessage,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: media[position].attachment.type == 'image'
|
||||
? IgnorePointer(
|
||||
child: ImageAttachment(
|
||||
attachment: media[position].attachment,
|
||||
message: media[position].message,
|
||||
showTitle: false,
|
||||
size: Size(
|
||||
MediaQuery.of(context).size.width * 0.8,
|
||||
MediaQuery.of(context).size.height * 0.3,
|
||||
),
|
||||
),
|
||||
)
|
||||
: VideoPlayer(media[position].videoPlayer),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: media.length,
|
||||
),
|
||||
);
|
||||
},
|
||||
stream: messageSearchBloc.messagesStream,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AssetPackage {
|
||||
Attachment attachment;
|
||||
Message message;
|
||||
VideoPlayerController videoPlayer;
|
||||
|
||||
_AssetPackage(this.attachment, this.message, this.videoPlayer);
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||
import '../stream_chat_flutter.dart';
|
||||
import 'channel_name.dart';
|
||||
import 'channel_unread_indicator.dart';
|
||||
import 'chat_info_screen.dart';
|
||||
|
||||
/// 
|
||||
/// 
|
||||
@@ -64,29 +63,7 @@ class ChannelPreview extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
leading: ChannelImage(
|
||||
onTap: onImageTap ??
|
||||
() {
|
||||
if (channel.memberCount == 2 && channel.isDistinct) {
|
||||
final currentUser = StreamChat.of(context).user;
|
||||
final otherUser = channel.state.members.firstWhere(
|
||||
(element) => element.user.id != currentUser.id,
|
||||
orElse: () => null,
|
||||
);
|
||||
if (otherUser != null) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: ChatInfoScreen(
|
||||
user: otherUser.user,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
onTap: onImageTap,
|
||||
),
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
||||
@@ -6,9 +6,12 @@ import 'package:photo_view/photo_view.dart';
|
||||
import 'package:stream_chat_flutter/src/image_footer.dart';
|
||||
import 'package:stream_chat_flutter/src/image_header.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'stream_channel.dart';
|
||||
|
||||
import '../stream_chat_flutter.dart';
|
||||
|
||||
typedef ShowMessageCallback = void Function(Message message, Channel channel);
|
||||
|
||||
/// A full screen image widget
|
||||
class FullScreenMedia extends StatefulWidget {
|
||||
/// The url of the image
|
||||
@@ -18,6 +21,7 @@ class FullScreenMedia extends StatefulWidget {
|
||||
final int startIndex;
|
||||
final String userName;
|
||||
final DateTime sentAt;
|
||||
final ShowMessageCallback onShowMessage;
|
||||
|
||||
/// Instantiate a new FullScreenImage
|
||||
const FullScreenMedia({
|
||||
@@ -27,6 +31,7 @@ class FullScreenMedia extends StatefulWidget {
|
||||
this.startIndex = 0,
|
||||
this.userName = '',
|
||||
this.sentAt,
|
||||
this.onShowMessage,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -167,6 +172,10 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
||||
message: widget.message,
|
||||
urls: widget.mediaAttachments,
|
||||
currentIndex: _currentPage,
|
||||
onShowMessage: () {
|
||||
widget.onShowMessage(
|
||||
widget.message, StreamChannel.of(context).channel);
|
||||
},
|
||||
),
|
||||
ImageFooter(
|
||||
currentPage: _currentPage,
|
||||
|
||||
@@ -12,6 +12,7 @@ class GiphyAttachment extends StatelessWidget {
|
||||
final MessageTheme messageTheme;
|
||||
final Message message;
|
||||
final Size size;
|
||||
final ShowMessageCallback onShowMessage;
|
||||
|
||||
const GiphyAttachment({
|
||||
Key key,
|
||||
@@ -19,6 +20,7 @@ class GiphyAttachment extends StatelessWidget {
|
||||
this.messageTheme,
|
||||
this.message,
|
||||
this.size,
|
||||
this.onShowMessage,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -75,6 +77,7 @@ class GiphyAttachment extends StatelessWidget {
|
||||
userName: message.user.name,
|
||||
sentAt: message.createdAt,
|
||||
message: message,
|
||||
onShowMessage: onShowMessage,
|
||||
),
|
||||
);
|
||||
}));
|
||||
@@ -327,6 +330,7 @@ class GiphyAttachment extends StatelessWidget {
|
||||
userName: message.user.name,
|
||||
sentAt: message.createdAt,
|
||||
message: message,
|
||||
onShowMessage: onShowMessage,
|
||||
),
|
||||
);
|
||||
}));
|
||||
|
||||
@@ -15,9 +15,15 @@ class ImageActionsModal extends StatelessWidget {
|
||||
final String sentAt;
|
||||
final List<Attachment> urls;
|
||||
final currentIndex;
|
||||
final VoidCallback onShowMessage;
|
||||
|
||||
ImageActionsModal(
|
||||
{this.message, this.userName, this.sentAt, this.urls, this.currentIndex});
|
||||
{this.message,
|
||||
this.userName,
|
||||
this.sentAt,
|
||||
this.urls,
|
||||
this.currentIndex,
|
||||
this.onShowMessage});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -134,10 +140,8 @@ class ImageActionsModal extends StatelessWidget {
|
||||
size: 24.0,
|
||||
color:
|
||||
StreamChatTheme.of(context).colorTheme.black,
|
||||
), () {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
}),
|
||||
),
|
||||
onShowMessage),
|
||||
_buildButton(
|
||||
context,
|
||||
'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}',
|
||||
|
||||
@@ -12,6 +12,8 @@ class ImageAttachment extends StatelessWidget {
|
||||
final Message message;
|
||||
final MessageTheme messageTheme;
|
||||
final Size size;
|
||||
final bool showTitle;
|
||||
final ShowMessageCallback onShowMessage;
|
||||
|
||||
const ImageAttachment({
|
||||
Key key,
|
||||
@@ -19,6 +21,8 @@ class ImageAttachment extends StatelessWidget {
|
||||
@required this.message,
|
||||
@required this.size,
|
||||
this.messageTheme,
|
||||
this.showTitle = true,
|
||||
this.onShowMessage,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -54,6 +58,7 @@ class ImageAttachment extends StatelessWidget {
|
||||
userName: message.user.name,
|
||||
sentAt: message.createdAt,
|
||||
message: message,
|
||||
onShowMessage: onShowMessage,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -83,7 +88,7 @@ class ImageAttachment extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (attachment.title != null)
|
||||
if (showTitle && attachment.title != null)
|
||||
Material(
|
||||
color: messageTheme.messageBackgroundColor,
|
||||
child: AttachmentTitle(
|
||||
@@ -93,7 +98,8 @@ class ImageAttachment extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (attachment.titleLink != null || attachment.ogScrapeUrl != null)
|
||||
if (showTitle &&
|
||||
(attachment.titleLink != null || attachment.ogScrapeUrl != null))
|
||||
Positioned.fill(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
|
||||
+115
-374
@@ -12,11 +12,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
class ImageFooter extends StatefulWidget {
|
||||
class ImageFooter extends StatefulWidget implements PreferredSizeWidget {
|
||||
/// Callback to call when pressing the back button.
|
||||
/// By default it calls [Navigator.pop]
|
||||
final VoidCallback onBackPressed;
|
||||
@@ -48,21 +47,25 @@ class ImageFooter extends StatefulWidget {
|
||||
this.message,
|
||||
this.videoPackages,
|
||||
this.mediaSelectedCallBack,
|
||||
}) : super(key: key);
|
||||
}) : preferredSize = Size.fromHeight(kToolbarHeight),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
_ImageFooterState createState() => _ImageFooterState();
|
||||
|
||||
@override
|
||||
final Size preferredSize;
|
||||
}
|
||||
|
||||
class _ImageFooterState extends State<ImageFooter> {
|
||||
bool _userSearchMode = false;
|
||||
TextEditingController _searchController;
|
||||
TextEditingController _messageController = TextEditingController();
|
||||
FocusNode _messageFocusNode = FocusNode();
|
||||
final TextEditingController _messageController = TextEditingController();
|
||||
final FocusNode _messageFocusNode = FocusNode();
|
||||
|
||||
String _channelNameQuery;
|
||||
|
||||
List<Channel> _selectedChannels = [];
|
||||
final List<Channel> _selectedChannels = [];
|
||||
bool _loading = false;
|
||||
|
||||
Timer _debounce;
|
||||
@@ -99,54 +102,73 @@ class _ImageFooterState extends State<ImageFooter> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Container(
|
||||
color:
|
||||
StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color,
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: StreamSvgIcon.icon_SHARE(
|
||||
size: 24.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
return SizedBox.fromSize(
|
||||
size: Size(
|
||||
MediaQuery.of(context).size.width,
|
||||
MediaQuery.of(context).padding.bottom + widget.preferredSize.height,
|
||||
),
|
||||
child: MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeTop: true,
|
||||
child: BottomAppBar(
|
||||
color: StreamChatTheme.of(context).colorTheme.white,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: StreamSvgIcon.icon_SHARE(
|
||||
size: 24.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
),
|
||||
onPressed: () async {
|
||||
final attachment =
|
||||
widget.mediaAttachments[widget.currentPage];
|
||||
var url = attachment.imageUrl ??
|
||||
attachment.assetUrl ??
|
||||
attachment.thumbUrl;
|
||||
var type = attachment.type == 'image'
|
||||
? 'jpg'
|
||||
: url?.split('?')?.first?.split('.')?.last ?? 'jpg';
|
||||
var request = await HttpClient().getUrl(Uri.parse(url));
|
||||
var response = await request.close();
|
||||
var bytes =
|
||||
await consolidateHttpClientResponseBytes(response);
|
||||
await Share.file('File', 'image.$type', bytes, 'image/$type');
|
||||
},
|
||||
),
|
||||
onPressed: () {
|
||||
_buildShareModal(context);
|
||||
},
|
||||
),
|
||||
InkWell(
|
||||
onTap: widget.onTitleTap,
|
||||
child: Container(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'${widget.currentPage + 1} of ${widget.totalPages}',
|
||||
style: StreamChatTheme.of(context).textTheme.headlineBold,
|
||||
),
|
||||
],
|
||||
InkWell(
|
||||
onTap: widget.onTitleTap,
|
||||
child: Container(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'${widget.currentPage + 1} of ${widget.totalPages}',
|
||||
style:
|
||||
StreamChatTheme.of(context).textTheme.headlineBold,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: StreamSvgIcon.Icon_grid(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
IconButton(
|
||||
icon: StreamSvgIcon.Icon_grid(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
),
|
||||
onPressed: () {
|
||||
_buildPhotosModal(context);
|
||||
},
|
||||
),
|
||||
onPressed: () {
|
||||
_buildPhotosModal(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _buildPhotosModal(context) {
|
||||
Widget _buildPhotosModal(context) {
|
||||
var videoAttachments = widget.mediaAttachments
|
||||
.where((element) => element.type == 'video')
|
||||
.toList();
|
||||
@@ -247,305 +269,26 @@ class _ImageFooterState extends State<ImageFooter> {
|
||||
);
|
||||
}
|
||||
|
||||
void _buildShareModal(context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(builder: (context, modalSetState) {
|
||||
modalSetStateCallback = modalSetState;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: _userSearchMode || _messageFocusNode.hasFocus
|
||||
? 16.0
|
||||
: MediaQuery.of(context).size.height / 2,
|
||||
left: 8.0,
|
||||
right: 8.0),
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16.0),
|
||||
topRight: Radius.circular(16.0),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Scaffold(
|
||||
body: UsersBloc(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildTextInputSection(modalSetState),
|
||||
if (_userSearchMode)
|
||||
SizedBox(
|
||||
height: 22.0,
|
||||
),
|
||||
Expanded(
|
||||
child: ChannelsBloc(
|
||||
child: ChannelListView(
|
||||
selectedChannels: _selectedChannels,
|
||||
onChannelTap: (channel, _) {
|
||||
_searchController.clear();
|
||||
if (!_selectedChannels.contains(channel)) {
|
||||
modalSetState(() {
|
||||
_selectedChannels.add(channel);
|
||||
});
|
||||
} else {
|
||||
modalSetState(() {
|
||||
_selectedChannels.remove(channel);
|
||||
});
|
||||
}
|
||||
},
|
||||
crossAxisCount: 4,
|
||||
pagination: PaginationParams(
|
||||
limit: 25,
|
||||
),
|
||||
filter: {
|
||||
if (_channelNameQuery?.trim()?.isNotEmpty == true)
|
||||
'name': {
|
||||
r'$autocomplete': _channelNameQuery,
|
||||
},
|
||||
'id': {
|
||||
r'$ne': StreamChat.of(context).user.id,
|
||||
},
|
||||
},
|
||||
sort: [
|
||||
SortOption(
|
||||
'name',
|
||||
direction: SortOption.ASC,
|
||||
),
|
||||
],
|
||||
emptyBuilder: (_) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, viewportConstraints) {
|
||||
return SingleChildScrollView(
|
||||
physics: AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight:
|
||||
viewportConstraints.maxHeight,
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: StreamSvgIcon.search(
|
||||
size: 96,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'No chat matches these keywords...'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_selectedChannels.isNotEmpty)
|
||||
_buildShareTextInputSection(modalSetState),
|
||||
if (!_userSearchMode && _selectedChannels.isEmpty)
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
color: StreamChatTheme.of(context).colorTheme.white,
|
||||
height: 48.0,
|
||||
child: Material(
|
||||
color:
|
||||
StreamChatTheme.of(context).colorTheme.white,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
var url = widget
|
||||
.mediaAttachments[widget.currentPage]
|
||||
.imageUrl ??
|
||||
widget
|
||||
.mediaAttachments[widget.currentPage]
|
||||
.assetUrl ??
|
||||
widget
|
||||
.mediaAttachments[widget.currentPage]
|
||||
.thumbUrl;
|
||||
|
||||
if (widget
|
||||
.mediaAttachments[widget.currentPage]
|
||||
.type ==
|
||||
'video') {
|
||||
await _saveVideo(url);
|
||||
Navigator.pop(context);
|
||||
} else {
|
||||
await _saveImage(url);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: SizedBox.expand(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Save to Photos',
|
||||
style: TextStyle(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.accentBlue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextInputSection(modalSetState) {
|
||||
if (_userSearchMode) {
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 16.0,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 16.0,
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
cursorColor: StreamChatTheme.of(context).colorTheme.black,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
prefixIconConstraints:
|
||||
BoxConstraints.tight(Size(36.0, 44.0)),
|
||||
prefixIcon: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 2.0, horizontal: 6.0),
|
||||
child: StreamSvgIcon.search(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
),
|
||||
),
|
||||
hintText: 'Search',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(32.0),
|
||||
borderSide: BorderSide(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.black
|
||||
.withOpacity(0.08)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(32.0),
|
||||
borderSide: BorderSide(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.black
|
||||
.withOpacity(0.08)),
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 8.0,
|
||||
),
|
||||
IconButton(
|
||||
icon: StreamSvgIcon.close_small(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.black
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
onPressed: () {
|
||||
modalSetState(() {
|
||||
_userSearchMode = false;
|
||||
});
|
||||
setState(() {});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: IconButton(
|
||||
icon: StreamSvgIcon.search(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
),
|
||||
iconSize: 24.0,
|
||||
onPressed: () {
|
||||
modalSetState(() {
|
||||
_userSearchMode = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
'Select a Chat to Share',
|
||||
style: StreamChatTheme.of(context).textTheme.headlineBold,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: IconButton(
|
||||
icon: StreamSvgIcon.share_arrow(
|
||||
color: StreamChatTheme.of(context).colorTheme.black,
|
||||
),
|
||||
onPressed: () async {
|
||||
var url =
|
||||
widget.mediaAttachments[widget.currentPage].imageUrl ??
|
||||
widget.mediaAttachments[widget.currentPage].assetUrl ??
|
||||
widget.mediaAttachments[widget.currentPage].thumbUrl;
|
||||
var type =
|
||||
widget.mediaAttachments[widget.currentPage].type == 'image'
|
||||
? 'jpg'
|
||||
: url?.split('?')?.first?.split('.')?.last ?? 'jpg';
|
||||
var request = await HttpClient().getUrl(Uri.parse(url));
|
||||
var response = await request.close();
|
||||
var bytes = await consolidateHttpClientResponseBytes(response);
|
||||
await Share.file('File', 'image.$type', bytes, 'image/$type');
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildShareTextInputSection(modalSetState) {
|
||||
return Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
color: StreamChatTheme.of(context).colorTheme.white,
|
||||
height: 56.0,
|
||||
child: _loading
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: BottomAppBar(
|
||||
child: Container(
|
||||
height: 40.0,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 8,
|
||||
),
|
||||
child: _loading
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _messageController,
|
||||
focusNode: _messageFocusNode,
|
||||
@@ -557,11 +300,10 @@ class _ImageFooterState extends State<ImageFooter> {
|
||||
setState(() {});
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
prefixText: ' ',
|
||||
hintText: 'Add a comment',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(32.0),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
borderSide: BorderSide(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
@@ -570,49 +312,48 @@ class _ImageFooterState extends State<ImageFooter> {
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(32.0),
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
borderSide: BorderSide(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.black
|
||||
.withOpacity(0.16),
|
||||
)),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 12.0),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconTheme(
|
||||
data: StreamChatTheme.of(context)
|
||||
.channelTheme
|
||||
.messageInputButtonIconTheme,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
modalSetState(() {
|
||||
_loading = true;
|
||||
});
|
||||
await sendMessage();
|
||||
modalSetState(() {
|
||||
_loading = false;
|
||||
});
|
||||
},
|
||||
child: Transform.rotate(
|
||||
angle: -pi / 2,
|
||||
child: StreamSvgIcon.Icon_send_message(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.accentBlue,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
IconTheme(
|
||||
data: StreamChatTheme.of(context)
|
||||
.channelTheme
|
||||
.messageInputButtonIconTheme,
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
modalSetState(() => _loading = true);
|
||||
await sendMessage();
|
||||
modalSetState(() => _loading = false);
|
||||
},
|
||||
splashRadius: 24,
|
||||
visualDensity: VisualDensity.compact,
|
||||
constraints: BoxConstraints.tightFor(
|
||||
height: 24,
|
||||
width: 24,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Transform.rotate(
|
||||
angle: -pi / 2,
|
||||
child: StreamSvgIcon.Icon_send_message(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.accentBlue,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ class ImageGroup extends StatelessWidget {
|
||||
@required this.images,
|
||||
@required this.message,
|
||||
@required this.size,
|
||||
this.onShowMessage,
|
||||
}) : super(key: key);
|
||||
|
||||
final List<Attachment> images;
|
||||
final Message message;
|
||||
final Size size;
|
||||
final ShowMessageCallback onShowMessage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -119,6 +121,7 @@ class ImageGroup extends StatelessWidget {
|
||||
userName: message.user.name,
|
||||
sentAt: message.createdAt,
|
||||
message: message,
|
||||
onShowMessage: onShowMessage,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,6 +13,9 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// By default it calls [Navigator.pop]
|
||||
final VoidCallback onBackPressed;
|
||||
|
||||
/// Callback to call when pressing the show message button.
|
||||
final VoidCallback onShowMessage;
|
||||
|
||||
/// Callback to call when the header is tapped.
|
||||
final VoidCallback onTitleTap;
|
||||
|
||||
@@ -35,6 +38,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
this.currentIndex,
|
||||
this.showBackButton = true,
|
||||
this.onBackPressed,
|
||||
this.onShowMessage,
|
||||
this.onTitleTap,
|
||||
this.onImageTap,
|
||||
this.userName = '',
|
||||
@@ -111,6 +115,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
message: message,
|
||||
urls: urls,
|
||||
currentIndex: currentIndex,
|
||||
onShowMessage: onShowMessage,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -116,6 +116,7 @@ class MessageListView extends StatefulWidget {
|
||||
this.scrollController,
|
||||
this.itemPositionListener,
|
||||
this.highlightInitialMessage = false,
|
||||
this.onShowMessage,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Function used to build a custom message widget
|
||||
@@ -162,6 +163,8 @@ class MessageListView extends StatefulWidget {
|
||||
/// Also See [StreamChannel]
|
||||
final bool highlightInitialMessage;
|
||||
|
||||
final ShowMessageCallback onShowMessage;
|
||||
|
||||
@override
|
||||
_MessageListViewState createState() => _MessageListViewState();
|
||||
}
|
||||
@@ -711,6 +714,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
messageTheme: isMyMessage
|
||||
? StreamChatTheme.of(context).ownMessageTheme
|
||||
: StreamChatTheme.of(context).otherMessageTheme,
|
||||
onShowMessage: widget.onShowMessage,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -788,6 +792,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
: StreamChatTheme.of(context).otherMessageTheme,
|
||||
readList: readList,
|
||||
allRead: allRead,
|
||||
onShowMessage: widget.onShowMessage,
|
||||
);
|
||||
|
||||
if (!initialMessageHighlightComplete &&
|
||||
|
||||
@@ -55,6 +55,7 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
|
||||
/// Calls [Client.search] updating [queryMessagesLoading] stream
|
||||
Future<void> search({
|
||||
Map<String, dynamic> filter,
|
||||
Map<String, dynamic> messageFilter,
|
||||
List<SortOption> sort,
|
||||
String query,
|
||||
PaginationParams pagination,
|
||||
@@ -78,6 +79,7 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
|
||||
sort,
|
||||
query,
|
||||
pagination,
|
||||
messageFilters: messageFilter,
|
||||
);
|
||||
|
||||
if (clear) {
|
||||
|
||||
@@ -119,6 +119,8 @@ class MessageWidget extends StatefulWidget {
|
||||
|
||||
final List<Read> readList;
|
||||
|
||||
final ShowMessageCallback onShowMessage;
|
||||
|
||||
/// If true show the users username next to the timestamp of the message
|
||||
final bool showUsername;
|
||||
final bool showTimestamp;
|
||||
@@ -157,6 +159,7 @@ class MessageWidget extends StatefulWidget {
|
||||
this.onUserAvatarTap,
|
||||
this.onLinkTap,
|
||||
this.onMessageActions,
|
||||
this.onShowMessage,
|
||||
this.editMessageInputBuilder,
|
||||
this.textBuilder,
|
||||
Map<String, AttachmentBuilder> customAttachmentBuilders,
|
||||
@@ -179,6 +182,7 @@ class MessageWidget extends StatefulWidget {
|
||||
MediaQuery.of(context).size.width * 0.8,
|
||||
MediaQuery.of(context).size.height * 0.3,
|
||||
),
|
||||
onShowMessage: onShowMessage,
|
||||
);
|
||||
},
|
||||
'video': (context, message, attachment) {
|
||||
@@ -190,6 +194,7 @@ class MessageWidget extends StatefulWidget {
|
||||
MediaQuery.of(context).size.height * 0.3,
|
||||
),
|
||||
message: message,
|
||||
onShowMessage: onShowMessage,
|
||||
);
|
||||
},
|
||||
'giphy': (context, message, attachment) {
|
||||
@@ -201,6 +206,7 @@ class MessageWidget extends StatefulWidget {
|
||||
MediaQuery.of(context).size.width * 0.8,
|
||||
MediaQuery.of(context).size.height * 0.3,
|
||||
),
|
||||
onShowMessage: onShowMessage,
|
||||
);
|
||||
},
|
||||
'file': (context, message, attachment) {
|
||||
@@ -710,6 +716,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
),
|
||||
images: images,
|
||||
message: widget.message,
|
||||
onShowMessage: widget.onShowMessage,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -4,10 +4,13 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||
|
||||
class OptionListTile extends StatelessWidget {
|
||||
final String title;
|
||||
final StreamSvgIcon leading;
|
||||
final Widget leading;
|
||||
final Widget trailing;
|
||||
final VoidCallback onTap;
|
||||
final Color titleColor;
|
||||
final Color tileColor;
|
||||
final Color separatorColor;
|
||||
final TextStyle titleTextStyle;
|
||||
|
||||
OptionListTile({
|
||||
this.title,
|
||||
@@ -15,6 +18,9 @@ class OptionListTile extends StatelessWidget {
|
||||
this.trailing,
|
||||
this.onTap,
|
||||
this.titleColor,
|
||||
this.tileColor,
|
||||
this.separatorColor,
|
||||
this.titleTextStyle,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -22,21 +28,19 @@ class OptionListTile extends StatelessWidget {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||
color: separatorColor ??
|
||||
StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||
height: 1.0,
|
||||
),
|
||||
Material(
|
||||
color: StreamChatTheme.of(context).colorTheme.white,
|
||||
color: tileColor ?? StreamChatTheme.of(context).colorTheme.white,
|
||||
child: Container(
|
||||
height: 63.0,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
children: [
|
||||
if (leading != null)
|
||||
Expanded(
|
||||
child: Center(child: leading),
|
||||
),
|
||||
if (leading != null) Center(child: leading),
|
||||
if (leading == null)
|
||||
SizedBox(
|
||||
width: 16.0,
|
||||
@@ -45,14 +49,15 @@ class OptionListTile extends StatelessWidget {
|
||||
flex: 4,
|
||||
child: Text(
|
||||
title,
|
||||
style: titleColor == null
|
||||
? StreamChatTheme.of(context).textTheme.bodyBold
|
||||
: StreamChatTheme.of(context)
|
||||
.textTheme
|
||||
.bodyBold
|
||||
.copyWith(
|
||||
color: titleColor,
|
||||
),
|
||||
style: titleTextStyle ??
|
||||
(titleColor == null
|
||||
? StreamChatTheme.of(context).textTheme.bodyBold
|
||||
: StreamChatTheme.of(context)
|
||||
.textTheme
|
||||
.bodyBold
|
||||
.copyWith(
|
||||
color: titleColor,
|
||||
)),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
||||
@@ -781,4 +781,16 @@ class StreamSvgIcon extends StatelessWidget {
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.Icon_user_settings({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'Icon_user_settings.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ class VideoAttachment extends StatefulWidget {
|
||||
final MessageTheme messageTheme;
|
||||
final Size size;
|
||||
final Message message;
|
||||
final ShowMessageCallback onShowMessage;
|
||||
|
||||
VideoAttachment({
|
||||
Key key,
|
||||
@@ -21,6 +22,7 @@ class VideoAttachment extends StatefulWidget {
|
||||
@required this.messageTheme,
|
||||
this.message,
|
||||
this.size,
|
||||
this.onShowMessage,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -95,6 +97,7 @@ class _VideoAttachmentState extends State<VideoAttachment> {
|
||||
userName: widget.message.user.name,
|
||||
sentAt: widget.message.createdAt,
|
||||
message: widget.message,
|
||||
onShowMessage: widget.onShowMessage,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -43,4 +43,6 @@ export 'src/message_search_bloc.dart';
|
||||
export 'src/message_search_item.dart';
|
||||
export 'src/message_search_list_view.dart';
|
||||
export 'src/unread_indicator.dart';
|
||||
export 'src/chat_info_screen.dart';
|
||||
export 'src/option_list_tile.dart';
|
||||
export 'src/channel_file_display_screen.dart';
|
||||
export 'src/channel_media_display_screen.dart';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 11C14.2091 11 16 9.20914 16 7C16 4.79086 14.2091 3 12 3C9.79086 3 8 4.79086 8 7C8 9.20914 9.79086 11 12 11ZM12 9C13.1046 9 14 8.10457 14 7C14 5.89543 13.1046 5 12 5C10.8954 5 10 5.89543 10 7C10 8.10457 10.8954 9 12 9Z" fill="#7A7A7A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 12C6.4686 12 4 15.6324 4 18C4 18.5523 3.55228 19 3 19C2.44771 19 2 18.5523 2 18C2 14.3676 5.5314 10 12 10C13.9955 10 15.7139 10.4121 17.1402 11.0991C17.6378 11.3387 17.8469 11.9363 17.6072 12.4339C17.3676 12.9315 16.7699 13.1406 16.2724 12.9009C15.1351 12.3532 13.716 12 12 12Z" fill="#7A7A7A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.5 14C18.2239 14 18 14.2239 18 14.5V15.2679L17.3349 14.884C17.0958 14.7459 16.79 14.8278 16.6519 15.067L16.1519 15.933C16.0139 16.1722 16.0958 16.478 16.3349 16.616L17 17L16.3349 17.384C16.0958 17.522 16.0138 17.8278 16.1519 18.067L16.6519 18.933C16.79 19.1722 17.0958 19.2541 17.3349 19.116L18 18.732V19.5C18 19.7761 18.2239 20 18.5 20H19.5C19.7761 20 20 19.7761 20 19.5V18.732L20.6651 19.116C20.9042 19.2541 21.21 19.1722 21.3481 18.933L21.8481 18.067C21.9861 17.8278 21.9042 17.522 21.6651 17.384L21 17L21.665 16.616C21.9042 16.478 21.9861 16.1722 21.8481 15.933L21.3481 15.067C21.21 14.8278 20.9042 14.7459 20.665 14.884L20 15.2679V14.5C20 14.2239 19.7761 14 19.5 14H18.5ZM19 18C19.5523 18 20 17.5523 20 17C20 16.4477 19.5523 16 19 16C18.4477 16 18 16.4477 18 17C18 17.5523 18.4477 18 19 18Z" fill="#7A7A7A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
Reference in New Issue
Block a user