@@ -1,9 +1,6 @@
|
|||||||
import 'package:collection/collection.dart' show IterableExtension;
|
import 'package:collection/collection.dart' show IterableExtension;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:jiffy/jiffy.dart';
|
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
|
||||||
|
|
||||||
/*Navigator.push(
|
/*Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:imessage/message_list_view.dart';
|
import 'package:imessage/message_list_view.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'
|
||||||
show
|
show
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 46;
|
objectVersion = 50;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 1140;
|
LastSwiftUpdateCheck = 1140;
|
||||||
LastUpgradeCheck = 1020;
|
LastUpgradeCheck = 1300;
|
||||||
ORGANIZATIONNAME = "The Chromium Authors";
|
ORGANIZATIONNAME = "The Chromium Authors";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
97C146ED1CF9000F007C117D = {
|
97C146ED1CF9000F007C117D = {
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
|
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework",
|
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework",
|
"${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework",
|
"${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
|
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
|
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework",
|
"${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework",
|
||||||
@@ -256,16 +256,16 @@
|
|||||||
"${BUILT_PRODUCTS_DIR}/image_gallery_saver/image_gallery_saver.framework",
|
"${BUILT_PRODUCTS_DIR}/image_gallery_saver/image_gallery_saver.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework",
|
"${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework",
|
"${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
|
"${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework",
|
"${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework",
|
"${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework",
|
"${BUILT_PRODUCTS_DIR}/shared_preferences_ios/shared_preferences_ios.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework",
|
"${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/sqlite3/sqlite3.framework",
|
"${BUILT_PRODUCTS_DIR}/sqlite3/sqlite3.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/sqlite3_flutter_libs/sqlite3_flutter_libs.framework",
|
"${BUILT_PRODUCTS_DIR}/sqlite3_flutter_libs/sqlite3_flutter_libs.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework",
|
"${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/video_compress/video_compress.framework",
|
"${BUILT_PRODUCTS_DIR}/video_compress/video_compress.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/video_player/video_player.framework",
|
"${BUILT_PRODUCTS_DIR}/video_player_avfoundation/video_player_avfoundation.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/video_thumbnail/video_thumbnail.framework",
|
"${BUILT_PRODUCTS_DIR}/video_thumbnail/video_thumbnail.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/wakelock/wakelock.framework",
|
"${BUILT_PRODUCTS_DIR}/wakelock/wakelock.framework",
|
||||||
);
|
);
|
||||||
@@ -285,16 +285,16 @@
|
|||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_gallery_saver.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_gallery_saver.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_ios.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3_flutter_libs.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3_flutter_libs.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_compress.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_compress.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_player.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_player_avfoundation.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_thumbnail.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_thumbnail.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock.framework",
|
||||||
);
|
);
|
||||||
@@ -442,7 +442,10 @@
|
|||||||
);
|
);
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
@@ -581,7 +584,10 @@
|
|||||||
);
|
);
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
@@ -615,7 +621,10 @@
|
|||||||
);
|
);
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1020"
|
LastUpgradeVersion = "1300"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:example/localizations.dart';
|
|||||||
import 'package:example/routes/routes.dart';
|
import 'package:example/routes/routes.dart';
|
||||||
import 'package:example/stream_version.dart';
|
import 'package:example/stream_version.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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';
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +1,42 @@
|
|||||||
import 'package:example/localizations.dart';
|
import 'package:example/localizations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
class ChannelFileDisplayScreen extends StatefulWidget {
|
class ChannelFileDisplayScreen extends StatefulWidget {
|
||||||
/// The sorting used for the channels matching the filters.
|
final StreamMessageThemeData messageTheme;
|
||||||
/// 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({
|
const ChannelFileDisplayScreen({
|
||||||
this.sortOptions,
|
Key? key,
|
||||||
this.paginationParams = const PaginationParams(limit: 20),
|
required this.messageTheme,
|
||||||
this.emptyBuilder,
|
}) : super(key: key);
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ChannelFileDisplayScreenState createState() =>
|
State<ChannelFileDisplayScreen> createState() =>
|
||||||
_ChannelFileDisplayScreenState();
|
_ChannelFileDisplayScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
|
class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
|
||||||
@override
|
final Map<String?, VideoPlayerController?> controllerCache = {};
|
||||||
void initState() {
|
|
||||||
super.initState();
|
late final controller = StreamMessageSearchListController(
|
||||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
client: StreamChat.of(context).client,
|
||||||
messageSearchBloc.search(
|
filter: Filter.in_(
|
||||||
filter: Filter.in_(
|
'cid',
|
||||||
'cid',
|
[StreamChannel.of(context).channel.cid!],
|
||||||
[StreamChannel.of(context).channel.cid!],
|
),
|
||||||
|
messageFilter: Filter.in_(
|
||||||
|
'attachments.type',
|
||||||
|
['file'],
|
||||||
|
),
|
||||||
|
sort: [
|
||||||
|
SortOption(
|
||||||
|
'created_at',
|
||||||
|
direction: SortOption.ASC,
|
||||||
),
|
),
|
||||||
messageFilter: Filter.in_(
|
],
|
||||||
'attachments.type',
|
limit: 20,
|
||||||
['file'],
|
);
|
||||||
),
|
|
||||||
sort: widget.sortOptions,
|
|
||||||
pagination: widget.paginationParams,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -58,120 +48,109 @@ class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
|
|||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(context).files,
|
AppLocalizations.of(context).files,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
fontSize: 16.0),
|
fontSize: 16.0,
|
||||||
),
|
|
||||||
leading: Center(
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
width: 24.0,
|
|
||||||
height: 24.0,
|
|
||||||
child: StreamSvgIcon.left(
|
|
||||||
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
|
||||||
size: 24.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
leading: StreamBackButton(),
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
),
|
),
|
||||||
body: _buildMediaGrid(),
|
body: ValueListenableBuilder(
|
||||||
);
|
valueListenable: controller,
|
||||||
}
|
builder: (
|
||||||
|
BuildContext context,
|
||||||
Widget _buildMediaGrid() {
|
PagedValue<String, GetMessageResponse> value,
|
||||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
Widget? child,
|
||||||
|
) {
|
||||||
return StreamBuilder<List<GetMessageResponse>>(
|
return value.when(
|
||||||
builder: (context, snapshot) {
|
(items, nextPageKey, error) {
|
||||||
if (snapshot.data == null) {
|
if (items.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
child: const CircularProgressIndicator(),
|
child: Column(
|
||||||
);
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
}
|
children: [
|
||||||
|
StreamSvgIcon.files(
|
||||||
if (snapshot.data!.isEmpty) {
|
size: 136.0,
|
||||||
if (widget.emptyBuilder != null) {
|
color: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
return widget.emptyBuilder!(context);
|
),
|
||||||
}
|
SizedBox(height: 16.0),
|
||||||
return Center(
|
Text(
|
||||||
child: Column(
|
AppLocalizations.of(context).noFiles,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
style: TextStyle(
|
||||||
children: [
|
fontSize: 14.0,
|
||||||
StreamSvgIcon.files(
|
color: StreamChatTheme.of(context)
|
||||||
size: 136.0,
|
.colorTheme
|
||||||
color: StreamChatTheme.of(context).colorTheme.disabled,
|
.textHighEmphasis,
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.0),
|
),
|
||||||
Text(
|
SizedBox(height: 8.0),
|
||||||
AppLocalizations.of(context).noFiles,
|
Text(
|
||||||
style: TextStyle(
|
AppLocalizations.of(context).filesAppearHere,
|
||||||
fontSize: 14.0,
|
textAlign: TextAlign.center,
|
||||||
color:
|
style: TextStyle(
|
||||||
StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
fontSize: 14.0,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
SizedBox(height: 8.0),
|
}
|
||||||
Text(
|
final media = <Attachment, Message>{};
|
||||||
AppLocalizations.of(context).filesAppearHere,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.0,
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.textHighEmphasis
|
|
||||||
.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final media = <Attachment, Message>{};
|
for (var item in items) {
|
||||||
|
item.message.attachments
|
||||||
|
.where((e) => e.type == 'file')
|
||||||
|
.forEach((e) {
|
||||||
|
media[e] = item.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for (var item in snapshot.data!) {
|
return LazyLoadScrollView(
|
||||||
item.message.attachments.where((e) => e.type == 'file').forEach((e) {
|
onEndOfPage: () async {
|
||||||
media[e] = item.message;
|
if (nextPageKey != null) {
|
||||||
});
|
controller.loadMore(nextPageKey);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
return LazyLoadScrollView(
|
child: ListView.builder(
|
||||||
onEndOfPage: () => messageSearchBloc.search(
|
itemBuilder: (context, position) {
|
||||||
filter: Filter.in_(
|
return Padding(
|
||||||
'cid',
|
padding: const EdgeInsets.all(1.0),
|
||||||
[StreamChannel.of(context).channel.cid!],
|
child: Padding(
|
||||||
),
|
padding: const EdgeInsets.all(8.0),
|
||||||
messageFilter: Filter.in_(
|
child: StreamFileAttachment(
|
||||||
'attachments.type',
|
message: media.values.toList()[position],
|
||||||
['file'],
|
attachment: media.keys.toList()[position],
|
||||||
),
|
),
|
||||||
sort: widget.sortOptions,
|
),
|
||||||
pagination: widget.paginationParams.copyWith(
|
);
|
||||||
offset: messageSearchBloc.messageResponses?.length ?? 0,
|
},
|
||||||
),
|
itemCount: media.length,
|
||||||
),
|
|
||||||
child: ListView.builder(
|
|
||||||
itemBuilder: (context, position) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(1.0),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: FileAttachment(
|
|
||||||
message: media.values.toList()[position],
|
|
||||||
attachment: media.keys.toList()[position],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
itemCount: media.length,
|
loading: () => Center(
|
||||||
),
|
child: const CircularProgressIndicator(),
|
||||||
);
|
),
|
||||||
},
|
error: (_) => Offstage(),
|
||||||
stream: messageSearchBloc.messagesStream,
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
controller.doInitialLoad();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:example/search_text_field.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
|
|
||||||
import 'channel_page.dart';
|
import 'channel_page.dart';
|
||||||
import 'chat_info_screen.dart';
|
import 'chat_info_screen.dart';
|
||||||
@@ -19,9 +20,21 @@ class ChannelList extends StatefulWidget {
|
|||||||
class _ChannelList extends State<ChannelList> {
|
class _ChannelList extends State<ChannelList> {
|
||||||
ScrollController _scrollController = ScrollController();
|
ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
TextEditingController? _controller;
|
late StreamMessageSearchListController _messageSearchListController =
|
||||||
|
StreamMessageSearchListController(
|
||||||
|
client: StreamChat.of(context).client,
|
||||||
|
filter: Filter.in_('members', [StreamChat.of(context).currentUser!.id]),
|
||||||
|
limit: 5,
|
||||||
|
searchQuery: '',
|
||||||
|
sort: [
|
||||||
|
SortOption(
|
||||||
|
'created_at',
|
||||||
|
direction: SortOption.ASC,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
String _channelQuery = '';
|
TextEditingController? _controller;
|
||||||
|
|
||||||
bool _isSearchActive = false;
|
bool _isSearchActive = false;
|
||||||
|
|
||||||
@@ -31,14 +44,25 @@ class _ChannelList extends State<ChannelList> {
|
|||||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||||
_debounce = Timer(const Duration(milliseconds: 350), () {
|
_debounce = Timer(const Duration(milliseconds: 350), () {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
_messageSearchListController.searchQuery = _controller!.text;
|
||||||
setState(() {
|
setState(() {
|
||||||
_channelQuery = _controller!.text;
|
_isSearchActive = _controller!.text.isNotEmpty;
|
||||||
_isSearchActive = _channelQuery.isNotEmpty;
|
|
||||||
});
|
});
|
||||||
|
if (_isSearchActive) _messageSearchListController.doInitialLoad();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
late final _channelListController = StreamChannelListController(
|
||||||
|
client: StreamChat.of(context).client,
|
||||||
|
filter: Filter.in_(
|
||||||
|
'members',
|
||||||
|
[StreamChat.of(context).currentUser!.id],
|
||||||
|
),
|
||||||
|
presence: true,
|
||||||
|
limit: 30,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -50,12 +74,12 @@ class _ChannelList extends State<ChannelList> {
|
|||||||
_controller?.removeListener(_channelQueryListener);
|
_controller?.removeListener(_channelQueryListener);
|
||||||
_controller?.dispose();
|
_controller?.dispose();
|
||||||
_scrollController.dispose();
|
_scrollController.dispose();
|
||||||
|
_channelListController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final user = StreamChat.of(context).currentUser;
|
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
if (_isSearchActive) {
|
if (_isSearchActive) {
|
||||||
@@ -86,18 +110,8 @@ class _ChannelList extends State<ChannelList> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
body: _isSearchActive
|
body: _isSearchActive
|
||||||
? MessageSearchListView(
|
? StreamMessageSearchListView(
|
||||||
showErrorTile: true,
|
controller: _messageSearchListController,
|
||||||
messageQuery: _channelQuery,
|
|
||||||
filters: Filter.in_('members', [user!.id]),
|
|
||||||
sortOptions: [
|
|
||||||
SortOption(
|
|
||||||
'created_at',
|
|
||||||
direction: SortOption.ASC,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
pullToRefresh: false,
|
|
||||||
limit: 30,
|
|
||||||
emptyBuilder: (_) {
|
emptyBuilder: (_) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, viewportConstraints) {
|
builder: (context, viewportConstraints) {
|
||||||
@@ -128,80 +142,175 @@ class _ChannelList extends State<ChannelList> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onItemTap: (messageResponse) async {
|
itemBuilder: (
|
||||||
FocusScope.of(context).requestFocus(FocusNode());
|
context,
|
||||||
final client = StreamChat.of(context).client;
|
messageResponses,
|
||||||
final message = messageResponse.message;
|
index,
|
||||||
final channel = client.channel(
|
defaultWidget,
|
||||||
messageResponse.channel!.type,
|
) {
|
||||||
id: messageResponse.channel!.id,
|
return defaultWidget.copyWith(
|
||||||
);
|
onTap: () async {
|
||||||
if (channel.state == null) {
|
final messageResponse = messageResponses[index];
|
||||||
await channel.watch();
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
}
|
final client = StreamChat.of(context).client;
|
||||||
Navigator.pushNamed(
|
final message = messageResponse.message;
|
||||||
context,
|
final channel = client.channel(
|
||||||
Routes.CHANNEL_PAGE,
|
messageResponse.channel!.type,
|
||||||
arguments: ChannelPageArgs(
|
id: messageResponse.channel!.id,
|
||||||
channel: channel,
|
);
|
||||||
initialMessage: message,
|
if (channel.state == null) {
|
||||||
),
|
await channel.watch();
|
||||||
|
}
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
Routes.CHANNEL_PAGE,
|
||||||
|
arguments: ChannelPageArgs(
|
||||||
|
channel: channel,
|
||||||
|
initialMessage: message,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: ChannelListView(
|
: SlidableAutoCloseBehavior(
|
||||||
onChannelTap: (channel, _) {
|
closeWhenOpened: true,
|
||||||
Navigator.pushNamed(
|
child: RefreshIndicator(
|
||||||
context,
|
onRefresh: _channelListController.refresh,
|
||||||
Routes.CHANNEL_PAGE,
|
child: StreamChannelListView(
|
||||||
arguments: ChannelPageArgs(
|
controller: _channelListController,
|
||||||
channel: channel,
|
itemBuilder: (context, channels, index, defaultWidget) {
|
||||||
),
|
final chatTheme = StreamChatTheme.of(context);
|
||||||
);
|
final backgroundColor = chatTheme.colorTheme.inputBg;
|
||||||
},
|
final channel = channels[index];
|
||||||
onStartChatPressed: () {
|
final canDeleteChannel = channel.ownCapabilities
|
||||||
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
.contains(PermissionType.deleteChannel);
|
||||||
},
|
return Slidable(
|
||||||
swipeToAction: true,
|
groupTag: 'channels-actions',
|
||||||
filter: Filter.in_('members', [user!.id]),
|
endActionPane: ActionPane(
|
||||||
presence: true,
|
extentRatio: canDeleteChannel ? 0.40 : 0.20,
|
||||||
limit: 30,
|
motion: const BehindMotion(),
|
||||||
onViewInfoTap: (channel) {
|
children: [
|
||||||
Navigator.pop(context);
|
CustomSlidableAction(
|
||||||
if (channel.memberCount == 2 && channel.isDistinct) {
|
child: Icon(Icons.more_horiz),
|
||||||
Navigator.push(
|
backgroundColor: backgroundColor,
|
||||||
context,
|
onPressed: (_) {
|
||||||
MaterialPageRoute(
|
showChannelInfoModalBottomSheet(
|
||||||
builder: (context) => StreamChannel(
|
context: context,
|
||||||
|
channel: channel,
|
||||||
|
onViewInfoTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
final isOneToOne =
|
||||||
|
channel.memberCount == 2 &&
|
||||||
|
channel.isDistinct;
|
||||||
|
return StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: isOneToOne
|
||||||
|
? ChatInfoScreen(
|
||||||
|
messageTheme: chatTheme
|
||||||
|
.ownMessageTheme,
|
||||||
|
user: channel
|
||||||
|
.state!.members
|
||||||
|
.where((m) =>
|
||||||
|
m.userId !=
|
||||||
|
channel
|
||||||
|
.client
|
||||||
|
.state
|
||||||
|
.currentUser!
|
||||||
|
.id)
|
||||||
|
.first
|
||||||
|
.user,
|
||||||
|
)
|
||||||
|
: GroupInfoScreen(
|
||||||
|
messageTheme: chatTheme
|
||||||
|
.ownMessageTheme,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (canDeleteChannel)
|
||||||
|
CustomSlidableAction(
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
child: StreamSvgIcon.delete(
|
||||||
|
color: chatTheme.colorTheme.accentError,
|
||||||
|
),
|
||||||
|
onPressed: (_) async {
|
||||||
|
final res = await showConfirmationDialog(
|
||||||
|
context,
|
||||||
|
title: 'Delete Conversation',
|
||||||
|
question:
|
||||||
|
'Are you sure you want to delete this conversation?',
|
||||||
|
okText: 'Delete',
|
||||||
|
cancelText: 'Cancel',
|
||||||
|
icon: StreamSvgIcon.delete(
|
||||||
|
color: chatTheme.colorTheme.accentError,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (res == true) {
|
||||||
|
await _channelListController
|
||||||
|
.deleteChannel(channel);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: defaultWidget,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onChannelTap: (channel) {
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
Routes.CHANNEL_PAGE,
|
||||||
|
arguments: ChannelPageArgs(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: ChatInfoScreen(
|
),
|
||||||
messageTheme:
|
);
|
||||||
StreamChatTheme.of(context).ownMessageTheme,
|
},
|
||||||
user: channel.state!.members
|
emptyBuilder: (_) {
|
||||||
.where((m) =>
|
return Center(
|
||||||
m.userId !=
|
child: Padding(
|
||||||
channel.client.state.currentUser!.id)
|
padding: const EdgeInsets.all(8),
|
||||||
.first
|
child: StreamScrollViewEmptyWidget(
|
||||||
.user,
|
emptyIcon: StreamSvgIcon.message(
|
||||||
|
size: 148,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.disabled,
|
||||||
|
),
|
||||||
|
emptyTitle: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
Routes.NEW_CHAT,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'Start a chat',
|
||||||
|
style: StreamChatTheme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyBold
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
} else {
|
),
|
||||||
Navigator.push(
|
),
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => StreamChannel(
|
|
||||||
channel: channel,
|
|
||||||
child: GroupInfoScreen(
|
|
||||||
messageTheme:
|
|
||||||
StreamChatTheme.of(context).ownMessageTheme,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
|||||||
Positioned(
|
Positioned(
|
||||||
top: -3,
|
top: -3,
|
||||||
right: -16,
|
right: -16,
|
||||||
child: UnreadIndicator(),
|
child: StreamUnreadIndicator(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -70,7 +70,7 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
|||||||
}
|
}
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
appBar: ChannelListHeader(
|
appBar: StreamChannelListHeader(
|
||||||
onNewChatButtonTap: () {
|
onNewChatButtonTap: () {
|
||||||
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
||||||
},
|
},
|
||||||
@@ -100,11 +100,7 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
|||||||
body: IndexedStack(
|
body: IndexedStack(
|
||||||
index: _currentIndex,
|
index: _currentIndex,
|
||||||
children: [
|
children: [
|
||||||
ChannelsBloc(
|
ChannelList(),
|
||||||
child: MessageSearchBloc(
|
|
||||||
child: ChannelList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
UserMentionsPage(),
|
UserMentionsPage(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -165,7 +161,7 @@ class LeftDrawer extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
UserAvatar(
|
StreamUserAvatar(
|
||||||
user: user,
|
user: user,
|
||||||
showOnlineStatus: false,
|
showOnlineStatus: false,
|
||||||
constraints: BoxConstraints.tight(Size.fromRadius(20)),
|
constraints: BoxConstraints.tight(Size.fromRadius(20)),
|
||||||
|
|||||||
@@ -1,61 +1,45 @@
|
|||||||
import 'package:example/localizations.dart';
|
import 'package:example/localizations.dart';
|
||||||
|
import 'package:example/routes/routes.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
|
import 'channel_page.dart';
|
||||||
|
|
||||||
class ChannelMediaDisplayScreen extends StatefulWidget {
|
class ChannelMediaDisplayScreen extends StatefulWidget {
|
||||||
/// The sorting used for the channels matching the filters.
|
final StreamMessageThemeData messageTheme;
|
||||||
/// 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;
|
|
||||||
|
|
||||||
final MessageThemeData messageTheme;
|
|
||||||
|
|
||||||
const ChannelMediaDisplayScreen({
|
const ChannelMediaDisplayScreen({
|
||||||
|
Key? key,
|
||||||
required this.messageTheme,
|
required this.messageTheme,
|
||||||
this.sortOptions,
|
}) : super(key: key);
|
||||||
this.paginationParams = const PaginationParams(limit: 20),
|
|
||||||
this.emptyBuilder,
|
|
||||||
this.onShowMessage,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ChannelMediaDisplayScreenState createState() =>
|
State<ChannelMediaDisplayScreen> createState() =>
|
||||||
_ChannelMediaDisplayScreenState();
|
_ChannelMediaDisplayScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
||||||
Map<String?, VideoPlayerController?> controllerCache = {};
|
final Map<String?, VideoPlayerController?> controllerCache = {};
|
||||||
|
|
||||||
@override
|
late final controller = StreamMessageSearchListController(
|
||||||
void initState() {
|
client: StreamChat.of(context).client,
|
||||||
super.initState();
|
filter: Filter.in_(
|
||||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
'cid',
|
||||||
messageSearchBloc.search(
|
[StreamChannel.of(context).channel.cid!],
|
||||||
filter: Filter.in_(
|
),
|
||||||
'cid',
|
messageFilter: Filter.in_(
|
||||||
[StreamChannel.of(context).channel.cid!],
|
'attachments.type',
|
||||||
|
['image', 'video'],
|
||||||
|
),
|
||||||
|
sort: [
|
||||||
|
SortOption(
|
||||||
|
'created_at',
|
||||||
|
direction: SortOption.ASC,
|
||||||
),
|
),
|
||||||
messageFilter: Filter.in_(
|
],
|
||||||
'attachments.type',
|
limit: 20,
|
||||||
['image', 'video'],
|
);
|
||||||
),
|
|
||||||
sort: widget.sortOptions,
|
|
||||||
pagination: widget.paginationParams,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -74,155 +58,169 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
|||||||
leading: StreamBackButton(),
|
leading: StreamBackButton(),
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
),
|
),
|
||||||
body: _buildMediaGrid(),
|
body: ValueListenableBuilder(
|
||||||
);
|
valueListenable: controller,
|
||||||
}
|
builder: (BuildContext context,
|
||||||
|
PagedValue<String, GetMessageResponse> value, Widget? child) {
|
||||||
Widget _buildMediaGrid() {
|
return value.when(
|
||||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
(items, nextPageKey, error) {
|
||||||
|
if (items.isEmpty) {
|
||||||
return StreamBuilder<List<GetMessageResponse>>(
|
return Center(
|
||||||
builder: (context, snapshot) {
|
child: Column(
|
||||||
if (snapshot.data == null) {
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
return Center(
|
children: [
|
||||||
child: const CircularProgressIndicator(),
|
StreamSvgIcon.pictures(
|
||||||
);
|
size: 136.0,
|
||||||
}
|
color: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
|
),
|
||||||
if (snapshot.data!.isEmpty) {
|
SizedBox(height: 16.0),
|
||||||
if (widget.emptyBuilder != null) {
|
Text(
|
||||||
return widget.emptyBuilder!(context);
|
AppLocalizations.of(context).noMedia,
|
||||||
}
|
style: TextStyle(
|
||||||
return Center(
|
fontSize: 14.0,
|
||||||
child: Column(
|
color: StreamChatTheme.of(context)
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
.colorTheme
|
||||||
children: [
|
.textHighEmphasis,
|
||||||
StreamSvgIcon.pictures(
|
|
||||||
size: 136.0,
|
|
||||||
color: StreamChatTheme.of(context).colorTheme.disabled,
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.0),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context).noMedia,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.0,
|
|
||||||
color:
|
|
||||||
StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 8.0),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context).photosOrVideosWillAppearHere,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.0,
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.textHighEmphasis
|
|
||||||
.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final media = <_AssetPackage>[];
|
|
||||||
|
|
||||||
for (var item in snapshot.data!) {
|
|
||||||
item.message.attachments
|
|
||||||
.where((e) =>
|
|
||||||
(e.type == 'image' || e.type == 'video') &&
|
|
||||||
e.ogScrapeUrl == null)
|
|
||||||
.forEach((e) {
|
|
||||||
VideoPlayerController? controller;
|
|
||||||
if (e.type == 'video') {
|
|
||||||
var cachedController = controllerCache[e.assetUrl];
|
|
||||||
|
|
||||||
if (cachedController == null) {
|
|
||||||
controller = VideoPlayerController.network(e.assetUrl!);
|
|
||||||
controller.initialize();
|
|
||||||
controllerCache[e.assetUrl] = controller;
|
|
||||||
} else {
|
|
||||||
controller = cachedController;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
media.add(_AssetPackage(e, item.message, controller));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return LazyLoadScrollView(
|
|
||||||
onEndOfPage: () => messageSearchBloc.search(
|
|
||||||
filter: Filter.in_(
|
|
||||||
'cid',
|
|
||||||
[StreamChannel.of(context).channel.cid!],
|
|
||||||
),
|
|
||||||
messageFilter: Filter.in_(
|
|
||||||
'attachments.type',
|
|
||||||
['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,
|
|
||||||
userName: media[position].message.user!.name,
|
|
||||||
onShowMessage: widget.onShowMessage,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 8.0),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)
|
||||||
|
.photosOrVideosWillAppearHere,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final media = <_AssetPackage>[];
|
||||||
|
|
||||||
|
for (var item in value.asSuccess.items) {
|
||||||
|
item.message.attachments
|
||||||
|
.where((e) =>
|
||||||
|
(e.type == 'image' || e.type == 'video') &&
|
||||||
|
e.ogScrapeUrl == null)
|
||||||
|
.forEach((e) {
|
||||||
|
VideoPlayerController? controller;
|
||||||
|
if (e.type == 'video') {
|
||||||
|
var cachedController = controllerCache[e.assetUrl];
|
||||||
|
|
||||||
|
if (cachedController == null) {
|
||||||
|
controller = VideoPlayerController.network(e.assetUrl!);
|
||||||
|
controller.initialize();
|
||||||
|
controllerCache[e.assetUrl] = controller;
|
||||||
|
} else {
|
||||||
|
controller = cachedController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
media.add(_AssetPackage(e, item.message, controller));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return LazyLoadScrollView(
|
||||||
|
onEndOfPage: () async {
|
||||||
|
if (nextPageKey != null) {
|
||||||
|
controller.loadMore(nextPageKey);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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: StreamFullScreenMedia(
|
||||||
|
mediaAttachmentPackages: media
|
||||||
|
.map(
|
||||||
|
(e) => StreamAttachmentPackage(
|
||||||
|
attachment: e.attachment,
|
||||||
|
message: e.message,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
startIndex: position,
|
||||||
|
userName: media[position].message.user!.name,
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: media[position].attachment.type == 'image'
|
||||||
|
? IgnorePointer(
|
||||||
|
child: StreamImageAttachment(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
messageTheme: widget.messageTheme,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: VideoPlayer(media[position].videoPlayer!),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: media[position].attachment.type == 'image'
|
itemCount: media.length,
|
||||||
? 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,
|
|
||||||
),
|
|
||||||
messageTheme: widget.messageTheme,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: VideoPlayer(media[position].videoPlayer!),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
itemCount: media.length,
|
loading: () => Center(
|
||||||
),
|
child: const CircularProgressIndicator(),
|
||||||
);
|
),
|
||||||
},
|
error: (_) => Offstage(),
|
||||||
stream: messageSearchBloc.messagesStream,
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
for (var c in controllerCache.values) {
|
}
|
||||||
c!.dispose();
|
|
||||||
}
|
@override
|
||||||
|
void initState() {
|
||||||
|
controller.doInitialLoad();
|
||||||
|
super.initState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ class ChannelPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelPageState extends State<ChannelPage> {
|
class _ChannelPageState extends State<ChannelPage> {
|
||||||
Message? _quotedMessage;
|
|
||||||
FocusNode? _focusNode;
|
FocusNode? _focusNode;
|
||||||
|
StreamMessageInputController _messageInputController = StreamMessageInputController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -50,7 +50,7 @@ class _ChannelPageState extends State<ChannelPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _reply(Message message) {
|
void _reply(Message message) {
|
||||||
setState(() => _quotedMessage = message);
|
_messageInputController.quotedMessage = message;
|
||||||
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
|
||||||
_focusNode!.requestFocus();
|
_focusNode!.requestFocus();
|
||||||
});
|
});
|
||||||
@@ -60,7 +60,7 @@ class _ChannelPageState extends State<ChannelPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
appBar: ChannelHeader(
|
appBar: StreamChannelHeader(
|
||||||
showTypingIndicator: false,
|
showTypingIndicator: false,
|
||||||
onImageTap: () async {
|
onImageTap: () async {
|
||||||
var channel = StreamChannel.of(context).channel;
|
var channel = StreamChannel.of(context).channel;
|
||||||
@@ -108,7 +108,7 @@ class _ChannelPageState extends State<ChannelPage> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
MessageListView(
|
StreamMessageListView(
|
||||||
initialScrollIndex: widget.initialScrollIndex,
|
initialScrollIndex: widget.initialScrollIndex,
|
||||||
initialAlignment: widget.initialAlignment,
|
initialAlignment: widget.initialAlignment,
|
||||||
highlightInitialMessage: widget.highlightInitialMessage,
|
highlightInitialMessage: widget.highlightInitialMessage,
|
||||||
@@ -137,14 +137,13 @@ class _ChannelPageState extends State<ChannelPage> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
deletedBottomRowBuilder: (context, message) {
|
deletedBottomRowBuilder: (context, message) {
|
||||||
return const VisibleFootnote();
|
return const StreamVisibleFootnote();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
threadBuilder: (_, parentMessage) {
|
threadBuilder: (_, parentMessage) {
|
||||||
return ThreadPage(parent: parentMessage!);
|
return ThreadPage(parent: parentMessage!);
|
||||||
},
|
},
|
||||||
pinPermissions: ['owner', 'admin', 'member'],
|
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
@@ -156,8 +155,7 @@ class _ChannelPageState extends State<ChannelPage> {
|
|||||||
.colorTheme
|
.colorTheme
|
||||||
.appBg
|
.appBg
|
||||||
.withOpacity(.9),
|
.withOpacity(.9),
|
||||||
child: TypingIndicator(
|
child: StreamTypingIndicator(
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 8,
|
horizontal: 8,
|
||||||
vertical: 4,
|
vertical: 4,
|
||||||
@@ -175,13 +173,9 @@ class _ChannelPageState extends State<ChannelPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MessageInput(
|
StreamMessageInput(
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
quotedMessage: _quotedMessage,
|
messageInputController: _messageInputController,
|
||||||
onQuotedMessageCleared: () {
|
|
||||||
setState(() => _quotedMessage = null);
|
|
||||||
_focusNode!.unfocus();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
import 'package:collection/collection.dart' show IterableExtension;
|
|
||||||
import 'package:example/localizations.dart';
|
import 'package:example/localizations.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
import 'channel_file_display_screen.dart';
|
import 'channel_file_display_screen.dart';
|
||||||
import 'channel_media_display_screen.dart';
|
import 'channel_media_display_screen.dart';
|
||||||
import 'channel_page.dart';
|
|
||||||
import 'pinned_messages_screen.dart';
|
import 'pinned_messages_screen.dart';
|
||||||
import 'routes/routes.dart';
|
|
||||||
|
|
||||||
/// Detail screen for a 1:1 chat correspondence
|
/// Detail screen for a 1:1 chat correspondence
|
||||||
class ChatInfoScreen extends StatefulWidget {
|
class ChatInfoScreen extends StatefulWidget {
|
||||||
/// User in consideration
|
/// User in consideration
|
||||||
final User? user;
|
final User? user;
|
||||||
|
|
||||||
final MessageThemeData messageTheme;
|
final StreamMessageThemeData messageTheme;
|
||||||
|
|
||||||
const ChatInfoScreen({
|
const ChatInfoScreen({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -54,13 +50,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
height: 8.0,
|
height: 8.0,
|
||||||
color: StreamChatTheme.of(context).colorTheme.disabled,
|
color: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
),
|
),
|
||||||
if ([
|
if (channel.ownCapabilities.contains(PermissionType.deleteChannel))
|
||||||
'admin',
|
|
||||||
'owner',
|
|
||||||
].contains(channel.state!.members
|
|
||||||
.firstWhereOrNull(
|
|
||||||
(m) => m.userId == channel.client.state.currentUser!.id)
|
|
||||||
?.role))
|
|
||||||
_buildDeleteListTile(),
|
_buildDeleteListTile(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -78,7 +68,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: UserAvatar(
|
child: StreamUserAvatar(
|
||||||
user: widget.user!,
|
user: widget.user!,
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
width: 72.0,
|
width: 72.0,
|
||||||
@@ -95,7 +85,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
SizedBox(height: 7.0),
|
SizedBox(height: 7.0),
|
||||||
_buildConnectedTitleState(),
|
_buildConnectedTitleState(),
|
||||||
SizedBox(height: 15.0),
|
SizedBox(height: 15.0),
|
||||||
OptionListTile(
|
StreamOptionListTile(
|
||||||
title: '@${widget.user!.id}',
|
title: '@${widget.user!.id}',
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
trailing: Padding(
|
trailing: Padding(
|
||||||
@@ -148,7 +138,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
mutedBool.value = snapshot.data;
|
mutedBool.value = snapshot.data;
|
||||||
|
|
||||||
return OptionListTile(
|
return StreamOptionListTile(
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
title: AppLocalizations.of(context).muteUser,
|
title: AppLocalizations.of(context).muteUser,
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
@@ -201,7 +191,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
// ),
|
// ),
|
||||||
// onTap: () {},
|
// onTap: () {},
|
||||||
// ),
|
// ),
|
||||||
OptionListTile(
|
StreamOptionListTile(
|
||||||
title: AppLocalizations.of(context).pinnedMessages,
|
title: AppLocalizations.of(context).pinnedMessages,
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
@@ -226,43 +216,13 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: MessageSearchBloc(
|
child: PinnedMessagesScreen(),
|
||||||
child: PinnedMessagesScreen(
|
|
||||||
messageTheme: widget.messageTheme,
|
|
||||||
sortOptions: [
|
|
||||||
SortOption(
|
|
||||||
'created_at',
|
|
||||||
direction: SortOption.ASC,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
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.pushNamedAndRemoveUntil(
|
|
||||||
context,
|
|
||||||
Routes.CHANNEL_PAGE,
|
|
||||||
ModalRoute.withName(Routes.CHANNEL_LIST_PAGE),
|
|
||||||
arguments: ChannelPageArgs(
|
|
||||||
channel: c,
|
|
||||||
initialMessage: m,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
OptionListTile(
|
StreamOptionListTile(
|
||||||
title: AppLocalizations.of(context).photosAndVideos,
|
title: AppLocalizations.of(context).photosAndVideos,
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
@@ -287,42 +247,15 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: MessageSearchBloc(
|
child: ChannelMediaDisplayScreen(
|
||||||
child: ChannelMediaDisplayScreen(
|
messageTheme: widget.messageTheme,
|
||||||
messageTheme: widget.messageTheme,
|
|
||||||
sortOptions: [
|
|
||||||
SortOption(
|
|
||||||
'created_at',
|
|
||||||
direction: SortOption.ASC,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
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(
|
StreamOptionListTile(
|
||||||
title: AppLocalizations.of(context).files,
|
title: AppLocalizations.of(context).files,
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
@@ -347,22 +280,15 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: MessageSearchBloc(
|
child: ChannelFileDisplayScreen(
|
||||||
child: ChannelFileDisplayScreen(
|
messageTheme: widget.messageTheme,
|
||||||
sortOptions: [
|
|
||||||
SortOption(
|
|
||||||
'created_at',
|
|
||||||
direction: SortOption.ASC,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
OptionListTile(
|
StreamOptionListTile(
|
||||||
title: AppLocalizations.of(context).sharedGroups,
|
title: AppLocalizations.of(context).sharedGroups,
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
@@ -392,7 +318,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDeleteListTile() {
|
Widget _buildDeleteListTile() {
|
||||||
return OptionListTile(
|
return StreamOptionListTile(
|
||||||
title: 'Delete Conversation',
|
title: 'Delete Conversation',
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body.copyWith(
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body.copyWith(
|
||||||
@@ -633,7 +559,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: ChannelAvatar(
|
child: StreamChannelAvatar(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
constraints:
|
constraints:
|
||||||
BoxConstraints(maxWidth: 40.0, maxHeight: 40.0),
|
BoxConstraints(maxWidth: 40.0, maxHeight: 40.0),
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
arguments: HomePageArgs(client),
|
arguments: HomePageArgs(client),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
leading: UserAvatar(
|
leading: StreamUserAvatar(
|
||||||
user: user,
|
user: user,
|
||||||
constraints: BoxConstraints.tight(
|
constraints: BoxConstraints.tight(
|
||||||
Size.fromRadius(20),
|
Size.fromRadius(20),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:example/localizations.dart';
|
import 'package:example/localizations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
import 'channel_page.dart';
|
import 'channel_page.dart';
|
||||||
import 'routes/routes.dart';
|
import 'routes/routes.dart';
|
||||||
@@ -155,7 +154,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ConnectionStatusBuilder(
|
body: StreamConnectionStatusBuilder(
|
||||||
statusBuilder: (context, status) {
|
statusBuilder: (context, status) {
|
||||||
String statusString = '';
|
String statusString = '';
|
||||||
bool showStatus = true;
|
bool showStatus = true;
|
||||||
@@ -172,7 +171,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
statusString = AppLocalizations.of(context).disconnected;
|
statusString = AppLocalizations.of(context).disconnected;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return InfoTile(
|
return StreamInfoTile(
|
||||||
showMessage: showStatus,
|
showMessage: showStatus,
|
||||||
tileAnchor: Alignment.topCenter,
|
tileAnchor: Alignment.topCenter,
|
||||||
childAnchor: Alignment.topCenter,
|
childAnchor: Alignment.topCenter,
|
||||||
@@ -222,7 +221,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
final user = _selectedUsers[index];
|
final user = _selectedUsers[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
key: ObjectKey(user),
|
key: ObjectKey(user),
|
||||||
leading: UserAvatar(
|
leading: StreamUserAvatar(
|
||||||
user: user,
|
user: user,
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
width: 40,
|
width: 40,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:collection/collection.dart' show IterableExtension;
|
|||||||
import 'package:example/localizations.dart';
|
import 'package:example/localizations.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
import 'channel_file_display_screen.dart';
|
import 'channel_file_display_screen.dart';
|
||||||
@@ -12,10 +11,9 @@ import 'channel_media_display_screen.dart';
|
|||||||
import 'channel_page.dart';
|
import 'channel_page.dart';
|
||||||
import 'chat_info_screen.dart';
|
import 'chat_info_screen.dart';
|
||||||
import 'pinned_messages_screen.dart';
|
import 'pinned_messages_screen.dart';
|
||||||
import 'routes/routes.dart';
|
|
||||||
|
|
||||||
class GroupInfoScreen extends StatefulWidget {
|
class GroupInfoScreen extends StatefulWidget {
|
||||||
final MessageThemeData messageTheme;
|
final StreamMessageThemeData messageTheme;
|
||||||
|
|
||||||
const GroupInfoScreen({
|
const GroupInfoScreen({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -41,16 +39,52 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
|
|
||||||
ValueNotifier<bool?> mutedBool = ValueNotifier(false);
|
ValueNotifier<bool?> mutedBool = ValueNotifier(false);
|
||||||
|
|
||||||
|
late final channel = StreamChannel.of(context).channel;
|
||||||
|
|
||||||
|
late final userListController = StreamUserListController(
|
||||||
|
client: StreamChat.of(context).client,
|
||||||
|
limit: 25,
|
||||||
|
filter: Filter.and(
|
||||||
|
[
|
||||||
|
if (_searchController!.text.isNotEmpty)
|
||||||
|
Filter.autoComplete('name', _userNameQuery),
|
||||||
|
Filter.notIn('id', [
|
||||||
|
StreamChat.of(context).currentUser!.id,
|
||||||
|
...channel.state!.members
|
||||||
|
.map<String?>(((e) => e.userId))
|
||||||
|
.whereType<String>(),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
sort: [
|
||||||
|
SortOption(
|
||||||
|
'name',
|
||||||
|
direction: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
void _userNameListener() {
|
void _userNameListener() {
|
||||||
if (_searchController!.text == _userNameQuery) {
|
if (_searchController!.text == _userNameQuery) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||||
_debounce = Timer(const Duration(milliseconds: 350), () {
|
_debounce = Timer(const Duration(milliseconds: 350), () {
|
||||||
if (mounted && modalSetStateCallback != null) {
|
if (mounted) {
|
||||||
modalSetStateCallback!(() {
|
_userNameQuery = _searchController!.text;
|
||||||
_userNameQuery = _searchController!.text;
|
userListController.filter = Filter.and(
|
||||||
});
|
[
|
||||||
|
if (_searchController!.text.isNotEmpty)
|
||||||
|
Filter.autoComplete('name', _userNameQuery),
|
||||||
|
Filter.notIn('id', [
|
||||||
|
StreamChat.of(context).currentUser!.id,
|
||||||
|
...channel.state!.members
|
||||||
|
.map<String?>(((e) => e.userId))
|
||||||
|
.whereType<String>(),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
userListController.doInitialLoad();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -58,25 +92,28 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
var channel = StreamChannel.of(context);
|
|
||||||
_nameController = TextEditingController.fromValue(
|
_nameController = TextEditingController.fromValue(
|
||||||
TextEditingValue(
|
TextEditingValue(text: (channel.extraData['name'] as String?) ?? ''),
|
||||||
text: (channel.channel.extraData['name'] as String?) ?? ''),
|
|
||||||
);
|
);
|
||||||
_searchController = TextEditingController()..addListener(_userNameListener);
|
_searchController = TextEditingController()..addListener(_userNameListener);
|
||||||
|
|
||||||
_nameController!.addListener(() {
|
_nameController!.addListener(() {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
});
|
||||||
mutedBool = ValueNotifier(StreamChannel.of(context).channel.isMuted);
|
mutedBool = ValueNotifier(channel.isMuted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
userListController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var channel = StreamChannel.of(context);
|
|
||||||
|
|
||||||
return StreamBuilder<List<Member>>(
|
return StreamBuilder<List<Member>>(
|
||||||
stream: channel.channel.state!.membersStream,
|
stream: channel.state!.membersStream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return Container(
|
return Container(
|
||||||
@@ -85,11 +122,6 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var userMember = snapshot.data!.firstWhereOrNull(
|
|
||||||
(e) => e.user!.id == StreamChat.of(context).currentUser!.id,
|
|
||||||
);
|
|
||||||
var isOwner = userMember?.role == 'owner';
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -100,7 +132,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
title: Column(
|
title: Column(
|
||||||
children: [
|
children: [
|
||||||
StreamBuilder<ChannelState>(
|
StreamBuilder<ChannelState>(
|
||||||
stream: channel.channelStateStream,
|
stream: channel.state?.channelStateStream,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (!state.hasData) {
|
if (!state.hasData) {
|
||||||
return Text(
|
return Text(
|
||||||
@@ -137,7 +169,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
height: 3.0,
|
height: 3.0,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${channel.channel.memberCount} ${AppLocalizations.of(context).members}, ${snapshot.data?.where((e) => e.user!.online).length ?? 0} ${AppLocalizations.of(context).online}',
|
'${channel.memberCount} ${AppLocalizations.of(context).members}, ${snapshot.data?.where((e) => e.user!.online).length ?? 0} ${AppLocalizations.of(context).online}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
@@ -150,7 +182,8 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
actions: [
|
actions: [
|
||||||
if (!channel.channel.isDistinct && isOwner)
|
if (channel.ownCapabilities
|
||||||
|
.contains(PermissionType.updateChannelMembers))
|
||||||
StreamNeumorphicButton(
|
StreamNeumorphicButton(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -174,7 +207,9 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
height: 8.0,
|
height: 8.0,
|
||||||
color: StreamChatTheme.of(context).colorTheme.disabled,
|
color: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
),
|
),
|
||||||
if (isOwner) _buildNameTile(),
|
if (channel.ownCapabilities
|
||||||
|
.contains(PermissionType.updateChannel))
|
||||||
|
_buildNameTile(),
|
||||||
_buildOptionListTiles(),
|
_buildOptionListTiles(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -224,7 +259,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
horizontal: 8.0,
|
horizontal: 8.0,
|
||||||
vertical: 12.0,
|
vertical: 12.0,
|
||||||
),
|
),
|
||||||
child: UserAvatar(
|
child: StreamUserAvatar(
|
||||||
user: member.user!,
|
user: member.user!,
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
height: 40.0,
|
height: 40.0,
|
||||||
@@ -339,7 +374,6 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildNameTile() {
|
Widget _buildNameTile() {
|
||||||
var channel = StreamChannel.of(context).channel;
|
|
||||||
var channelName = (channel.extraData['name'] as String?) ?? '';
|
var channelName = (channel.extraData['name'] as String?) ?? '';
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
@@ -365,6 +399,8 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
|
enabled: channel.ownCapabilities
|
||||||
|
.contains(PermissionType.updateChannel),
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
controller: _nameController,
|
controller: _nameController,
|
||||||
cursorColor:
|
cursorColor:
|
||||||
@@ -413,7 +449,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
size: 24.0,
|
size: 24.0,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
StreamChannel.of(context).channel.update({
|
channel.update({
|
||||||
'name': _nameController!.text.trim(),
|
'name': _nameController!.text.trim(),
|
||||||
}).catchError((err) {
|
}).catchError((err) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -433,8 +469,6 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildOptionListTiles() {
|
Widget _buildOptionListTiles() {
|
||||||
var channel = StreamChannel.of(context);
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
// OptionListTile(
|
// OptionListTile(
|
||||||
@@ -449,48 +483,50 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
// ),
|
// ),
|
||||||
// onTap: () {},
|
// onTap: () {},
|
||||||
// ),
|
// ),
|
||||||
StreamBuilder<bool>(
|
if (channel.ownCapabilities.contains(PermissionType.muteChannel))
|
||||||
stream: StreamChannel.of(context).channel.isMutedStream,
|
StreamBuilder<bool>(
|
||||||
builder: (context, snapshot) {
|
stream: channel.isMutedStream,
|
||||||
mutedBool.value = snapshot.data;
|
builder: (context, snapshot) {
|
||||||
|
mutedBool.value = snapshot.data;
|
||||||
|
|
||||||
return OptionListTile(
|
return StreamOptionListTile(
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
separatorColor: StreamChatTheme.of(context).colorTheme.disabled,
|
separatorColor:
|
||||||
title: AppLocalizations.of(context).muteGroup,
|
StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
title: AppLocalizations.of(context).muteGroup,
|
||||||
leading: Padding(
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
leading: Padding(
|
||||||
child: StreamSvgIcon.mute(
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
size: 24.0,
|
child: StreamSvgIcon.mute(
|
||||||
color: StreamChatTheme.of(context)
|
size: 24.0,
|
||||||
.colorTheme
|
color: StreamChatTheme.of(context)
|
||||||
.textHighEmphasis
|
.colorTheme
|
||||||
.withOpacity(0.5),
|
.textHighEmphasis
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
trailing: snapshot.data == null
|
||||||
trailing: snapshot.data == null
|
? CircularProgressIndicator()
|
||||||
? CircularProgressIndicator()
|
: ValueListenableBuilder<bool?>(
|
||||||
: ValueListenableBuilder<bool?>(
|
valueListenable: mutedBool,
|
||||||
valueListenable: mutedBool,
|
builder: (context, value, _) {
|
||||||
builder: (context, value, _) {
|
return CupertinoSwitch(
|
||||||
return CupertinoSwitch(
|
value: value!,
|
||||||
value: value!,
|
onChanged: (val) {
|
||||||
onChanged: (val) {
|
mutedBool.value = val;
|
||||||
mutedBool.value = val;
|
|
||||||
|
|
||||||
if (snapshot.data!) {
|
if (snapshot.data!) {
|
||||||
channel.channel.unmute();
|
channel.unmute();
|
||||||
} else {
|
} else {
|
||||||
channel.channel.mute();
|
channel.mute();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
OptionListTile(
|
StreamOptionListTile(
|
||||||
title: AppLocalizations.of(context).pinnedMessages,
|
title: AppLocalizations.of(context).pinnedMessages,
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
@@ -515,43 +551,13 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: MessageSearchBloc(
|
child: PinnedMessagesScreen(),
|
||||||
child: PinnedMessagesScreen(
|
|
||||||
messageTheme: widget.messageTheme,
|
|
||||||
sortOptions: [
|
|
||||||
SortOption(
|
|
||||||
'created_at',
|
|
||||||
direction: SortOption.ASC,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
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.pushNamedAndRemoveUntil(
|
|
||||||
context,
|
|
||||||
Routes.CHANNEL_PAGE,
|
|
||||||
ModalRoute.withName(Routes.CHANNEL_LIST_PAGE),
|
|
||||||
arguments: ChannelPageArgs(
|
|
||||||
channel: channel,
|
|
||||||
initialMessage: message,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
OptionListTile(
|
StreamOptionListTile(
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
separatorColor: StreamChatTheme.of(context).colorTheme.disabled,
|
separatorColor: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
title: AppLocalizations.of(context).photosAndVideos,
|
title: AppLocalizations.of(context).photosAndVideos,
|
||||||
@@ -577,43 +583,15 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: MessageSearchBloc(
|
child: ChannelMediaDisplayScreen(
|
||||||
child: ChannelMediaDisplayScreen(
|
messageTheme: widget.messageTheme,
|
||||||
messageTheme: widget.messageTheme,
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
await Navigator.pushNamed(
|
|
||||||
context,
|
|
||||||
Routes.CHANNEL_PAGE,
|
|
||||||
arguments: ChannelPageArgs(
|
|
||||||
channel: channel,
|
|
||||||
initialMessage: message,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
OptionListTile(
|
StreamOptionListTile(
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
separatorColor: StreamChatTheme.of(context).colorTheme.disabled,
|
separatorColor: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
title: AppLocalizations.of(context).files,
|
title: AppLocalizations.of(context).files,
|
||||||
@@ -639,24 +617,17 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: MessageSearchBloc(
|
child: ChannelFileDisplayScreen(
|
||||||
child: ChannelFileDisplayScreen(
|
messageTheme: widget.messageTheme,
|
||||||
sortOptions: [
|
|
||||||
SortOption(
|
|
||||||
'created_at',
|
|
||||||
direction: SortOption.ASC,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
paginationParams: PaginationParams(limit: 20),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (!channel.channel.isDistinct)
|
if (!channel.isDistinct &&
|
||||||
OptionListTile(
|
channel.ownCapabilities.contains(PermissionType.leaveChannel))
|
||||||
|
StreamOptionListTile(
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
separatorColor: StreamChatTheme.of(context).colorTheme.disabled,
|
separatorColor: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
title: AppLocalizations.of(context).leaveGroup,
|
title: AppLocalizations.of(context).leaveGroup,
|
||||||
@@ -700,106 +671,81 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _buildAddUserModal(context) {
|
void _buildAddUserModal(context) {
|
||||||
var channel = StreamChannel.of(context).channel;
|
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
barrierColor: StreamChatTheme.of(context).colorTheme.overlay,
|
barrierColor: StreamChatTheme.of(context).colorTheme.overlay,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return StatefulBuilder(builder: (context, modalSetState) {
|
return Padding(
|
||||||
modalSetStateCallback = modalSetState;
|
padding: EdgeInsets.only(top: 16.0, left: 8.0, right: 8.0),
|
||||||
return Padding(
|
child: Material(
|
||||||
padding: EdgeInsets.only(top: 16.0, left: 8.0, right: 8.0),
|
borderRadius: BorderRadius.only(
|
||||||
child: Material(
|
topLeft: Radius.circular(16.0),
|
||||||
borderRadius: BorderRadius.only(
|
topRight: Radius.circular(16.0),
|
||||||
topLeft: Radius.circular(16.0),
|
),
|
||||||
topRight: Radius.circular(16.0),
|
clipBehavior: Clip.antiAlias,
|
||||||
),
|
child: Scaffold(
|
||||||
clipBehavior: Clip.antiAlias,
|
body: Column(
|
||||||
child: Scaffold(
|
children: [
|
||||||
body: UsersBloc(
|
Padding(
|
||||||
child: Column(
|
padding: const EdgeInsets.all(16),
|
||||||
children: [
|
child: _buildTextInputSection(),
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.all(16),
|
Expanded(
|
||||||
child: _buildTextInputSection(modalSetState),
|
child: StreamUserGridView(
|
||||||
),
|
controller: userListController,
|
||||||
Expanded(
|
onUserTap: (user) async {
|
||||||
child: UserListView(
|
_searchController!.clear();
|
||||||
selectedUsers: {},
|
|
||||||
onUserTap: (user, _) async {
|
|
||||||
_searchController!.clear();
|
|
||||||
|
|
||||||
await channel.addMembers([user.id]);
|
await channel.addMembers([user.id]);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
crossAxisCount: 4,
|
emptyBuilder: (_) {
|
||||||
limit: 25,
|
return LayoutBuilder(
|
||||||
filter: Filter.and(
|
builder: (context, viewportConstraints) {
|
||||||
[
|
return SingleChildScrollView(
|
||||||
if (_searchController!.text.isNotEmpty)
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
Filter.autoComplete('name', _userNameQuery),
|
child: ConstrainedBox(
|
||||||
Filter.notIn('id', [
|
constraints: BoxConstraints(
|
||||||
StreamChat.of(context).currentUser!.id,
|
minHeight: viewportConstraints.maxHeight,
|
||||||
...channel.state!.members
|
),
|
||||||
.map<String?>(((e) => e.userId))
|
child: Center(
|
||||||
.whereType<String>(),
|
child: Column(
|
||||||
]),
|
children: [
|
||||||
],
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.all(24),
|
||||||
sort: [
|
child: StreamSvgIcon.search(
|
||||||
SortOption(
|
size: 96,
|
||||||
'name',
|
color: StreamChatTheme.of(context)
|
||||||
direction: 1,
|
.colorTheme
|
||||||
),
|
.textLowEmphasis,
|
||||||
],
|
),
|
||||||
emptyBuilder: (_) {
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (context, viewportConstraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: viewportConstraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
child: StreamSvgIcon.search(
|
|
||||||
size: 96,
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.textLowEmphasis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(AppLocalizations.of(context)
|
|
||||||
.noUserMatchesTheseKeywords),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
Text(AppLocalizations.of(context)
|
||||||
|
.noUserMatchesTheseKeywords),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
});
|
);
|
||||||
},
|
},
|
||||||
);
|
).whenComplete(() {
|
||||||
|
_searchController?.clear();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTextInputSection(modalSetState) {
|
Widget _buildTextInputSection() {
|
||||||
final theme = StreamChatTheme.of(context);
|
final theme = StreamChatTheme.of(context);
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -859,7 +805,6 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showUserInfoModal(User? user, bool isUserAdmin) {
|
void _showUserInfoModal(User? user, bool isUserAdmin) {
|
||||||
var channel = StreamChannel.of(context).channel;
|
|
||||||
final color = StreamChatTheme.of(context).colorTheme.barsBg;
|
final color = StreamChatTheme.of(context).colorTheme.barsBg;
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
@@ -896,7 +841,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: UserAvatar(
|
child: StreamUserAvatar(
|
||||||
user: user,
|
user: user,
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
height: 64.0,
|
height: 64.0,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:example/channel_page.dart';
|
|||||||
import 'package:example/notifications_service.dart';
|
import 'package:example/notifications_service.dart';
|
||||||
import 'package:example/routes/app_routes.dart';
|
import 'package:example/routes/app_routes.dart';
|
||||||
import 'package:example/routes/routes.dart';
|
import 'package:example/routes/routes.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,20 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
|
|
||||||
late TextEditingController _controller;
|
late TextEditingController _controller;
|
||||||
|
|
||||||
|
late final userListController = StreamUserListController(
|
||||||
|
client: StreamChat.of(context).client,
|
||||||
|
limit: 25,
|
||||||
|
filter: Filter.and([
|
||||||
|
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
|
||||||
|
]),
|
||||||
|
sort: [
|
||||||
|
SortOption(
|
||||||
|
'name',
|
||||||
|
direction: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
ChipInputTextFieldState? get _chipInputTextFieldState =>
|
ChipInputTextFieldState? get _chipInputTextFieldState =>
|
||||||
_chipInputTextFieldStateKey.currentState;
|
_chipInputTextFieldStateKey.currentState;
|
||||||
|
|
||||||
@@ -45,6 +59,12 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
_userNameQuery = _controller.text;
|
_userNameQuery = _controller.text;
|
||||||
_isSearchActive = _userNameQuery.isNotEmpty;
|
_isSearchActive = _userNameQuery.isNotEmpty;
|
||||||
});
|
});
|
||||||
|
userListController.filter = Filter.and([
|
||||||
|
if (_userNameQuery.isNotEmpty)
|
||||||
|
Filter.autoComplete('name', _userNameQuery),
|
||||||
|
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
|
||||||
|
]);
|
||||||
|
userListController.doInitialLoad();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +132,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
_controller.clear();
|
_controller.clear();
|
||||||
_controller.removeListener(_userNameListener);
|
_controller.removeListener(_userNameListener);
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
|
userListController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +151,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: ConnectionStatusBuilder(
|
body: StreamConnectionStatusBuilder(
|
||||||
statusBuilder: (context, status) {
|
statusBuilder: (context, status) {
|
||||||
String statusString = '';
|
String statusString = '';
|
||||||
bool showStatus = true;
|
bool showStatus = true;
|
||||||
@@ -147,7 +168,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
statusString = AppLocalizations.of(context).disconnected;
|
statusString = AppLocalizations.of(context).disconnected;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return InfoTile(
|
return StreamInfoTile(
|
||||||
showMessage: showStatus,
|
showMessage: showStatus,
|
||||||
tileAnchor: Alignment.topCenter,
|
tileAnchor: Alignment.topCenter,
|
||||||
childAnchor: Alignment.topCenter,
|
childAnchor: Alignment.topCenter,
|
||||||
@@ -200,7 +221,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
.overlay,
|
.overlay,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: UserAvatar(
|
child: StreamUserAvatar(
|
||||||
showOnlineStatus: false,
|
showOnlineStatus: false,
|
||||||
user: user,
|
user: user,
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
@@ -287,86 +308,81 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
? GestureDetector(
|
? GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onPanDown: (_) => FocusScope.of(context).unfocus(),
|
onPanDown: (_) => FocusScope.of(context).unfocus(),
|
||||||
child: UsersBloc(
|
child: StreamUserListView(
|
||||||
child: UserListView(
|
controller: userListController,
|
||||||
selectedUsers: _selectedUsers,
|
// groupAlphabetically:
|
||||||
groupAlphabetically:
|
// _isSearchActive ? false : true,
|
||||||
_isSearchActive ? false : true,
|
onUserTap: (user) {
|
||||||
onUserTap: (user, _) {
|
_controller.clear();
|
||||||
_controller.clear();
|
if (!_selectedUsers.contains(user)) {
|
||||||
if (!_selectedUsers.contains(user)) {
|
_chipInputTextFieldState
|
||||||
_chipInputTextFieldState
|
?..addItem(user)
|
||||||
?..addItem(user)
|
..pauseItemAddition();
|
||||||
..pauseItemAddition();
|
} else {
|
||||||
} else {
|
_chipInputTextFieldState!.removeItem(user);
|
||||||
_chipInputTextFieldState!.removeItem(user);
|
}
|
||||||
}
|
},
|
||||||
},
|
itemBuilder: (
|
||||||
limit: 25,
|
context,
|
||||||
filter: Filter.and([
|
users,
|
||||||
if (_userNameQuery.isNotEmpty)
|
index,
|
||||||
Filter.autoComplete('name', _userNameQuery),
|
defaultWidget,
|
||||||
Filter.notEqual('id',
|
) {
|
||||||
StreamChat.of(context).currentUser!.id),
|
return defaultWidget.copyWith(
|
||||||
]),
|
selected:
|
||||||
sort: [
|
_selectedUsers.contains(users[index]),
|
||||||
SortOption(
|
);
|
||||||
'name',
|
},
|
||||||
direction: 1,
|
emptyBuilder: (_) {
|
||||||
),
|
return LayoutBuilder(
|
||||||
],
|
builder: (context, viewportConstraints) {
|
||||||
emptyBuilder: (_) {
|
return SingleChildScrollView(
|
||||||
return LayoutBuilder(
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
builder: (context, viewportConstraints) {
|
child: ConstrainedBox(
|
||||||
return SingleChildScrollView(
|
constraints: BoxConstraints(
|
||||||
physics:
|
minHeight:
|
||||||
AlwaysScrollableScrollPhysics(),
|
viewportConstraints.maxHeight,
|
||||||
child: ConstrainedBox(
|
),
|
||||||
constraints: BoxConstraints(
|
child: Center(
|
||||||
minHeight:
|
child: Column(
|
||||||
viewportConstraints.maxHeight,
|
children: [
|
||||||
),
|
Padding(
|
||||||
child: Center(
|
padding:
|
||||||
child: Column(
|
const EdgeInsets.all(24),
|
||||||
children: [
|
child: StreamSvgIcon.search(
|
||||||
Padding(
|
size: 96,
|
||||||
padding:
|
color: Colors.grey,
|
||||||
const EdgeInsets.all(24),
|
|
||||||
child: StreamSvgIcon.search(
|
|
||||||
size: 96,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Text(
|
),
|
||||||
AppLocalizations.of(context)
|
Text(
|
||||||
.noUserMatchesTheseKeywords,
|
AppLocalizations.of(context)
|
||||||
style: StreamChatTheme.of(
|
.noUserMatchesTheseKeywords,
|
||||||
context)
|
style: StreamChatTheme.of(
|
||||||
.textTheme
|
context)
|
||||||
.footnote
|
.textTheme
|
||||||
.copyWith(
|
.footnote
|
||||||
color: StreamChatTheme
|
.copyWith(
|
||||||
.of(context)
|
color: StreamChatTheme
|
||||||
.colorTheme
|
.of(context)
|
||||||
.textHighEmphasis
|
.colorTheme
|
||||||
.withOpacity(.5)),
|
.textHighEmphasis
|
||||||
),
|
.withOpacity(.5)),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: FutureBuilder<bool>(
|
: FutureBuilder<bool>(
|
||||||
future: channel!.initialized,
|
future: channel!.initialized,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.data == true) {
|
if (snapshot.data == true) {
|
||||||
return MessageListView();
|
return StreamMessageListView();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
@@ -384,7 +400,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MessageInput(
|
StreamMessageInput(
|
||||||
focusNode: _messageInputFocusNode,
|
focusNode: _messageInputFocusNode,
|
||||||
preMessageSending: (message) async {
|
preMessageSending: (message) async {
|
||||||
await channel!.watch();
|
await channel!.watch();
|
||||||
|
|||||||
@@ -23,6 +23,20 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
|
|
||||||
Timer? _debounce;
|
Timer? _debounce;
|
||||||
|
|
||||||
|
late final userListController = StreamUserListController(
|
||||||
|
client: StreamChat.of(context).client,
|
||||||
|
sort: [
|
||||||
|
SortOption(
|
||||||
|
'name',
|
||||||
|
direction: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
limit: 25,
|
||||||
|
filter: Filter.and([
|
||||||
|
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
void _userNameListener() {
|
void _userNameListener() {
|
||||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||||
_debounce = Timer(const Duration(milliseconds: 350), () {
|
_debounce = Timer(const Duration(milliseconds: 350), () {
|
||||||
@@ -31,6 +45,12 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
_userNameQuery = _controller!.text;
|
_userNameQuery = _controller!.text;
|
||||||
_isSearchActive = _userNameQuery.isNotEmpty;
|
_isSearchActive = _userNameQuery.isNotEmpty;
|
||||||
});
|
});
|
||||||
|
userListController.filter = Filter.and([
|
||||||
|
if (_userNameQuery.isNotEmpty)
|
||||||
|
Filter.autoComplete('name', _userNameQuery),
|
||||||
|
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
|
||||||
|
]);
|
||||||
|
userListController.doInitialLoad();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -46,6 +66,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
_controller?.clear();
|
_controller?.clear();
|
||||||
_controller?.removeListener(_userNameListener);
|
_controller?.removeListener(_userNameListener);
|
||||||
_controller?.dispose();
|
_controller?.dispose();
|
||||||
|
userListController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +109,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ConnectionStatusBuilder(
|
body: StreamConnectionStatusBuilder(
|
||||||
statusBuilder: (context, status) {
|
statusBuilder: (context, status) {
|
||||||
String statusString = '';
|
String statusString = '';
|
||||||
bool showStatus = true;
|
bool showStatus = true;
|
||||||
@@ -105,7 +126,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
statusString = AppLocalizations.of(context).disconnected;
|
statusString = AppLocalizations.of(context).disconnected;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return InfoTile(
|
return StreamInfoTile(
|
||||||
showMessage: showStatus,
|
showMessage: showStatus,
|
||||||
tileAnchor: Alignment.topCenter,
|
tileAnchor: Alignment.topCenter,
|
||||||
childAnchor: Alignment.topCenter,
|
childAnchor: Alignment.topCenter,
|
||||||
@@ -136,7 +157,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
UserAvatar(
|
StreamUserAvatar(
|
||||||
onlineIndicatorAlignment:
|
onlineIndicatorAlignment:
|
||||||
Alignment(0.9, 0.9),
|
Alignment(0.9, 0.9),
|
||||||
user: user,
|
user: user,
|
||||||
@@ -228,77 +249,66 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
body: GestureDetector(
|
body: GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onPanDown: (_) => FocusScope.of(context).unfocus(),
|
onPanDown: (_) => FocusScope.of(context).unfocus(),
|
||||||
child: UsersBloc(
|
child: StreamUserListView(
|
||||||
child: UserListView(
|
controller: userListController,
|
||||||
selectedUsers: _selectedUsers,
|
// groupAlphabetically: _isSearchActive ? false : true,
|
||||||
pullToRefresh: false,
|
itemBuilder: (context, items, index, defaultWidget) {
|
||||||
groupAlphabetically: _isSearchActive ? false : true,
|
return defaultWidget.copyWith(
|
||||||
onUserTap: (user, _) {
|
selected: _selectedUsers.contains(items[index]),
|
||||||
if (!_selectedUsers.contains(user)) {
|
);
|
||||||
setState(() {
|
},
|
||||||
_selectedUsers.add(user);
|
onUserTap: (user) {
|
||||||
});
|
if (!_selectedUsers.contains(user)) {
|
||||||
} else {
|
setState(() {
|
||||||
setState(() {
|
_selectedUsers.add(user);
|
||||||
_selectedUsers.remove(user);
|
});
|
||||||
});
|
} else {
|
||||||
}
|
setState(() {
|
||||||
},
|
_selectedUsers.remove(user);
|
||||||
limit: 25,
|
});
|
||||||
filter: Filter.and([
|
}
|
||||||
if (_userNameQuery.isNotEmpty)
|
},
|
||||||
Filter.autoComplete('name', _userNameQuery),
|
emptyBuilder: (_) {
|
||||||
Filter.notEqual(
|
return LayoutBuilder(
|
||||||
'id', StreamChat.of(context).currentUser!.id),
|
builder: (context, viewportConstraints) {
|
||||||
]),
|
return SingleChildScrollView(
|
||||||
sort: [
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
SortOption(
|
child: ConstrainedBox(
|
||||||
'name',
|
constraints: BoxConstraints(
|
||||||
direction: 1,
|
minHeight: viewportConstraints.maxHeight,
|
||||||
),
|
),
|
||||||
],
|
child: Center(
|
||||||
emptyBuilder: (_) {
|
child: Column(
|
||||||
return LayoutBuilder(
|
children: [
|
||||||
builder: (context, viewportConstraints) {
|
Padding(
|
||||||
return SingleChildScrollView(
|
padding: const EdgeInsets.all(24),
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
child: StreamSvgIcon.search(
|
||||||
child: ConstrainedBox(
|
size: 96,
|
||||||
constraints: BoxConstraints(
|
color: StreamChatTheme.of(context)
|
||||||
minHeight: viewportConstraints.maxHeight,
|
.colorTheme
|
||||||
),
|
.textLowEmphasis,
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
child: StreamSvgIcon.search(
|
|
||||||
size: 96,
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.textLowEmphasis,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Text(
|
),
|
||||||
AppLocalizations.of(context)
|
Text(
|
||||||
.noUserMatchesTheseKeywords,
|
AppLocalizations.of(context)
|
||||||
style: StreamChatTheme.of(context)
|
.noUserMatchesTheseKeywords,
|
||||||
.textTheme
|
style: StreamChatTheme.of(context)
|
||||||
.footnote
|
.textTheme
|
||||||
.copyWith(
|
.footnote
|
||||||
color: StreamChatTheme.of(context)
|
.copyWith(
|
||||||
.colorTheme
|
color: StreamChatTheme.of(context)
|
||||||
.textLowEmphasis,
|
.colorTheme
|
||||||
),
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,60 +1,34 @@
|
|||||||
import 'package:example/localizations.dart';
|
import 'package:example/localizations.dart';
|
||||||
|
import 'package:example/routes/routes.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
|
||||||
|
import 'channel_page.dart';
|
||||||
|
|
||||||
class PinnedMessagesScreen extends StatefulWidget {
|
class PinnedMessagesScreen 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;
|
|
||||||
|
|
||||||
final MessageThemeData messageTheme;
|
|
||||||
|
|
||||||
const PinnedMessagesScreen({
|
|
||||||
required this.messageTheme,
|
|
||||||
this.sortOptions,
|
|
||||||
this.paginationParams = const PaginationParams(limit: 20),
|
|
||||||
this.emptyBuilder,
|
|
||||||
this.onShowMessage,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_PinnedMessagesScreenState createState() => _PinnedMessagesScreenState();
|
State<PinnedMessagesScreen> createState() => _PinnedMessagesScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
|
class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
|
||||||
Map<String?, VideoPlayerController?> controllerCache = {};
|
late final controller = StreamMessageSearchListController(
|
||||||
|
client: StreamChat.of(context).client,
|
||||||
@override
|
filter: Filter.in_(
|
||||||
void initState() {
|
'cid',
|
||||||
super.initState();
|
[StreamChannel.of(context).channel.cid!],
|
||||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
),
|
||||||
messageSearchBloc.search(
|
messageFilter: Filter.equal(
|
||||||
filter: Filter.in_(
|
'pinned',
|
||||||
'cid',
|
true,
|
||||||
[StreamChannel.of(context).channel.cid!],
|
),
|
||||||
|
sort: [
|
||||||
|
SortOption(
|
||||||
|
'created_at',
|
||||||
|
direction: SortOption.ASC,
|
||||||
),
|
),
|
||||||
messageFilter: Filter.equal(
|
],
|
||||||
'pinned',
|
limit: 20,
|
||||||
true,
|
);
|
||||||
),
|
|
||||||
sort: widget.sortOptions,
|
|
||||||
pagination: widget.paginationParams,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -73,25 +47,9 @@ class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
|
|||||||
leading: StreamBackButton(),
|
leading: StreamBackButton(),
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
),
|
),
|
||||||
body: _buildMediaGrid(),
|
body: StreamMessageSearchListView(
|
||||||
);
|
controller: controller,
|
||||||
}
|
emptyBuilder: (_) {
|
||||||
|
|
||||||
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(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -140,74 +98,33 @@ class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
onMessageTap: (messageResponse) async {
|
||||||
var data = snapshot.data ?? [];
|
final client = StreamChat.of(context).client;
|
||||||
|
final message = messageResponse.message;
|
||||||
return LazyLoadScrollView(
|
final channel = client.channel(
|
||||||
onEndOfPage: () => messageSearchBloc.search(
|
messageResponse.channel!.type,
|
||||||
filter: Filter.in_(
|
id: messageResponse.channel!.id,
|
||||||
'cid',
|
);
|
||||||
[StreamChannel.of(context).channel.cid!],
|
if (channel.state == null) {
|
||||||
|
await channel.watch();
|
||||||
|
}
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
Routes.CHANNEL_PAGE,
|
||||||
|
arguments: ChannelPageArgs(
|
||||||
|
channel: channel,
|
||||||
|
initialMessage: message,
|
||||||
),
|
),
|
||||||
messageFilter: Filter.equal(
|
);
|
||||||
'pinned',
|
},
|
||||||
true,
|
),
|
||||||
),
|
|
||||||
sort: widget.sortOptions,
|
|
||||||
pagination: widget.paginationParams.copyWith(
|
|
||||||
offset: messageSearchBloc.messageResponses?.length ?? 0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ListView.builder(
|
|
||||||
itemBuilder: (context, position) {
|
|
||||||
var user = data[position].message.user!;
|
|
||||||
var attachments = data[position].message.attachments;
|
|
||||||
var text = data[position].message.text ?? '';
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
leading: UserAvatar(
|
|
||||||
user: user,
|
|
||||||
constraints: BoxConstraints.tightFor(
|
|
||||||
width: 40.0,
|
|
||||||
height: 40.0,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(28),
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
user.name,
|
|
||||||
style: TextStyle(
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.textHighEmphasis,
|
|
||||||
fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
text != ''
|
|
||||||
? text
|
|
||||||
: (attachments.isNotEmpty
|
|
||||||
? '${attachments.length} ${attachments.length > 1 ? AppLocalizations.of(context).attachments : AppLocalizations.of(context).attachment}'
|
|
||||||
: ''),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
widget.onShowMessage?.call(data[position].message,
|
|
||||||
StreamChannel.of(context).channel);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
itemCount: snapshot.data!.length,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
stream: messageSearchBloc.messagesStream,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
for (var c in controllerCache.values) {
|
|
||||||
c!.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:collection/src/iterable_extensions.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:example/channel_list_page.dart';
|
import 'package:example/channel_list_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
|
||||||
import 'package:lottie/lottie.dart';
|
import 'package:lottie/lottie.dart';
|
||||||
|
|
||||||
mixin SplashScreenStateMixin<T extends StatefulWidget> on State<T>
|
mixin SplashScreenStateMixin<T extends StatefulWidget> on State<T>
|
||||||
|
|||||||
@@ -18,8 +18,15 @@ class ThreadPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ThreadPageState extends State<ThreadPage> {
|
class _ThreadPageState extends State<ThreadPage> {
|
||||||
Message? _quotedMessage;
|
|
||||||
FocusNode _focusNode = FocusNode();
|
FocusNode _focusNode = FocusNode();
|
||||||
|
late StreamMessageInputController _messageInputController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_messageInputController =
|
||||||
|
StreamMessageInputController(message: widget.parent);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -28,7 +35,7 @@ class _ThreadPageState extends State<ThreadPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _reply(Message message) {
|
void _reply(Message message) {
|
||||||
setState(() => _quotedMessage = message);
|
_messageInputController.quotedMessage = message;
|
||||||
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
|
||||||
_focusNode.requestFocus();
|
_focusNode.requestFocus();
|
||||||
});
|
});
|
||||||
@@ -38,13 +45,13 @@ class _ThreadPageState extends State<ThreadPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
appBar: ThreadHeader(
|
appBar: StreamThreadHeader(
|
||||||
parent: widget.parent,
|
parent: widget.parent,
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: MessageListView(
|
child: StreamMessageListView(
|
||||||
parentMessage: widget.parent,
|
parentMessage: widget.parent,
|
||||||
initialScrollIndex: widget.initialScrollIndex,
|
initialScrollIndex: widget.initialScrollIndex,
|
||||||
initialAlignment: widget.initialAlignment,
|
initialAlignment: widget.initialAlignment,
|
||||||
@@ -56,22 +63,16 @@ class _ThreadPageState extends State<ThreadPage> {
|
|||||||
return defaultMessage.copyWith(
|
return defaultMessage.copyWith(
|
||||||
onReplyTap: _reply,
|
onReplyTap: _reply,
|
||||||
deletedBottomRowBuilder: (context, message) {
|
deletedBottomRowBuilder: (context, message) {
|
||||||
return const VisibleFootnote();
|
return const StreamVisibleFootnote();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
pinPermissions: ['owner', 'admin', 'member'],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.parent.type != 'deleted')
|
if (widget.parent.type != 'deleted')
|
||||||
MessageInput(
|
StreamMessageInput(
|
||||||
parentMessage: widget.parent,
|
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
quotedMessage: _quotedMessage,
|
messageInputController: _messageInputController,
|
||||||
onQuotedMessageCleared: () {
|
|
||||||
setState(() => _quotedMessage = null);
|
|
||||||
_focusNode.unfocus();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,85 +5,94 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|||||||
|
|
||||||
import 'channel_page.dart';
|
import 'channel_page.dart';
|
||||||
|
|
||||||
class UserMentionsPage extends StatelessWidget {
|
class UserMentionsPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<UserMentionsPage> createState() => _UserMentionsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UserMentionsPageState extends State<UserMentionsPage> {
|
||||||
|
late final controller = StreamMessageSearchListController(
|
||||||
|
client: StreamChat.of(context).client,
|
||||||
|
filter: Filter.in_('members', [StreamChat.of(context).currentUser!.id]),
|
||||||
|
messageFilter: Filter.custom(
|
||||||
|
operator: r'$contains',
|
||||||
|
key: 'mentioned_users.id',
|
||||||
|
value: StreamChat.of(context).currentUser!.id,
|
||||||
|
),
|
||||||
|
sort: [
|
||||||
|
SortOption(
|
||||||
|
'created_at',
|
||||||
|
direction: SortOption.ASC,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
limit: 20,
|
||||||
|
);
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final user = StreamChat.of(context).currentUser!;
|
return StreamMessageSearchListView(
|
||||||
return MessageSearchBloc(
|
controller: controller,
|
||||||
child: MessageSearchListView(
|
emptyBuilder: (_) {
|
||||||
filters: Filter.in_('members', [user.id]),
|
return LayoutBuilder(
|
||||||
messageFilters: Filter.custom(
|
builder: (context, viewportConstraints) {
|
||||||
operator: r'$contains',
|
return SingleChildScrollView(
|
||||||
key: 'mentioned_users.id',
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
value: user.id,
|
child: ConstrainedBox(
|
||||||
),
|
constraints: BoxConstraints(
|
||||||
sortOptions: [
|
minHeight: viewportConstraints.maxHeight,
|
||||||
SortOption(
|
),
|
||||||
'created_at',
|
child: Center(
|
||||||
direction: SortOption.ASC,
|
child: Column(
|
||||||
),
|
children: [
|
||||||
],
|
Padding(
|
||||||
limit: 20,
|
padding: const EdgeInsets.all(24),
|
||||||
showResultCount: false,
|
child: StreamSvgIcon.mentions(
|
||||||
emptyBuilder: (_) {
|
size: 96,
|
||||||
return LayoutBuilder(
|
color:
|
||||||
builder: (context, viewportConstraints) {
|
StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
return SingleChildScrollView(
|
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: viewportConstraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
child: StreamSvgIcon.mentions(
|
|
||||||
size: 96,
|
|
||||||
color:
|
|
||||||
StreamChatTheme.of(context).colorTheme.disabled,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Text(
|
),
|
||||||
AppLocalizations.of(context).noMentionsExistYet,
|
Text(
|
||||||
style: StreamChatTheme.of(context)
|
AppLocalizations.of(context).noMentionsExistYet,
|
||||||
.textTheme
|
style:
|
||||||
.body
|
StreamChatTheme.of(context).textTheme.body.copyWith(
|
||||||
.copyWith(
|
color: StreamChatTheme.of(context)
|
||||||
color: StreamChatTheme.of(context)
|
.colorTheme
|
||||||
.colorTheme
|
.textLowEmphasis,
|
||||||
.textLowEmphasis,
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
onItemTap: (messageResponse) async {
|
},
|
||||||
final client = StreamChat.of(context).client;
|
onMessageTap: (messageResponse) async {
|
||||||
final message = messageResponse.message;
|
final client = StreamChat.of(context).client;
|
||||||
final channel = client.channel(
|
final message = messageResponse.message;
|
||||||
messageResponse.channel!.type,
|
final channel = client.channel(
|
||||||
id: messageResponse.channel!.id,
|
messageResponse.channel!.type,
|
||||||
);
|
id: messageResponse.channel!.id,
|
||||||
if (channel.state == null) {
|
);
|
||||||
await channel.watch();
|
if (channel.state == null) {
|
||||||
}
|
await channel.watch();
|
||||||
Navigator.pushNamed(
|
}
|
||||||
context,
|
Navigator.pushNamed(
|
||||||
Routes.CHANNEL_PAGE,
|
context,
|
||||||
arguments: ChannelPageArgs(
|
Routes.CHANNEL_PAGE,
|
||||||
channel: channel,
|
arguments: ChannelPageArgs(
|
||||||
initialMessage: message,
|
channel: channel,
|
||||||
),
|
initialMessage: message,
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,17 +13,17 @@ dependencies:
|
|||||||
stream_chat_flutter:
|
stream_chat_flutter:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/GetStream/stream-chat-flutter.git
|
url: https://github.com/GetStream/stream-chat-flutter.git
|
||||||
ref: develop
|
ref: v4
|
||||||
path: packages/stream_chat_flutter
|
path: packages/stream_chat_flutter
|
||||||
stream_chat_persistence:
|
stream_chat_persistence:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/GetStream/stream-chat-flutter.git
|
url: https://github.com/GetStream/stream-chat-flutter.git
|
||||||
ref: develop
|
ref: v4
|
||||||
path: packages/stream_chat_persistence
|
path: packages/stream_chat_persistence
|
||||||
stream_chat_localizations:
|
stream_chat_localizations:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/GetStream/stream-chat-flutter.git
|
url: https://github.com/GetStream/stream-chat-flutter.git
|
||||||
ref: develop
|
ref: v4
|
||||||
path: packages/stream_chat_localizations
|
path: packages/stream_chat_localizations
|
||||||
flutter_local_notifications: ^9.0.0
|
flutter_local_notifications: ^9.0.0
|
||||||
flutter_svg: ^1.0.3
|
flutter_svg: ^1.0.3
|
||||||
@@ -43,17 +43,17 @@ dependency_overrides:
|
|||||||
stream_chat:
|
stream_chat:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/GetStream/stream-chat-flutter.git
|
url: https://github.com/GetStream/stream-chat-flutter.git
|
||||||
ref: develop
|
ref: v4
|
||||||
path: packages/stream_chat
|
path: packages/stream_chat
|
||||||
stream_chat_flutter_core:
|
stream_chat_flutter_core:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/GetStream/stream-chat-flutter.git
|
url: https://github.com/GetStream/stream-chat-flutter.git
|
||||||
ref: develop
|
ref: v4
|
||||||
path: packages/stream_chat_flutter_core
|
path: packages/stream_chat_flutter_core
|
||||||
stream_chat_flutter:
|
stream_chat_flutter:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/GetStream/stream-chat-flutter.git
|
url: https://github.com/GetStream/stream-chat-flutter.git
|
||||||
ref: develop
|
ref: v4
|
||||||
path: packages/stream_chat_flutter
|
path: packages/stream_chat_flutter
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|||||||
Reference in New Issue
Block a user