@@ -69,3 +69,24 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: android-stream-chat-v1
|
name: android-stream-chat-v1
|
||||||
path: packages/stream_chat_v1/build/app/outputs/apk/release/app-release.apk
|
path: packages/stream_chat_v1/build/app/outputs/apk/release/app-release.apk
|
||||||
|
build_and_deploy_web:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: config git
|
||||||
|
run: |
|
||||||
|
git config --global user.email "$(git log --format='%ae' HEAD^!)"
|
||||||
|
git config --global user.name "$(git log --format='%an' HEAD^!)"
|
||||||
|
git fetch origin gh-pages:gh-pages
|
||||||
|
- uses: subosito/[email protected]
|
||||||
|
with:
|
||||||
|
channel: 'stable'
|
||||||
|
- run: flutter pub get
|
||||||
|
- name: Copy production config
|
||||||
|
run: echo "${{ secrets.PRODUCTION_CONFIG }}" > lib/app_config.dart
|
||||||
|
- uses: erickzanardo/flutter-gh-pages@v3
|
||||||
|
with:
|
||||||
|
webRenderer: canvaskit
|
||||||
|
workingDir: packages/stream_chat_v1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,6 +2,6 @@
|
|||||||
<Workspace
|
<Workspace
|
||||||
version = "1.0">
|
version = "1.0">
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Runner.xcodeproj">
|
location = "self:">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:stream_chatter/domain/models/auth_user.dart';
|
import 'package:stream_chatter/domain/models/auth_user.dart';
|
||||||
|
|
||||||
abstract class AuthRepository {
|
abstract class AuthRepository {
|
||||||
Future<AuthUser> getAuthUser();
|
Future<AuthUser?> getAuthUser();
|
||||||
Future<AuthUser> signIn();
|
Future<AuthUser> signIn();
|
||||||
Future<void> logout();
|
Future<void> logout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
abstract class ImagePickerRepository {
|
abstract class ImagePickerRepository {
|
||||||
Future<File> pickImage();
|
Future<File?> pickImage();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,17 @@ import 'package:stream_chatter/data/image_picker_repository.dart';
|
|||||||
|
|
||||||
class ImagePickerImpl extends ImagePickerRepository {
|
class ImagePickerImpl extends ImagePickerRepository {
|
||||||
@override
|
@override
|
||||||
Future<File> pickImage() async {
|
Future<File?> pickImage() async {
|
||||||
final picker = ImagePicker();
|
final picker = ImagePicker();
|
||||||
final pickedFile = await picker.getImage(source: ImageSource.gallery, maxWidth: 400);
|
final pickedFile = await picker.getImage(
|
||||||
|
source: ImageSource.gallery,
|
||||||
|
maxWidth: 400,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedFile == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return File(pickedFile.path);
|
return File(pickedFile.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class StreamApiLocalImpl extends StreamApiRepository {
|
|||||||
final StreamChatClient _client;
|
final StreamChatClient _client;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ChatUser> connectUser(ChatUser user, String token) async {
|
Future<ChatUser> connectUser(ChatUser user, String? token) async {
|
||||||
Map<String, dynamic> extraData = {};
|
Map<String, dynamic> extraData = {};
|
||||||
if (user.image != null) {
|
if (user.image != null) {
|
||||||
extraData['image'] = user.image;
|
extraData['image'] = user.image;
|
||||||
@@ -18,7 +18,7 @@ class StreamApiLocalImpl extends StreamApiRepository {
|
|||||||
}
|
}
|
||||||
await _client.disconnect();
|
await _client.disconnect();
|
||||||
await _client.connectUser(
|
await _client.connectUser(
|
||||||
User(id: user.id, extraData: extraData),
|
User(id: user.id!, extraData: extraData as Map<String, Object>),
|
||||||
token,
|
token,
|
||||||
);
|
);
|
||||||
return user;
|
return user;
|
||||||
@@ -28,12 +28,12 @@ class StreamApiLocalImpl extends StreamApiRepository {
|
|||||||
Future<List<ChatUser>> getChatUsers() async {
|
Future<List<ChatUser>> getChatUsers() async {
|
||||||
final result = await _client.queryUsers();
|
final result = await _client.queryUsers();
|
||||||
final chatUsers = result.users
|
final chatUsers = result.users
|
||||||
.where((element) => element.id != _client.state.user.id)
|
.where((element) => element.id != _client.state.user!.id)
|
||||||
.map(
|
.map(
|
||||||
(e) => ChatUser(
|
(e) => ChatUser(
|
||||||
id: e.id,
|
id: e.id,
|
||||||
name: e.name,
|
name: e.name,
|
||||||
image: e.extraData['image'],
|
image: e.extraData['image'] as String?,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -46,25 +46,28 @@ class StreamApiLocalImpl extends StreamApiRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Channel> createGroupChat(String channelId, String name, List<String> members, {String image}) async {
|
Future<Channel> createGroupChat(
|
||||||
|
String channelId, String? name, List<String?>? members,
|
||||||
|
{String? image}) async {
|
||||||
final channel = _client.channel('messaging', id: channelId, extraData: {
|
final channel = _client.channel('messaging', id: channelId, extraData: {
|
||||||
'name': name,
|
'name': name!,
|
||||||
'image': image,
|
'image': image!,
|
||||||
'members': [_client.state.user.id, ...members],
|
'members': [_client.state.user!.id, ...members!],
|
||||||
});
|
});
|
||||||
await channel.watch();
|
await channel.watch();
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Channel> createSimpleChat(String friendId) async {
|
Future<Channel> createSimpleChat(String? friendId) async {
|
||||||
final channel =
|
final channel = _client.channel('messaging',
|
||||||
_client.channel('messaging', id: '${_client.state.user.id.hashCode}${friendId.hashCode}', extraData: {
|
id: '${_client.state.user!.id.hashCode}${friendId.hashCode}',
|
||||||
'members': [
|
extraData: {
|
||||||
friendId,
|
'members': [
|
||||||
_client.state.user.id,
|
friendId,
|
||||||
],
|
_client.state.user!.id,
|
||||||
});
|
],
|
||||||
|
});
|
||||||
await channel.watch();
|
await channel.watch();
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
@@ -81,6 +84,6 @@ class StreamApiLocalImpl extends StreamApiRepository {
|
|||||||
User(id: userId),
|
User(id: userId),
|
||||||
token,
|
token,
|
||||||
);
|
);
|
||||||
return _client.state.user.name != null && _client.state.user.name != userId;
|
return _client.state.user!.name != userId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:stream_chatter/data/upload_storage_repository.dart';
|
|||||||
|
|
||||||
class UploadStorageLocalImpl extends UploadStorageRepository {
|
class UploadStorageLocalImpl extends UploadStorageRepository {
|
||||||
@override
|
@override
|
||||||
Future<String> uploadPhoto(File file, String path) async {
|
Future<String> uploadPhoto(File? file, String path) async {
|
||||||
return 'https://lh3.googleusercontent.com/a-/AOh14GjhqGZ-V7tNXS1pOIp9vbBij4OS9JbzxXgxgy1t=s600-k-no-rp-mo';
|
return 'https://lh3.googleusercontent.com/a-/AOh14GjhqGZ-V7tNXS1pOIp9vbBij4OS9JbzxXgxgy1t=s600-k-no-rp-mo';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class AuthImpl extends AuthRepository {
|
|||||||
FirebaseAuth _auth = FirebaseAuth.instance;
|
FirebaseAuth _auth = FirebaseAuth.instance;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<AuthUser> getAuthUser() async {
|
Future<AuthUser?> getAuthUser() async {
|
||||||
final user = _auth.currentUser;
|
final user = _auth.currentUser;
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
return AuthUser(user.uid);
|
return AuthUser(user.uid);
|
||||||
@@ -19,14 +19,21 @@ class AuthImpl extends AuthRepository {
|
|||||||
Future<AuthUser> signIn() async {
|
Future<AuthUser> signIn() async {
|
||||||
try {
|
try {
|
||||||
UserCredential userCredential;
|
UserCredential userCredential;
|
||||||
final GoogleSignInAccount googleUser = await GoogleSignIn().signIn();
|
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
|
||||||
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
|
|
||||||
final GoogleAuthCredential googleAuthCredential = GoogleAuthProvider.credential(
|
if (googleUser == null) {
|
||||||
|
throw Exception('login error');
|
||||||
|
}
|
||||||
|
|
||||||
|
final GoogleSignInAuthentication googleAuth =
|
||||||
|
await googleUser.authentication;
|
||||||
|
final GoogleAuthCredential googleAuthCredential =
|
||||||
|
GoogleAuthProvider.credential(
|
||||||
accessToken: googleAuth.accessToken,
|
accessToken: googleAuth.accessToken,
|
||||||
idToken: googleAuth.idToken,
|
idToken: googleAuth.idToken,
|
||||||
);
|
) as GoogleAuthCredential;
|
||||||
userCredential = await _auth.signInWithCredential(googleAuthCredential);
|
userCredential = await _auth.signInWithCredential(googleAuthCredential);
|
||||||
final user = userCredential.user;
|
final user = userCredential.user!;
|
||||||
return AuthUser(user.uid);
|
return AuthUser(user.uid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
|
|||||||
@@ -13,6 +13,6 @@ class PersistentStorageImpl extends PersistentStorageRepository {
|
|||||||
@override
|
@override
|
||||||
Future<void> updateDarkMode(bool isDarkMode) async {
|
Future<void> updateDarkMode(bool isDarkMode) async {
|
||||||
final preference = await SharedPreferences.getInstance();
|
final preference = await SharedPreferences.getInstance();
|
||||||
return await preference.setBool(_isDarkMode, isDarkMode);
|
await preference.setBool(_isDarkMode, isDarkMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class StreamApiImpl extends StreamApiRepository {
|
|||||||
final StreamChatClient _client;
|
final StreamChatClient _client;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ChatUser> connectUser(ChatUser user, String token) async {
|
Future<ChatUser> connectUser(ChatUser user, String? token) async {
|
||||||
Map<String, dynamic> extraData = {};
|
Map<String, dynamic> extraData = {};
|
||||||
if (user.image != null) {
|
if (user.image != null) {
|
||||||
extraData['image'] = user.image;
|
extraData['image'] = user.image;
|
||||||
@@ -21,7 +21,7 @@ class StreamApiImpl extends StreamApiRepository {
|
|||||||
}
|
}
|
||||||
await _client.disconnect();
|
await _client.disconnect();
|
||||||
await _client.connectUser(
|
await _client.connectUser(
|
||||||
User(id: user.id, extraData: extraData),
|
User(id: user.id!, extraData: extraData as Map<String, Object>),
|
||||||
token,
|
token,
|
||||||
);
|
);
|
||||||
return user;
|
return user;
|
||||||
@@ -31,12 +31,12 @@ class StreamApiImpl extends StreamApiRepository {
|
|||||||
Future<List<ChatUser>> getChatUsers() async {
|
Future<List<ChatUser>> getChatUsers() async {
|
||||||
final result = await _client.queryUsers();
|
final result = await _client.queryUsers();
|
||||||
final chatUsers = result.users
|
final chatUsers = result.users
|
||||||
.where((element) => element.id != _client.state.user.id)
|
.where((element) => element.id != _client.state.user!.id)
|
||||||
.map(
|
.map(
|
||||||
(e) => ChatUser(
|
(e) => ChatUser(
|
||||||
id: e.id,
|
id: e.id,
|
||||||
name: e.name,
|
name: e.name,
|
||||||
image: e.extraData['image'],
|
image: e.extraData['image'] as String?,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -44,10 +44,10 @@ class StreamApiImpl extends StreamApiRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> getToken(String userId) async {
|
Future<String?> getToken(String userId) async {
|
||||||
//TODO: use your own implementation in Production
|
//TODO: use your own implementation in Production
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
'your_backend_url',
|
Uri.parse('your_backend_url'),
|
||||||
body: jsonEncode(<String, String>{'id': userId}),
|
body: jsonEncode(<String, String>{'id': userId}),
|
||||||
headers: <String, String>{
|
headers: <String, String>{
|
||||||
'Content-Type': 'application/json; charset=UTF-8',
|
'Content-Type': 'application/json; charset=UTF-8',
|
||||||
@@ -62,25 +62,27 @@ class StreamApiImpl extends StreamApiRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Channel> createGroupChat(String id, String name, List<String> members, {String image}) async {
|
Future<Channel> createGroupChat(String id, String? name, List<String?>? members,
|
||||||
|
{String? image}) async {
|
||||||
final channel = _client.channel('messaging', id: id, extraData: {
|
final channel = _client.channel('messaging', id: id, extraData: {
|
||||||
'name': name,
|
'name': name!,
|
||||||
'image': image,
|
'image': image!,
|
||||||
'members': [_client.state.user.id, ...members],
|
'members': [_client.state.user!.id, ...members!],
|
||||||
});
|
});
|
||||||
await channel.watch();
|
await channel.watch();
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Channel> createSimpleChat(String friendId) async {
|
Future<Channel> createSimpleChat(String? friendId) async {
|
||||||
final channel =
|
final channel = _client.channel('messaging',
|
||||||
_client.channel('messaging', id: '${_client.state.user.id.hashCode}${friendId.hashCode}', extraData: {
|
id: '${_client.state.user!.id.hashCode}${friendId.hashCode}',
|
||||||
'members': [
|
extraData: {
|
||||||
friendId,
|
'members': [
|
||||||
_client.state.user.id,
|
friendId,
|
||||||
],
|
_client.state.user!.id,
|
||||||
});
|
],
|
||||||
|
});
|
||||||
await channel.watch();
|
await channel.watch();
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
@@ -97,6 +99,6 @@ class StreamApiImpl extends StreamApiRepository {
|
|||||||
User(id: userId),
|
User(id: userId),
|
||||||
token,
|
token,
|
||||||
);
|
);
|
||||||
return _client.state.user.name != null && _client.state.user.name != userId;
|
return _client.state.user!.name != userId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import 'package:stream_chatter/data/upload_storage_repository.dart';
|
|||||||
|
|
||||||
class UploadStorageImpl extends UploadStorageRepository {
|
class UploadStorageImpl extends UploadStorageRepository {
|
||||||
@override
|
@override
|
||||||
Future<String> uploadPhoto(File file, String path) async {
|
Future<String> uploadPhoto(File? file, String path) async {
|
||||||
final ref = firebase_storage.FirebaseStorage.instance.ref(path);
|
final ref = firebase_storage.FirebaseStorage.instance.ref(path);
|
||||||
final uploadTask = ref.putFile(file);
|
final uploadTask = ref.putFile(file!);
|
||||||
await uploadTask;
|
await uploadTask;
|
||||||
return await ref.getDownloadURL();
|
return await ref.getDownloadURL();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|||||||
|
|
||||||
abstract class StreamApiRepository {
|
abstract class StreamApiRepository {
|
||||||
Future<List<ChatUser>> getChatUsers();
|
Future<List<ChatUser>> getChatUsers();
|
||||||
Future<String> getToken(String userId);
|
Future<String?> getToken(String userId);
|
||||||
Future<bool> connectIfExist(String userId);
|
Future<bool> connectIfExist(String userId);
|
||||||
Future<ChatUser> connectUser(ChatUser user, String token);
|
Future<ChatUser> connectUser(ChatUser user, String? token);
|
||||||
Future<Channel> createGroupChat(String channelId, String name, List<String> members, {String image});
|
Future<Channel> createGroupChat(
|
||||||
Future<Channel> createSimpleChat(String friendId);
|
String channelId, String? name, List<String?>? members,
|
||||||
|
{String? image});
|
||||||
|
Future<Channel> createSimpleChat(String? friendId);
|
||||||
Future<void> logout();
|
Future<void> logout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
abstract class UploadStorageRepository {
|
abstract class UploadStorageRepository {
|
||||||
Future<String> uploadPhoto(File file, String path);
|
Future<String> uploadPhoto(File? file, String path);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|||||||
List<RepositoryProvider> buildRepositories(StreamChatClient client) {
|
List<RepositoryProvider> buildRepositories(StreamChatClient client) {
|
||||||
//TODO: Here you can use your local implementations of your repositories
|
//TODO: Here you can use your local implementations of your repositories
|
||||||
return [
|
return [
|
||||||
RepositoryProvider<StreamApiRepository>(create: (_) => StreamApiImpl(client)),
|
RepositoryProvider<StreamApiRepository>(
|
||||||
RepositoryProvider<PersistentStorageRepository>(create: (_) => PersistentStorageImpl()),
|
create: (_) => StreamApiImpl(client)),
|
||||||
|
RepositoryProvider<PersistentStorageRepository>(
|
||||||
|
create: (_) => PersistentStorageImpl()),
|
||||||
RepositoryProvider<AuthRepository>(create: (_) => AuthImpl()),
|
RepositoryProvider<AuthRepository>(create: (_) => AuthImpl()),
|
||||||
RepositoryProvider<UploadStorageRepository>(create: (_) => UploadStorageImpl()),
|
RepositoryProvider<UploadStorageRepository>(
|
||||||
|
create: (_) => UploadStorageImpl()),
|
||||||
RepositoryProvider<ImagePickerRepository>(create: (_) => ImagePickerImpl()),
|
RepositoryProvider<ImagePickerRepository>(create: (_) => ImagePickerImpl()),
|
||||||
RepositoryProvider<ProfileSignInUseCase>(
|
RepositoryProvider<ProfileSignInUseCase>(
|
||||||
create: (context) => ProfileSignInUseCase(
|
create: (context) => ProfileSignInUseCase(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class ChatUser {
|
class ChatUser {
|
||||||
const ChatUser({this.name, this.image, this.id});
|
const ChatUser({this.name, this.image, this.id});
|
||||||
final String name;
|
final String? name;
|
||||||
final String image;
|
final String? image;
|
||||||
final String id;
|
final String? id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import 'package:uuid/uuid.dart';
|
|||||||
|
|
||||||
class CreateGroupInput {
|
class CreateGroupInput {
|
||||||
CreateGroupInput({this.imageFile, this.name, this.members});
|
CreateGroupInput({this.imageFile, this.name, this.members});
|
||||||
final File imageFile;
|
final File? imageFile;
|
||||||
final String name;
|
final String? name;
|
||||||
final List<String> members;
|
final List<String?>? members;
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateGroupUseCase {
|
class CreateGroupUseCase {
|
||||||
@@ -23,9 +23,10 @@ class CreateGroupUseCase {
|
|||||||
|
|
||||||
Future<Channel> createGroup(CreateGroupInput input) async {
|
Future<Channel> createGroup(CreateGroupInput input) async {
|
||||||
final channelId = Uuid().v4();
|
final channelId = Uuid().v4();
|
||||||
String image;
|
String? image;
|
||||||
if (input.imageFile != null) {
|
if (input.imageFile != null) {
|
||||||
image = await _uploadStorageRepository.uploadPhoto(input.imageFile, 'channels/$channelId');
|
image = await _uploadStorageRepository.uploadPhoto(
|
||||||
|
input.imageFile, 'channels/$channelId');
|
||||||
}
|
}
|
||||||
final channel = await _streamApiRepository.createGroupChat(
|
final channel = await _streamApiRepository.createGroupChat(
|
||||||
channelId,
|
channelId,
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import 'package:stream_chatter/data/stream_api_repository.dart';
|
|||||||
import 'package:stream_chatter/data/upload_storage_repository.dart';
|
import 'package:stream_chatter/data/upload_storage_repository.dart';
|
||||||
import 'package:stream_chatter/domain/models/chat_user.dart';
|
import 'package:stream_chatter/domain/models/chat_user.dart';
|
||||||
|
|
||||||
|
import '../exceptions/auth_exception.dart';
|
||||||
|
|
||||||
class ProfileInput {
|
class ProfileInput {
|
||||||
ProfileInput({this.imageFile, this.name});
|
ProfileInput({this.imageFile, this.name});
|
||||||
final File imageFile;
|
final File? imageFile;
|
||||||
final String name;
|
final String? name;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProfileSignInUseCase {
|
class ProfileSignInUseCase {
|
||||||
@@ -24,11 +26,21 @@ class ProfileSignInUseCase {
|
|||||||
|
|
||||||
Future<void> verify(ProfileInput input) async {
|
Future<void> verify(ProfileInput input) async {
|
||||||
final auth = await _authRepository.getAuthUser();
|
final auth = await _authRepository.getAuthUser();
|
||||||
final token = await _streamApiRepository.getToken(auth.id);
|
if (auth == null) {
|
||||||
String image;
|
throw AuthException(AuthErrorCode.not_auth);
|
||||||
if (input.imageFile != null) {
|
|
||||||
image = await _uploadStorageRepository.uploadPhoto(input.imageFile, 'users/${auth.id}');
|
|
||||||
}
|
}
|
||||||
await _streamApiRepository.connectUser(ChatUser(name: input.name, id: auth.id, image: image), token);
|
final token = await _streamApiRepository.getToken(auth.id);
|
||||||
|
String? image;
|
||||||
|
if (input.imageFile != null) {
|
||||||
|
image = await _uploadStorageRepository.uploadPhoto(
|
||||||
|
input.imageFile, 'users/${auth.id}');
|
||||||
|
}
|
||||||
|
await _streamApiRepository.connectUser(
|
||||||
|
ChatUser(
|
||||||
|
name: input.name,
|
||||||
|
id: auth.id,
|
||||||
|
image: image,
|
||||||
|
),
|
||||||
|
token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ class MyApp extends StatelessWidget {
|
|||||||
return StreamChat(
|
return StreamChat(
|
||||||
child: child,
|
child: child,
|
||||||
client: _streamChatClient,
|
client: _streamChatClient,
|
||||||
streamChatThemeData: StreamChatThemeData.fromTheme(Theme.of(context)).copyWith(
|
streamChatThemeData:
|
||||||
|
StreamChatThemeData.fromTheme(Theme.of(context)).copyWith(
|
||||||
ownMessageTheme: MessageTheme(
|
ownMessageTheme: MessageTheme(
|
||||||
messageBackgroundColor: Theme.of(context).accentColor,
|
messageBackgroundColor: Theme.of(context).accentColor,
|
||||||
messageText: TextStyle(color: Colors.white),
|
messageText: TextStyle(color: Colors.white),
|
||||||
|
|||||||
@@ -18,5 +18,7 @@ Future pushAndReplaceToPage(BuildContext context, Widget widget) async {
|
|||||||
|
|
||||||
Future popAllAndPush(BuildContext context, Widget widget) async {
|
Future popAllAndPush(BuildContext context, Widget widget) async {
|
||||||
await Navigator.pushAndRemoveUntil(
|
await Navigator.pushAndRemoveUntil(
|
||||||
context, MaterialPageRoute(builder: (BuildContext context) => widget), ModalRoute.withName('/'));
|
context,
|
||||||
|
MaterialPageRoute(builder: (BuildContext context) => widget),
|
||||||
|
ModalRoute.withName('/'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class AvatarImageView extends StatelessWidget {
|
class AvatarImageView extends StatelessWidget {
|
||||||
const AvatarImageView({Key key, this.onTap, this.child}) : super(key: key);
|
const AvatarImageView({Key? key, this.onTap, this.child}) : super(key: key);
|
||||||
final Widget child;
|
final Widget? child;
|
||||||
final VoidCallback onTap;
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ class LoadingView extends StatelessWidget {
|
|||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const LoadingView({
|
const LoadingView({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.child,
|
required this.child,
|
||||||
this.isLoading = false,
|
this.isLoading = false,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart' show IterableExtension;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
@@ -30,22 +31,22 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
|||||||
/// Modify it to change the widget appearance.
|
/// Modify it to change the widget appearance.
|
||||||
class MyChannelPreview extends StatelessWidget {
|
class MyChannelPreview extends StatelessWidget {
|
||||||
/// Function called when tapping this widget
|
/// Function called when tapping this widget
|
||||||
final void Function(Channel) onTap;
|
final void Function(Channel)? onTap;
|
||||||
|
|
||||||
/// Function called when long pressing this widget
|
/// Function called when long pressing this widget
|
||||||
final void Function(Channel) onLongPress;
|
final void Function(Channel)? onLongPress;
|
||||||
|
|
||||||
/// Channel displayed
|
/// Channel displayed
|
||||||
final Channel channel;
|
final Channel channel;
|
||||||
|
|
||||||
/// The function called when the image is tapped
|
/// The function called when the image is tapped
|
||||||
final VoidCallback onImageTap;
|
final VoidCallback? onImageTap;
|
||||||
|
|
||||||
final String heroTag;
|
final String? heroTag;
|
||||||
|
|
||||||
MyChannelPreview({
|
MyChannelPreview({
|
||||||
@required this.channel,
|
required this.channel,
|
||||||
Key key,
|
Key? key,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
this.onImageTap,
|
this.onImageTap,
|
||||||
@@ -59,24 +60,24 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
initialData: channel.isMuted,
|
initialData: channel.isMuted,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
return Opacity(
|
return Opacity(
|
||||||
opacity: snapshot.data ? 0.5 : 1,
|
opacity: snapshot.data! ? 0.5 : 1,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 8,
|
horizontal: 8,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (onTap != null) {
|
if (onTap != null) {
|
||||||
onTap(channel);
|
onTap!(channel);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
if (onLongPress != null) {
|
if (onLongPress != null) {
|
||||||
onLongPress(channel);
|
onLongPress!(channel);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
leading: Material(
|
leading: Material(
|
||||||
child: Hero(
|
child: Hero(
|
||||||
tag: heroTag,
|
tag: heroTag!,
|
||||||
child: StreamChannel(
|
child: StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: ChannelImage(
|
child: ChannelImage(
|
||||||
@@ -90,16 +91,18 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Flexible(
|
Flexible(
|
||||||
child: ChannelName(
|
child: ChannelName(
|
||||||
textStyle: StreamChatTheme.of(context).channelPreviewTheme.title,
|
textStyle:
|
||||||
|
StreamChatTheme.of(context).channelPreviewTheme.title,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
StreamBuilder<List<Member>>(
|
StreamBuilder<List<Member>>(
|
||||||
stream: channel.state.membersStream,
|
stream: channel.state!.membersStream,
|
||||||
initialData: channel.state.members,
|
initialData: channel.state!.members,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData ||
|
if (!snapshot.hasData ||
|
||||||
snapshot.data.isEmpty ||
|
snapshot.data!.isEmpty ||
|
||||||
!snapshot.data.any((Member e) => e.user.id == channel.client.state.user.id)) {
|
!snapshot.data!.any((Member e) =>
|
||||||
|
e.user!.id == channel.client.state.user!.id)) {
|
||||||
return SizedBox();
|
return SizedBox();
|
||||||
}
|
}
|
||||||
return ChannelUnreadIndicator(
|
return ChannelUnreadIndicator(
|
||||||
@@ -114,20 +117,26 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
Flexible(child: _buildSubtitle(context)),
|
Flexible(child: _buildSubtitle(context)),
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final lastMessage = channel.state.messages.lastWhere(
|
final lastMessage =
|
||||||
|
channel.state!.messages.lastWhereOrNull(
|
||||||
(m) => !m.isDeleted && m.shadowed != true,
|
(m) => !m.isDeleted && m.shadowed != true,
|
||||||
orElse: () => null,
|
|
||||||
);
|
);
|
||||||
if (lastMessage?.user?.id == StreamChat.of(context).user.id) {
|
if (lastMessage?.user?.id ==
|
||||||
|
StreamChat.of(context).user!.id) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(right: 4.0),
|
padding: const EdgeInsets.only(right: 4.0),
|
||||||
child: SendingIndicator(
|
child: SendingIndicator(
|
||||||
message: lastMessage,
|
message: lastMessage!,
|
||||||
size: StreamChatTheme.of(context).channelPreviewTheme.indicatorIconSize,
|
size: StreamChatTheme.of(context)
|
||||||
isMessageRead: channel.state.read
|
.channelPreviewTheme
|
||||||
?.where((element) => element.user.id != channel.client.state.user.id)
|
.indicatorIconSize,
|
||||||
?.where((element) => element.lastRead.isAfter(lastMessage.createdAt))
|
isMessageRead: channel.state!.read
|
||||||
?.isNotEmpty ==
|
?.where((element) =>
|
||||||
|
element.user.id !=
|
||||||
|
channel.client.state.user!.id)
|
||||||
|
.where((element) => element.lastRead
|
||||||
|
.isAfter(lastMessage.createdAt))
|
||||||
|
.isNotEmpty ==
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -144,21 +153,22 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDate(BuildContext context) {
|
Widget _buildDate(BuildContext context) {
|
||||||
return StreamBuilder<DateTime>(
|
return StreamBuilder<DateTime?>(
|
||||||
stream: channel.lastMessageAtStream,
|
stream: channel.lastMessageAtStream,
|
||||||
initialData: channel.lastMessageAt,
|
initialData: channel.lastMessageAt,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return SizedBox();
|
return SizedBox();
|
||||||
}
|
}
|
||||||
final lastMessageAt = snapshot.data.toLocal();
|
final lastMessageAt = snapshot.data!.toLocal();
|
||||||
|
|
||||||
String stringDate;
|
String stringDate;
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
|
|
||||||
var startOfDay = DateTime(now.year, now.month, now.day);
|
var startOfDay = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
if (lastMessageAt.millisecondsSinceEpoch >= startOfDay.millisecondsSinceEpoch) {
|
if (lastMessageAt.millisecondsSinceEpoch >=
|
||||||
|
startOfDay.millisecondsSinceEpoch) {
|
||||||
stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm');
|
stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm');
|
||||||
} else if (lastMessageAt.millisecondsSinceEpoch >=
|
} else if (lastMessageAt.millisecondsSinceEpoch >=
|
||||||
startOfDay.subtract(Duration(days: 1)).millisecondsSinceEpoch) {
|
startOfDay.subtract(Duration(days: 1)).millisecondsSinceEpoch) {
|
||||||
@@ -187,8 +197,14 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
' Channel is muted',
|
' Channel is muted',
|
||||||
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
style: StreamChatTheme.of(context)
|
||||||
color: StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
|
.channelPreviewTheme
|
||||||
|
.subtitle!
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.channelPreviewTheme
|
||||||
|
.subtitle!
|
||||||
|
.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -197,52 +213,64 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
return TypingIndicator(
|
return TypingIndicator(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
alternativeWidget: _buildLastMessage(context),
|
alternativeWidget: _buildLastMessage(context),
|
||||||
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle!.copyWith(
|
||||||
color: StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
|
color:
|
||||||
|
StreamChatTheme.of(context).channelPreviewTheme.subtitle!.color,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLastMessage(BuildContext context) {
|
Widget _buildLastMessage(BuildContext context) {
|
||||||
return StreamBuilder<List<Message>>(
|
return StreamBuilder<List<Message>?>(
|
||||||
stream: channel.state.messagesStream,
|
stream: channel.state!.messagesStream,
|
||||||
initialData: channel.state.messages,
|
initialData: channel.state!.messages,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final lastMessage = snapshot.data?.lastWhere((m) => m.shadowed != true && !m.isDeleted, orElse: () => null);
|
final lastMessage = snapshot.data
|
||||||
|
?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted);
|
||||||
if (lastMessage == null) {
|
if (lastMessage == null) {
|
||||||
return SizedBox();
|
return SizedBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
var text = lastMessage.text;
|
var text = lastMessage.text;
|
||||||
if (lastMessage.attachments != null) {
|
final parts = <String>[
|
||||||
final parts = <String>[
|
...lastMessage.attachments.map((e) {
|
||||||
...lastMessage.attachments.map((e) {
|
if (e.type == 'image') {
|
||||||
if (e.type == 'image') {
|
return '📷';
|
||||||
return '📷';
|
} else if (e.type == 'video') {
|
||||||
} else if (e.type == 'video') {
|
return '🎬';
|
||||||
return '🎬';
|
} else if (e.type == 'giphy') {
|
||||||
} else if (e.type == 'giphy') {
|
return '[GIF]';
|
||||||
return '[GIF]';
|
}
|
||||||
}
|
return e == lastMessage.attachments.last
|
||||||
return e == lastMessage.attachments.last ? (e.title ?? 'File') : '${e.title ?? 'File'} , ';
|
? (e.title ?? 'File')
|
||||||
}).where((e) => e != null),
|
: '${e.title ?? 'File'} , ';
|
||||||
lastMessage.text ?? '',
|
}),
|
||||||
];
|
lastMessage.text ?? '',
|
||||||
|
];
|
||||||
|
|
||||||
text = parts.join(' ');
|
text = parts.join(' ');
|
||||||
}
|
|
||||||
|
|
||||||
return Text.rich(
|
return Text.rich(
|
||||||
_getDisplayText(
|
_getDisplayText(
|
||||||
text,
|
text,
|
||||||
lastMessage.mentionedUsers,
|
lastMessage.mentionedUsers,
|
||||||
lastMessage.attachments,
|
lastMessage.attachments,
|
||||||
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
StreamChatTheme.of(context).channelPreviewTheme.subtitle!.copyWith(
|
||||||
color: StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
|
color: StreamChatTheme.of(context)
|
||||||
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) ? FontStyle.italic : FontStyle.normal),
|
.channelPreviewTheme
|
||||||
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
.subtitle!
|
||||||
color: StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
|
.color,
|
||||||
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) ? FontStyle.italic : FontStyle.normal,
|
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
||||||
|
? FontStyle.italic
|
||||||
|
: FontStyle.normal),
|
||||||
|
StreamChatTheme.of(context).channelPreviewTheme.subtitle!.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.channelPreviewTheme
|
||||||
|
.subtitle!
|
||||||
|
.color,
|
||||||
|
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
||||||
|
? FontStyle.italic
|
||||||
|
: FontStyle.normal,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
@@ -252,19 +280,25 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSpan _getDisplayText(String text, List<User> mentions, List<Attachment> attachments, TextStyle normalTextStyle,
|
TextSpan _getDisplayText(
|
||||||
|
String text,
|
||||||
|
List<User> mentions,
|
||||||
|
List<Attachment> attachments,
|
||||||
|
TextStyle normalTextStyle,
|
||||||
TextStyle mentionsTextStyle) {
|
TextStyle mentionsTextStyle) {
|
||||||
var textList = text.split(' ');
|
var textList = text.split(' ');
|
||||||
var resList = <TextSpan>[];
|
var resList = <TextSpan>[];
|
||||||
for (var e in textList) {
|
for (var e in textList) {
|
||||||
if (mentions != null && mentions.isNotEmpty && mentions.any((element) => '@${element.name}' == e)) {
|
if (mentions.isNotEmpty &&
|
||||||
|
mentions.any((element) => '@${element.name}' == e)) {
|
||||||
resList.add(TextSpan(
|
resList.add(TextSpan(
|
||||||
text: '$e ',
|
text: '$e ',
|
||||||
style: mentionsTextStyle,
|
style: mentionsTextStyle,
|
||||||
));
|
));
|
||||||
} else if (attachments != null &&
|
} else if (attachments.isNotEmpty &&
|
||||||
attachments.isNotEmpty &&
|
attachments
|
||||||
attachments.where((e) => e.title != null).any((element) => element.title == e)) {
|
.where((e) => e.title != null)
|
||||||
|
.any((element) => element.title == e)) {
|
||||||
resList.add(TextSpan(
|
resList.add(TextSpan(
|
||||||
text: '$e ',
|
text: '$e ',
|
||||||
style: normalTextStyle.copyWith(fontStyle: FontStyle.italic),
|
style: normalTextStyle.copyWith(fontStyle: FontStyle.italic),
|
||||||
@@ -283,8 +317,8 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
|
|
||||||
class ChannelUnreadIndicator extends StatelessWidget {
|
class ChannelUnreadIndicator extends StatelessWidget {
|
||||||
const ChannelUnreadIndicator({
|
const ChannelUnreadIndicator({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.channel,
|
required this.channel,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final Channel channel;
|
final Channel channel;
|
||||||
@@ -292,8 +326,8 @@ class ChannelUnreadIndicator extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return StreamBuilder<int>(
|
return StreamBuilder<int>(
|
||||||
stream: channel.state.unreadCountStream,
|
stream: channel.state!.unreadCountStream,
|
||||||
initialData: channel.state.unreadCount,
|
initialData: channel.state!.unreadCount,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData || snapshot.data == 0) {
|
if (!snapshot.hasData || snapshot.data == 0) {
|
||||||
return SizedBox();
|
return SizedBox();
|
||||||
@@ -301,7 +335,9 @@ class ChannelUnreadIndicator extends StatelessWidget {
|
|||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: StreamChatTheme.of(context).channelPreviewTheme.unreadCounterColor,
|
color: StreamChatTheme.of(context)
|
||||||
|
.channelPreviewTheme
|
||||||
|
.unreadCounterColor,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
left: 5.0,
|
left: 5.0,
|
||||||
@@ -311,7 +347,7 @@ class ChannelUnreadIndicator extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${snapshot.data > 99 ? '99+' : snapshot.data}',
|
'${snapshot.data! > 99 ? '99+' : snapshot.data}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|||||||
@@ -23,11 +23,10 @@ class ChatView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
body: ChannelsBloc(
|
body: ChannelsBloc(
|
||||||
child: ChannelListView(
|
child: ChannelListView(
|
||||||
filter: {
|
filter: Filter.in_(
|
||||||
'members': {
|
'members',
|
||||||
'\$in': [StreamChat.of(context).user?.id],
|
[StreamChat.of(context).user!.id],
|
||||||
}
|
),
|
||||||
},
|
|
||||||
sort: [SortOption('last_message_at')],
|
sort: [SortOption('last_message_at')],
|
||||||
channelPreviewBuilder: (context, channel) {
|
channelPreviewBuilder: (context, channel) {
|
||||||
return Container(
|
return Container(
|
||||||
@@ -36,20 +35,22 @@ class ChatView extends StatelessWidget {
|
|||||||
channel: channel,
|
channel: channel,
|
||||||
heroTag: channel.id,
|
heroTag: channel.id,
|
||||||
onImageTap: () {
|
onImageTap: () {
|
||||||
String name;
|
String? name;
|
||||||
String image;
|
String? image;
|
||||||
final currentUser = StreamChat.of(context).client.state.user;
|
final currentUser = StreamChat.of(context).client.state.user;
|
||||||
if (channel.isGroup) {
|
if (channel.isGroup) {
|
||||||
name = channel.extraData['name'];
|
name = channel.extraData['name'];
|
||||||
image = channel.extraData['image'];
|
image = channel.extraData['image'];
|
||||||
} else {
|
} else {
|
||||||
final friend =
|
final friend = channel.state!.members
|
||||||
channel.state.members.where((element) => element.userId != currentUser.id).first.user;
|
.where((element) => element.userId != currentUser!.id)
|
||||||
|
.first
|
||||||
|
.user!;
|
||||||
name = friend.name;
|
name = friend.name;
|
||||||
image = friend.extraData['image'];
|
image = friend.extraData['image'] as String?;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
PageRouteBuilder(
|
PageRouteBuilder(
|
||||||
barrierColor: Colors.black45,
|
barrierColor: Colors.black45,
|
||||||
barrierDismissible: true,
|
barrierDismissible: true,
|
||||||
@@ -108,15 +109,15 @@ class ChannelPage extends StatelessWidget {
|
|||||||
|
|
||||||
class ChatDetailView extends StatelessWidget {
|
class ChatDetailView extends StatelessWidget {
|
||||||
const ChatDetailView({
|
const ChatDetailView({
|
||||||
Key key,
|
Key? key,
|
||||||
this.image,
|
this.image,
|
||||||
this.name,
|
this.name,
|
||||||
this.channelId,
|
this.channelId,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String image;
|
final String? image;
|
||||||
final String name;
|
final String? name;
|
||||||
final String channelId;
|
final String? channelId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -133,10 +134,10 @@ class ChatDetailView extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Hero(
|
Hero(
|
||||||
tag: channelId,
|
tag: channelId!,
|
||||||
child: ClipOval(
|
child: ClipOval(
|
||||||
child: Image.network(
|
child: Image.network(
|
||||||
image,
|
image!,
|
||||||
height: 180,
|
height: 180,
|
||||||
width: 180,
|
width: 180,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@@ -144,7 +145,7 @@ class ChatDetailView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
name,
|
name!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
|
|||||||
@@ -13,21 +13,27 @@ class FriendsSelectionCubit extends Cubit<List<ChatUserState>> {
|
|||||||
FriendsSelectionCubit(this._streamApiRepository) : super([]);
|
FriendsSelectionCubit(this._streamApiRepository) : super([]);
|
||||||
final StreamApiRepository _streamApiRepository;
|
final StreamApiRepository _streamApiRepository;
|
||||||
|
|
||||||
List<ChatUserState> get selectedUsers => state.where((element) => element.selected).toList();
|
List<ChatUserState> get selectedUsers =>
|
||||||
|
state.where((element) => element.selected).toList();
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
final chatUsers = (await _streamApiRepository.getChatUsers()).map((e) => ChatUserState(e)).toList();
|
final chatUsers = (await _streamApiRepository.getChatUsers())
|
||||||
|
.map((e) => ChatUserState(e))
|
||||||
|
.toList();
|
||||||
emit(chatUsers);
|
emit(chatUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectUser(ChatUserState chatUser) {
|
void selectUser(ChatUserState chatUser) {
|
||||||
final index = state.indexWhere((element) => element.chatUser.id == chatUser.chatUser.id);
|
final index = state
|
||||||
state[index] = ChatUserState(state[index].chatUser, selected: !chatUser.selected);
|
.indexWhere((element) => element.chatUser.id == chatUser.chatUser.id);
|
||||||
|
state[index] =
|
||||||
|
ChatUserState(state[index].chatUser, selected: !chatUser.selected);
|
||||||
emit(List<ChatUserState>.from(state));
|
emit(List<ChatUserState>.from(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Channel> createFriendChannel(ChatUserState chatUserState) async {
|
Future<Channel> createFriendChannel(ChatUserState chatUserState) async {
|
||||||
return await _streamApiRepository.createSimpleChat(chatUserState.chatUser.id);
|
return await _streamApiRepository
|
||||||
|
.createSimpleChat(chatUserState.chatUser.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
class FriendsSelectionView extends StatelessWidget {
|
class FriendsSelectionView extends StatelessWidget {
|
||||||
void _createFriendChannel(BuildContext context, ChatUserState chatUserState) async {
|
void _createFriendChannel(
|
||||||
final channel = await context.read<FriendsSelectionCubit>().createFriendChannel(chatUserState);
|
BuildContext context, ChatUserState chatUserState) async {
|
||||||
|
final channel = await context
|
||||||
|
.read<FriendsSelectionCubit>()
|
||||||
|
.createFriendChannel(chatUserState);
|
||||||
pushAndReplaceToPage(
|
pushAndReplaceToPage(
|
||||||
context,
|
context,
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -26,19 +29,23 @@ class FriendsSelectionView extends StatelessWidget {
|
|||||||
final accentColor = Theme.of(context).accentColor;
|
final accentColor = Theme.of(context).accentColor;
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider(create: (context) => FriendsSelectionCubit(context.read())..init()),
|
BlocProvider(
|
||||||
|
create: (context) => FriendsSelectionCubit(context.read())..init()),
|
||||||
BlocProvider(create: (_) => FriendsGroupCubit()),
|
BlocProvider(create: (_) => FriendsGroupCubit()),
|
||||||
],
|
],
|
||||||
child: BlocBuilder<FriendsGroupCubit, bool>(builder: (context, isGroup) {
|
child: BlocBuilder<FriendsGroupCubit, bool>(builder: (context, isGroup) {
|
||||||
return BlocBuilder<FriendsSelectionCubit, List<ChatUserState>>(builder: (context, snapshot) {
|
return BlocBuilder<FriendsSelectionCubit, List<ChatUserState>>(
|
||||||
final selectedUsers = context.read<FriendsSelectionCubit>().selectedUsers;
|
builder: (context, snapshot) {
|
||||||
|
final selectedUsers =
|
||||||
|
context.read<FriendsSelectionCubit>().selectedUsers;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
floatingActionButton: isGroup && selectedUsers.isNotEmpty
|
floatingActionButton: isGroup && selectedUsers.isNotEmpty
|
||||||
? FloatingActionButton(
|
? FloatingActionButton(
|
||||||
child: Icon(Icons.arrow_right_alt_rounded),
|
child: Icon(Icons.arrow_right_alt_rounded),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
pushAndReplaceToPage(context, GroupSelectionView(selectedUsers));
|
pushAndReplaceToPage(
|
||||||
|
context, GroupSelectionView(selectedUsers));
|
||||||
})
|
})
|
||||||
: null,
|
: null,
|
||||||
backgroundColor: Theme.of(context).canvasColor,
|
backgroundColor: Theme.of(context).canvasColor,
|
||||||
@@ -91,12 +98,14 @@ class FriendsSelectionView extends StatelessWidget {
|
|||||||
backgroundColor: accentColor,
|
backgroundColor: accentColor,
|
||||||
child: Icon(Icons.group_outlined),
|
child: Icon(Icons.group_outlined),
|
||||||
),
|
),
|
||||||
title: Text('Create group', style: TextStyle(fontWeight: FontWeight.w700)),
|
title: Text('Create group',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.w700)),
|
||||||
subtitle: Text('Talk with 2 or more contacts'),
|
subtitle: Text('Talk with 2 or more contacts'),
|
||||||
)
|
)
|
||||||
else if (isGroup && selectedUsers.isEmpty)
|
else if (isGroup && selectedUsers.isEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 15.0, left: 20.0, bottom: 20),
|
padding: const EdgeInsets.only(
|
||||||
|
top: 15.0, left: 20.0, bottom: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -123,7 +132,8 @@ class FriendsSelectionView extends StatelessWidget {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final chatUserState = selectedUsers[index];
|
final chatUserState = selectedUsers[index];
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 13.0),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 13.0),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
@@ -132,20 +142,24 @@ class FriendsSelectionView extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 30,
|
radius: 30,
|
||||||
backgroundImage: NetworkImage(chatUserState.chatUser.image),
|
backgroundImage: NetworkImage(
|
||||||
|
chatUserState.chatUser.image!),
|
||||||
),
|
),
|
||||||
Text(chatUserState.chatUser.name),
|
Text(chatUserState.chatUser.name!),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 40,
|
bottom: 40,
|
||||||
right: -4,
|
right: -4,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => context.read<FriendsSelectionCubit>().selectUser(chatUserState),
|
onTap: () => context
|
||||||
|
.read<FriendsSelectionCubit>()
|
||||||
|
.selectUser(chatUserState),
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 9,
|
radius: 9,
|
||||||
backgroundColor: accentColor,
|
backgroundColor: accentColor,
|
||||||
child: Icon(Icons.close_rounded, size: 12),
|
child: Icon(Icons.close_rounded,
|
||||||
|
size: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -163,15 +177,18 @@ class FriendsSelectionView extends StatelessWidget {
|
|||||||
_createFriendChannel(context, chatUserState);
|
_createFriendChannel(context, chatUserState);
|
||||||
},
|
},
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
backgroundImage: NetworkImage(chatUserState.chatUser.image),
|
backgroundImage:
|
||||||
|
NetworkImage(chatUserState.chatUser.image!),
|
||||||
),
|
),
|
||||||
title: Text(chatUserState.chatUser.name),
|
title: Text(chatUserState.chatUser.name!),
|
||||||
trailing: isGroup
|
trailing: isGroup
|
||||||
? Checkbox(
|
? Checkbox(
|
||||||
value: chatUserState.selected,
|
value: chatUserState.selected,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
print('select user for group');
|
print('select user for group');
|
||||||
context.read<FriendsSelectionCubit>().selectUser(chatUserState);
|
context
|
||||||
|
.read<FriendsSelectionCubit>()
|
||||||
|
.selectUser(chatUserState);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ class GroupSelectionState {
|
|||||||
this.channel,
|
this.channel,
|
||||||
this.isLoading = false,
|
this.isLoading = false,
|
||||||
});
|
});
|
||||||
final File file;
|
final File? file;
|
||||||
final Channel channel;
|
final Channel? channel;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ class GroupSelectionView extends StatelessWidget {
|
|||||||
context.read(),
|
context.read(),
|
||||||
context.read(),
|
context.read(),
|
||||||
),
|
),
|
||||||
child: BlocConsumer<GroupSelectionCubit, GroupSelectionState>(listener: (context, snapshot) {
|
child: BlocConsumer<GroupSelectionCubit, GroupSelectionState>(
|
||||||
|
listener: (context, snapshot) {
|
||||||
if (snapshot.channel != null) {
|
if (snapshot.channel != null) {
|
||||||
pushAndReplaceToPage(
|
pushAndReplaceToPage(
|
||||||
context,
|
context,
|
||||||
Scaffold(
|
Scaffold(
|
||||||
body: StreamChannel(
|
body: StreamChannel(
|
||||||
channel: snapshot.channel,
|
channel: snapshot.channel!,
|
||||||
child: ChannelPage(),
|
child: ChannelPage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -59,9 +60,9 @@ class GroupSelectionView extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
AvatarImageView(
|
AvatarImageView(
|
||||||
onTap: context.read<GroupSelectionCubit>().pickImage,
|
onTap: context.read<GroupSelectionCubit>().pickImage,
|
||||||
child: snapshot?.file != null
|
child: snapshot.file != null
|
||||||
? Image.file(
|
? Image.file(
|
||||||
snapshot?.file,
|
snapshot.file!,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
)
|
)
|
||||||
: Icon(
|
: Icon(
|
||||||
@@ -76,9 +77,12 @@ class GroupSelectionView extends StatelessWidget {
|
|||||||
vertical: 20,
|
vertical: 20,
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: context.read<GroupSelectionCubit>().nameTextController,
|
controller:
|
||||||
|
context.read<GroupSelectionCubit>().nameTextController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
fillColor: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
|
fillColor: Theme.of(context)
|
||||||
|
.bottomNavigationBarTheme
|
||||||
|
.backgroundColor,
|
||||||
hintText: 'Name of the group',
|
hintText: 'Name of the group',
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@@ -99,9 +103,10 @@ class GroupSelectionView extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 30,
|
radius: 30,
|
||||||
backgroundImage: NetworkImage(chatUserState.chatUser.image),
|
backgroundImage:
|
||||||
|
NetworkImage(chatUserState.chatUser.image!),
|
||||||
),
|
),
|
||||||
Text(chatUserState.chatUser.name),
|
Text(chatUserState.chatUser.name!),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class HomeView extends StatelessWidget {
|
|||||||
|
|
||||||
class HomeNavigationBar extends StatelessWidget {
|
class HomeNavigationBar extends StatelessWidget {
|
||||||
const HomeNavigationBar({
|
const HomeNavigationBar({
|
||||||
Key key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -61,7 +61,9 @@ class HomeNavigationBar extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(25),
|
borderRadius: BorderRadius.circular(25),
|
||||||
color: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
|
color: Theme.of(context)
|
||||||
|
.bottomNavigationBarTheme
|
||||||
|
.backgroundColor,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
@@ -108,22 +110,24 @@ class HomeNavigationBar extends StatelessWidget {
|
|||||||
|
|
||||||
class _HomeNavItem extends StatelessWidget {
|
class _HomeNavItem extends StatelessWidget {
|
||||||
const _HomeNavItem({
|
const _HomeNavItem({
|
||||||
Key key,
|
Key? key,
|
||||||
this.iconData,
|
this.iconData,
|
||||||
this.text,
|
this.text,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.selected = false,
|
this.selected = false,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final IconData iconData;
|
final IconData? iconData;
|
||||||
final String text;
|
final String? text;
|
||||||
final VoidCallback onTap;
|
final VoidCallback? onTap;
|
||||||
final bool selected;
|
final bool selected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final selectedColor = Theme.of(context).bottomNavigationBarTheme.selectedItemColor;
|
final selectedColor =
|
||||||
final unselectedColor = Theme.of(context).bottomNavigationBarTheme.unselectedItemColor;
|
Theme.of(context).bottomNavigationBarTheme.selectedItemColor;
|
||||||
|
final unselectedColor =
|
||||||
|
Theme.of(context).bottomNavigationBarTheme.unselectedItemColor;
|
||||||
final color = selected ? selectedColor : unselectedColor;
|
final color = selected ? selectedColor : unselectedColor;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
@@ -131,7 +135,7 @@ class _HomeNavItem extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(iconData, color: color),
|
Icon(iconData, color: color),
|
||||||
Text(text, style: TextStyle(color: color)),
|
Text(text!, style: TextStyle(color: color)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|||||||
class SettingsView extends StatelessWidget {
|
class SettingsView extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final user = StreamChat.of(context).client.state.user;
|
final user = StreamChat.of(context).client.state.user!;
|
||||||
final image = user?.extraData['image'];
|
final image = user.extraData['image'];
|
||||||
final textColor = Theme.of(context).appBarTheme.color;
|
final textColor = Theme.of(context).appBarTheme.color;
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (_) => SettingsSwitchCubit(context.read<AppThemeCubit>().isDark),
|
create: (_) =>
|
||||||
|
SettingsSwitchCubit(context.read<AppThemeCubit>().isDark),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (_) => SettingsLogoutCubit(context.read()),
|
create: (_) => SettingsLogoutCubit(context.read()),
|
||||||
@@ -47,7 +48,7 @@ class SettingsView extends StatelessWidget {
|
|||||||
onTap: () => null,
|
onTap: () => null,
|
||||||
child: image != null
|
child: image != null
|
||||||
? Image.network(
|
? Image.network(
|
||||||
image,
|
image as String,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
)
|
)
|
||||||
: Icon(
|
: Icon(
|
||||||
@@ -75,11 +76,14 @@ class SettingsView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
BlocBuilder<SettingsSwitchCubit, bool>(builder: (context, snapshot) {
|
BlocBuilder<SettingsSwitchCubit, bool>(
|
||||||
|
builder: (context, snapshot) {
|
||||||
return Switch(
|
return Switch(
|
||||||
value: snapshot,
|
value: snapshot,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
context.read<SettingsSwitchCubit>().onChangeDarkMode(val);
|
context
|
||||||
|
.read<SettingsSwitchCubit>()
|
||||||
|
.onChangeDarkMode(val);
|
||||||
context.read<AppThemeCubit>().updateTheme(val);
|
context.read<AppThemeCubit>().updateTheme(val);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class ProfileState {
|
|||||||
this.success = false,
|
this.success = false,
|
||||||
this.loading = false,
|
this.loading = false,
|
||||||
});
|
});
|
||||||
final File file;
|
final File? file;
|
||||||
final bool success;
|
final bool success;
|
||||||
final bool loading;
|
final bool loading;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ class ProfileVerifyView extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => ProfileVerifyCubit(context.read(), context.read()),
|
create: (context) => ProfileVerifyCubit(context.read(), context.read()),
|
||||||
child: BlocConsumer<ProfileVerifyCubit, ProfileState>(listener: (context, snapshot) {
|
child: BlocConsumer<ProfileVerifyCubit, ProfileState>(
|
||||||
|
listener: (context, snapshot) {
|
||||||
if (snapshot.success) {
|
if (snapshot.success) {
|
||||||
pushAndReplaceToPage(context, HomeView());
|
pushAndReplaceToPage(context, HomeView());
|
||||||
}
|
}
|
||||||
@@ -38,7 +39,7 @@ class ProfileVerifyView extends StatelessWidget {
|
|||||||
onTap: context.read<ProfileVerifyCubit>().pickImage,
|
onTap: context.read<ProfileVerifyCubit>().pickImage,
|
||||||
child: snapshot.file != null
|
child: snapshot.file != null
|
||||||
? Image.file(
|
? Image.file(
|
||||||
snapshot.file,
|
snapshot.file!,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
)
|
)
|
||||||
: Icon(
|
: Icon(
|
||||||
@@ -59,9 +60,12 @@ class ProfileVerifyView extends StatelessWidget {
|
|||||||
vertical: 20,
|
vertical: 20,
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: context.read<ProfileVerifyCubit>().nameController,
|
controller:
|
||||||
|
context.read<ProfileVerifyCubit>().nameController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
fillColor: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
|
fillColor: Theme.of(context)
|
||||||
|
.bottomNavigationBarTheme
|
||||||
|
.backgroundColor,
|
||||||
hintText: 'Or just how people now you',
|
hintText: 'Or just how people now you',
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
|
|||||||
@@ -20,10 +20,8 @@ class SignInCubit extends Cubit<SignInState> {
|
|||||||
emit(SignInState.existing_user);
|
emit(SignInState.existing_user);
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
final result = await _loginUseCase.signIn();
|
_loginUseCase.signIn();
|
||||||
if (result != null) {
|
emit(SignInState.none);
|
||||||
emit(SignInState.none);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ class SignInView extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => SignInCubit(context.read()),
|
create: (context) => SignInCubit(context.read()),
|
||||||
child: BlocConsumer<SignInCubit, SignInState>(listener: (context, snapshot) {
|
child:
|
||||||
|
BlocConsumer<SignInCubit, SignInState>(listener: (context, snapshot) {
|
||||||
if (snapshot == SignInState.none) {
|
if (snapshot == SignInState.none) {
|
||||||
pushAndReplaceToPage(context, ProfileVerifyView());
|
pushAndReplaceToPage(context, ProfileVerifyView());
|
||||||
} else {
|
} else {
|
||||||
@@ -59,7 +60,9 @@ class SignInView extends StatelessWidget {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
color: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
|
color: Theme.of(context)
|
||||||
|
.bottomNavigationBarTheme
|
||||||
|
.backgroundColor,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<SignInCubit>().signIn();
|
context.read<SignInCubit>().signIn();
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class Themes {
|
|||||||
selectedItemColor: primaryColor,
|
selectedItemColor: primaryColor,
|
||||||
unselectedItemColor: Colors.grey[300],
|
unselectedItemColor: Colors.grey[300],
|
||||||
),
|
),
|
||||||
textSelectionColor: Colors.white,
|
textSelectionTheme: TextSelectionThemeData(selectionColor: Colors.white),
|
||||||
// switch active color
|
// switch active color
|
||||||
toggleableActiveColor: primaryColor,
|
toggleableActiveColor: primaryColor,
|
||||||
canvasColor: backgroundDarkColor,
|
canvasColor: backgroundDarkColor,
|
||||||
|
|||||||
@@ -4,22 +4,23 @@ publish_to: 'none'
|
|||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.7.0 <3.0.0"
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
flutter_bloc: 6.1.2
|
flutter_bloc: ^7.0.0
|
||||||
stream_chat_flutter: 1.3.0-beta
|
stream_chat_flutter: ^2.0.0-nullsafety.3
|
||||||
uuid: 2.2.2
|
uuid: ^3.0.4
|
||||||
|
|
||||||
firebase_core: 0.7.0
|
firebase_core: ^1.2.0
|
||||||
google_sign_in: 4.5.9
|
google_sign_in: ^5.0.3
|
||||||
firebase_auth: 0.20.0+1
|
firebase_auth: ^1.2.0
|
||||||
firebase_storage: 7.0.0
|
firebase_storage: ^8.1.0
|
||||||
shared_preferences: 0.5.12+4
|
shared_preferences: ^2.0.5
|
||||||
http: any
|
http: any
|
||||||
|
collection: ^1.15.0-nullsafety.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart' show Channel;
|
|||||||
import 'package:imessage/utils.dart';
|
import 'package:imessage/utils.dart';
|
||||||
|
|
||||||
class ChannelImage extends StatelessWidget {
|
class ChannelImage extends StatelessWidget {
|
||||||
const ChannelImage({Key key, @required this.channel, @required this.size})
|
const ChannelImage({Key? key, required this.channel, required this.size})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
final Channel channel;
|
final Channel channel;
|
||||||
@@ -14,7 +14,7 @@ class ChannelImage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final avatarUrl = channel.extraData.containsKey('image') &&
|
final avatarUrl = channel.extraData.containsKey('image') &&
|
||||||
(channel.extraData['image'] as String).isNotEmpty
|
(channel.extraData['image'] as String).isNotEmpty
|
||||||
? channel.extraData['image'] as String
|
? channel.extraData['image'] as String?
|
||||||
: 'https://4.bp.blogspot.com/-Jx21kNqFSTU/UXemtqPhZCI/AAAAAAAAh74/BMGSzpU6F48/s1600/funny-cat-pictures-047-001.jpg';
|
: 'https://4.bp.blogspot.com/-Jx21kNqFSTU/UXemtqPhZCI/AAAAAAAAh74/BMGSzpU6F48/s1600/funny-cat-pictures-047-001.jpg';
|
||||||
|
|
||||||
return CupertinoCircleAvatar(
|
return CupertinoCircleAvatar(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'
|
|||||||
show Channel, StreamChannel;
|
show Channel, StreamChannel;
|
||||||
|
|
||||||
class ChannelListView extends StatelessWidget {
|
class ChannelListView extends StatelessWidget {
|
||||||
const ChannelListView({Key key, @required this.channels}) : super(key: key);
|
const ChannelListView({Key? key, required this.channels}) : super(key: key);
|
||||||
final List<Channel> channels;
|
final List<Channel> channels;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -36,10 +36,10 @@ class ChannelListView extends StatelessWidget {
|
|||||||
child,
|
child,
|
||||||
) =>
|
) =>
|
||||||
SharedAxisTransition(
|
SharedAxisTransition(
|
||||||
child: child,
|
|
||||||
animation: animation,
|
animation: animation,
|
||||||
secondaryAnimation: secondaryAnimation,
|
secondaryAnimation: secondaryAnimation,
|
||||||
transitionType: SharedAxisTransitionType.horizontal,
|
transitionType: SharedAxisTransitionType.horizontal,
|
||||||
|
child: child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart' show Channel;
|
|||||||
|
|
||||||
class ChannelNameText extends StatelessWidget {
|
class ChannelNameText extends StatelessWidget {
|
||||||
const ChannelNameText({
|
const ChannelNameText({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.channel,
|
required this.channel,
|
||||||
this.size = 17,
|
this.size = 17,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ class ChannelNameText extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Text(
|
return Text(
|
||||||
channel.extraData['name'] as String ?? 'No name',
|
channel.extraData['name'] as String? ?? 'No name',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: size,
|
fontSize: size,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:flutter/cupertino.dart';
|
|||||||
|
|
||||||
class ChannelPageAppBar extends StatelessWidget {
|
class ChannelPageAppBar extends StatelessWidget {
|
||||||
const ChannelPageAppBar({
|
const ChannelPageAppBar({
|
||||||
Key key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -11,19 +11,20 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
final Channel channel;
|
final Channel channel;
|
||||||
|
|
||||||
const ChannelPreview({
|
const ChannelPreview({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.onTap,
|
required this.onTap,
|
||||||
@required this.channel,
|
required this.channel,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final lastMessage =
|
final lastMessage = channel.state!.messages.isNotEmpty
|
||||||
channel.state.messages.isNotEmpty ? channel.state.messages.last : null;
|
? channel.state!.messages.last
|
||||||
|
: null;
|
||||||
|
|
||||||
final prefix = lastMessage?.attachments != null
|
final prefix = lastMessage?.attachments != null
|
||||||
? lastMessage?.attachments //TODO: ugly
|
? lastMessage?.attachments //TODO: ugly
|
||||||
?.map((e) {
|
.map((e) {
|
||||||
if (e.type == 'image') {
|
if (e.type == 'image') {
|
||||||
return '📷 ';
|
return '📷 ';
|
||||||
} else if (e.type == 'video') {
|
} else if (e.type == 'video') {
|
||||||
@@ -31,8 +32,8 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
?.where((e) => e != null)
|
.where((e) => e != null)
|
||||||
?.join(' ')
|
.join(' ')
|
||||||
: '';
|
: '';
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
@@ -76,9 +77,9 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
isSameWeek(channel.lastMessageAt)
|
isSameWeek(channel.lastMessageAt!)
|
||||||
? formatDateSameWeek(channel.lastMessageAt)
|
? formatDateSameWeek(channel.lastMessageAt!)
|
||||||
: formatDate(channel.lastMessageAt),
|
: formatDate(channel.lastMessageAt!),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
color: CupertinoColors.systemGrey,
|
color: CupertinoColors.systemGrey,
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import 'package:flutter/cupertino.dart';
|
|||||||
|
|
||||||
class ChatBubble extends CustomPainter {
|
class ChatBubble extends CustomPainter {
|
||||||
final Color color;
|
final Color color;
|
||||||
final Alignment alignment;
|
final Alignment? alignment;
|
||||||
|
|
||||||
ChatBubble({
|
ChatBubble({
|
||||||
@required this.color,
|
required this.color,
|
||||||
this.alignment,
|
this.alignment,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,7 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart'
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart'
|
||||||
show
|
hide ChannelListView;
|
||||||
Channel,
|
|
||||||
ChannelListController,
|
|
||||||
ChannelListCore,
|
|
||||||
ChannelsBloc,
|
|
||||||
LazyLoadScrollView,
|
|
||||||
Level,
|
|
||||||
PaginationParams,
|
|
||||||
SortOption,
|
|
||||||
StreamChatClient,
|
|
||||||
StreamChatCore,
|
|
||||||
User;
|
|
||||||
|
|
||||||
import 'package:imessage/channel_list_view.dart';
|
import 'package:imessage/channel_list_view.dart';
|
||||||
|
|
||||||
@@ -37,7 +26,7 @@ Future<void> main() async {
|
|||||||
|
|
||||||
class IMessage extends StatelessWidget {
|
class IMessage extends StatelessWidget {
|
||||||
final StreamChatClient client;
|
final StreamChatClient client;
|
||||||
IMessage({@required this.client});
|
IMessage({required this.client});
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
initializeDateFormatting('en_US', null);
|
initializeDateFormatting('en_US', null);
|
||||||
@@ -52,70 +41,69 @@ class IMessage extends StatelessWidget {
|
|||||||
|
|
||||||
class ChatLoader extends StatelessWidget {
|
class ChatLoader extends StatelessWidget {
|
||||||
ChatLoader({
|
ChatLoader({
|
||||||
Key key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final channelListController = ChannelListController();
|
final channelListController = ChannelListController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final user = StreamChatCore.of(context).user;
|
final user = StreamChatCore.of(context).user!;
|
||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
child: ChannelsBloc(
|
child: ChannelsBloc(
|
||||||
child: ChannelListCore(
|
child: ChannelListCore(
|
||||||
channelListController: channelListController,
|
channelListController: channelListController,
|
||||||
filter: {
|
filter: Filter.and([
|
||||||
'members': {
|
Filter.in_('members', [user.id]),
|
||||||
r'$in': [user.id],
|
Filter.equal('type', 'messaging'),
|
||||||
},
|
]),
|
||||||
'type': {
|
sort: [SortOption('last_message_at')],
|
||||||
r'$eq': 'messaging',
|
pagination: PaginationParams(
|
||||||
},
|
limit: 20,
|
||||||
},
|
),
|
||||||
sort: [SortOption('last_message_at')],
|
emptyBuilder: (BuildContext context) {
|
||||||
pagination: PaginationParams(
|
return Center(
|
||||||
limit: 20,
|
child: Text('Looks like you are not in any channels'),
|
||||||
),
|
);
|
||||||
emptyBuilder: (BuildContext context) {
|
},
|
||||||
return Center(
|
loadingBuilder: (BuildContext context) {
|
||||||
child: Text('Looks like you are not in any channels'),
|
return Center(
|
||||||
);
|
child: SizedBox(
|
||||||
},
|
height: 100.0,
|
||||||
loadingBuilder: (BuildContext context) {
|
width: 100.0,
|
||||||
return Center(
|
child: CupertinoActivityIndicator(),
|
||||||
child: SizedBox(
|
),
|
||||||
height: 100.0,
|
);
|
||||||
width: 100.0,
|
},
|
||||||
child: CupertinoActivityIndicator(),
|
errorBuilder: (BuildContext context, dynamic error) {
|
||||||
),
|
return Center(
|
||||||
);
|
child: Text(
|
||||||
},
|
'Oh no, something went wrong. Please check your config.'),
|
||||||
errorBuilder: (BuildContext context, dynamic error) {
|
);
|
||||||
return Center(
|
},
|
||||||
child: Text(
|
listBuilder: (
|
||||||
'Oh no, something went wrong. Please check your config.'),
|
BuildContext context,
|
||||||
);
|
List<Channel> channels,
|
||||||
},
|
) =>
|
||||||
listBuilder: (
|
LazyLoadScrollView(
|
||||||
BuildContext context,
|
onEndOfPage: () async {
|
||||||
List<Channel> channels,
|
return channelListController.paginateData!();
|
||||||
) =>
|
},
|
||||||
LazyLoadScrollView(
|
child: CustomScrollView(
|
||||||
onEndOfPage: () async {
|
slivers: [
|
||||||
channelListController.paginateData();
|
CupertinoSliverRefreshControl(onRefresh: () async {
|
||||||
},
|
return channelListController.loadData!();
|
||||||
child: CustomScrollView(
|
}),
|
||||||
slivers: [
|
ChannelPageAppBar(),
|
||||||
CupertinoSliverRefreshControl(onRefresh: () async {
|
SliverPadding(
|
||||||
channelListController.loadData();
|
sliver: ChannelListView(channels: channels),
|
||||||
}),
|
padding: const EdgeInsets.only(top: 16),
|
||||||
ChannelPageAppBar(),
|
)
|
||||||
SliverPadding(
|
],
|
||||||
sliver: ChannelListView(channels: channels),
|
),
|
||||||
padding: const EdgeInsets.only(top: 16),
|
),
|
||||||
)
|
),
|
||||||
],
|
),
|
||||||
),
|
);
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:imessage/utils.dart';
|
|||||||
|
|
||||||
class MessageHeader extends StatelessWidget {
|
class MessageHeader extends StatelessWidget {
|
||||||
final String rawTimeStamp;
|
final String rawTimeStamp;
|
||||||
const MessageHeader({Key key, @required this.rawTimeStamp}) : super(key: key);
|
const MessageHeader({Key? key, required this.rawTimeStamp}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import 'dart:io';
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart'
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart'
|
||||||
show Attachment, AttachmentFile, Message, MultipartFile, StreamChannel;
|
show Attachment, AttachmentFile, Message, StreamChannel;
|
||||||
|
|
||||||
class MessageInput extends StatefulWidget {
|
class MessageInput extends StatefulWidget {
|
||||||
const MessageInput({
|
const MessageInput({
|
||||||
Key key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -16,7 +16,6 @@ class MessageInput extends StatefulWidget {
|
|||||||
|
|
||||||
class _MessageInputState extends State<MessageInput> {
|
class _MessageInputState extends State<MessageInput> {
|
||||||
final textController = TextEditingController();
|
final textController = TextEditingController();
|
||||||
File _image;
|
|
||||||
final picker = ImagePicker();
|
final picker = ImagePicker();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -39,16 +38,25 @@ class _MessageInputState extends State<MessageInput> {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final pickedFile =
|
final pickedFile =
|
||||||
await picker.getImage(source: ImageSource.gallery);
|
await (picker.getImage(source: ImageSource.gallery));
|
||||||
|
if (pickedFile == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final bytes = await File(pickedFile.path).readAsBytes();
|
final bytes = await File(pickedFile.path).readAsBytes();
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
final message =
|
final message = Message(
|
||||||
Message(text: textController.value.text, attachments: [
|
text: textController.value.text,
|
||||||
Attachment(
|
attachments: [
|
||||||
type: 'image',
|
Attachment(
|
||||||
file: AttachmentFile(bytes: bytes, path: pickedFile.path),
|
type: 'image',
|
||||||
),
|
file: AttachmentFile(
|
||||||
]);
|
bytes: bytes,
|
||||||
|
path: pickedFile.path,
|
||||||
|
size: bytes.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
await channel.sendMessage(message);
|
await channel.sendMessage(message);
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -68,10 +76,11 @@ class _MessageInputState extends State<MessageInput> {
|
|||||||
},
|
},
|
||||||
placeholder: 'Text Message',
|
placeholder: 'Text Message',
|
||||||
prefix: Padding(
|
prefix: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
"") //trick to add padding around placeholder iMessage text
|
'',
|
||||||
),
|
), //trick to add padding around placeholder iMessage text
|
||||||
|
),
|
||||||
suffix: GestureDetector(
|
suffix: GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (textController.value.text.isNotEmpty) {
|
if (textController.value.text.isNotEmpty) {
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'
|
|||||||
show Message, StreamChatCore;
|
show Message, StreamChatCore;
|
||||||
|
|
||||||
class MessageListView extends StatelessWidget {
|
class MessageListView extends StatelessWidget {
|
||||||
const MessageListView({Key key, this.messages}) : super(key: key);
|
const MessageListView({Key? key, this.messages}) : super(key: key);
|
||||||
final List<Message> messages;
|
final List<Message>? messages;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final entries = groupBy(messages,
|
final entries = groupBy(messages!,
|
||||||
(Message message) => message.createdAt.toString().substring(0, 10))
|
(Message message) => message.createdAt.toString().substring(0, 10))
|
||||||
.entries
|
.entries
|
||||||
.toList();
|
.toList();
|
||||||
@@ -64,8 +64,8 @@ class MessageListView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isReceived(Message message, BuildContext context) {
|
bool isReceived(Message message, BuildContext context) {
|
||||||
final currentUserId = StreamChatCore.of(context).user.id;
|
final currentUserId = StreamChatCore.of(context).user!.id;
|
||||||
return message.user.id == currentUserId;
|
return message.user!.id == currentUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSameDay(Message message) =>
|
bool isSameDay(Message message) =>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class MessagePage extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
messageListBuilder: (context, messages) => LazyLoadScrollView(
|
messageListBuilder: (context, messages) => LazyLoadScrollView(
|
||||||
onStartOfPage: () async {
|
onStartOfPage: () async {
|
||||||
messageListController.paginateData();
|
await messageListController.paginateData!();
|
||||||
},
|
},
|
||||||
child: MessageListView(
|
child: MessageListView(
|
||||||
messages: messages,
|
messages: messages,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:imessage/cutom_painter.dart';
|
import 'package:imessage/cutom_painter.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart'
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart' show Message;
|
||||||
show Message, AttachmentUploadStateBuilder;
|
|
||||||
|
|
||||||
class MessageWidget extends StatelessWidget {
|
class MessageWidget extends StatelessWidget {
|
||||||
final Alignment alignment;
|
final Alignment alignment;
|
||||||
@@ -11,16 +10,16 @@ class MessageWidget extends StatelessWidget {
|
|||||||
final Color messageColor;
|
final Color messageColor;
|
||||||
|
|
||||||
const MessageWidget(
|
const MessageWidget(
|
||||||
{Key key,
|
{Key? key,
|
||||||
@required this.alignment,
|
required this.alignment,
|
||||||
@required this.message,
|
required this.message,
|
||||||
@required this.color,
|
required this.color,
|
||||||
@required this.messageColor})
|
required this.messageColor})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (message.attachments?.isNotEmpty == true &&
|
if (message.attachments.isNotEmpty == true &&
|
||||||
message.attachments.first.type == 'image') {
|
message.attachments.first.type == 'image') {
|
||||||
return MessageImage(
|
return MessageImage(
|
||||||
color: color, message: message, messageColor: messageColor);
|
color: color, message: message, messageColor: messageColor);
|
||||||
@@ -36,10 +35,10 @@ class MessageWidget extends StatelessWidget {
|
|||||||
|
|
||||||
class MessageImage extends StatelessWidget {
|
class MessageImage extends StatelessWidget {
|
||||||
const MessageImage({
|
const MessageImage({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.color,
|
required this.color,
|
||||||
@required this.message,
|
required this.message,
|
||||||
@required this.messageColor,
|
required this.messageColor,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final Color color;
|
final Color color;
|
||||||
@@ -61,23 +60,23 @@ class MessageImage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (message.attachments.first.file != null)
|
if (message.attachments.first.file != null)
|
||||||
Image.memory(
|
Image.memory(
|
||||||
message.attachments.first.file.bytes,
|
message.attachments.first.file!.bytes!,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
CachedNetworkImage(
|
CachedNetworkImage(
|
||||||
imageUrl: message.attachments.first.thumbUrl ??
|
imageUrl: message.attachments.first.thumbUrl ??
|
||||||
message.attachments.first.imageUrl ??
|
message.attachments.first.imageUrl ??
|
||||||
message.attachments.first.assetUrl,
|
message.attachments.first.assetUrl!,
|
||||||
),
|
),
|
||||||
if (message.attachments.first?.title != null)
|
if (message.attachments.first.title != null)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Text(message.attachments.first.title,
|
child: Text(message.attachments.first.title!,
|
||||||
style: TextStyle(color: messageColor)),
|
style: TextStyle(color: messageColor)),
|
||||||
),
|
),
|
||||||
message.attachments.first.pretext != null
|
message.attachments.first.pretext != null
|
||||||
? Text(message.attachments.first.pretext)
|
? Text(message.attachments.first.pretext!)
|
||||||
: Container()
|
: Container()
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -92,7 +91,7 @@ class MessageImage extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
color: color,
|
color: color,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: message.attachments.first.thumbUrl,
|
imageUrl: message.attachments.first.thumbUrl!,
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -101,11 +100,11 @@ class MessageImage extends StatelessWidget {
|
|||||||
|
|
||||||
class MessageText extends StatelessWidget {
|
class MessageText extends StatelessWidget {
|
||||||
const MessageText({
|
const MessageText({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.alignment,
|
required this.alignment,
|
||||||
@required this.color,
|
required this.color,
|
||||||
@required this.message,
|
required this.message,
|
||||||
@required this.messageColor,
|
required this.messageColor,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final Alignment alignment;
|
final Alignment alignment;
|
||||||
@@ -133,7 +132,7 @@ class MessageText extends StatelessWidget {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(4.0),
|
padding: const EdgeInsets.all(4.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
message.text,
|
message.text!,
|
||||||
style: TextStyle(color: messageColor),
|
style: TextStyle(color: messageColor),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -26,44 +26,45 @@ bool isSameWeek(DateTime timestamp) =>
|
|||||||
DateTime.now().difference(timestamp).inDays < 7;
|
DateTime.now().difference(timestamp).inDays < 7;
|
||||||
|
|
||||||
class CupertinoCircleAvatar extends StatelessWidget {
|
class CupertinoCircleAvatar extends StatelessWidget {
|
||||||
final String url;
|
final String? url;
|
||||||
final double size;
|
final double? size;
|
||||||
const CupertinoCircleAvatar({Key key, this.url, this.size}) : super(key: key);
|
const CupertinoCircleAvatar({Key? key, this.url, this.size})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(size / 2),
|
borderRadius: BorderRadius.circular(size! / 2),
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: url,
|
imageUrl: url!,
|
||||||
height: size,
|
height: size,
|
||||||
width: size,
|
width: size,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorWidget: (context, url, error) {
|
errorWidget: (context, url, error) {
|
||||||
//TODO: this crash the app when getting 404 and in debug mode, see :https://github.com/Baseflow/flutter_cached_network_image/issues/504
|
//TODO: this crash the app when getting 404 and in debug mode, see :https://github.com/Baseflow/flutter_cached_network_image/issues/504
|
||||||
return CachedNetworkImage(
|
return CachedNetworkImage(
|
||||||
imageUrl:
|
imageUrl:
|
||||||
"https://4.bp.blogspot.com/-Jx21kNqFSTU/UXemtqPhZCI/AAAAAAAAh74/BMGSzpU6F48/s1600/funny-cat-pictures-047-001.jpg");
|
'https://4.bp.blogspot.com/-Jx21kNqFSTU/UXemtqPhZCI/AAAAAAAAh74/BMGSzpU6F48/s1600/funny-cat-pictures-047-001.jp',
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Divider extends StatelessWidget {
|
class Divider extends StatelessWidget {
|
||||||
const Divider({
|
const Divider({
|
||||||
Key key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Align(
|
child: Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: CupertinoColors.systemGrey5,
|
color: CupertinoColors.systemGrey5,
|
||||||
),
|
),
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,16 +18,16 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.7.0 <3.0.0"
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
intl: ^0.16.1
|
intl: ^0.17.0
|
||||||
stream_chat_flutter: ^1.3.0-beta
|
stream_chat_flutter: ^2.0.0-nullsafety.3
|
||||||
animations: ^1.0.0+5
|
animations: ^2.0.0
|
||||||
collection: ^1.14.13
|
collection: ^1.15.0
|
||||||
cached_network_image: ^2.0.0-rc
|
cached_network_image: ^3.0.0
|
||||||
|
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
||||||
|
|||||||
@@ -2,21 +2,21 @@ GEM
|
|||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.3)
|
CFPropertyList (3.0.3)
|
||||||
addressable (2.7.0)
|
addressable (2.8.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
artifactory (3.0.15)
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.1.1)
|
aws-eventstream (1.1.1)
|
||||||
aws-partitions (1.437.0)
|
aws-partitions (1.473.0)
|
||||||
aws-sdk-core (3.113.1)
|
aws-sdk-core (3.115.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.43.0)
|
aws-sdk-kms (1.44.0)
|
||||||
aws-sdk-core (~> 3, >= 3.112.0)
|
aws-sdk-core (~> 3, >= 3.112.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.93.0)
|
aws-sdk-s3 (1.96.1)
|
||||||
aws-sdk-core (~> 3, >= 3.112.0)
|
aws-sdk-core (~> 3, >= 3.112.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
@@ -26,29 +26,40 @@ GEM
|
|||||||
claide (1.0.3)
|
claide (1.0.3)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
commander-fastlane (4.4.6)
|
commander (4.6.0)
|
||||||
highline (~> 1.7.2)
|
highline (~> 2.0.0)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
declarative-option (0.1.0)
|
|
||||||
digest-crc (0.6.3)
|
digest-crc (0.6.3)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.7.6)
|
dotenv (2.7.6)
|
||||||
emoji_regex (3.2.2)
|
emoji_regex (3.2.2)
|
||||||
excon (0.79.0)
|
excon (0.83.0)
|
||||||
faraday (1.3.0)
|
faraday (1.5.0)
|
||||||
|
faraday-em_http (~> 1.0)
|
||||||
|
faraday-em_synchrony (~> 1.0)
|
||||||
|
faraday-excon (~> 1.1)
|
||||||
|
faraday-httpclient (~> 1.0.1)
|
||||||
faraday-net_http (~> 1.0)
|
faraday-net_http (~> 1.0)
|
||||||
|
faraday-net_http_persistent (~> 1.1)
|
||||||
|
faraday-patron (~> 1.0)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
ruby2_keywords
|
ruby2_keywords (>= 0.0.4)
|
||||||
faraday-cookie_jar (0.0.7)
|
faraday-cookie_jar (0.0.7)
|
||||||
faraday (>= 0.8.0)
|
faraday (>= 0.8.0)
|
||||||
http-cookie (~> 1.0.0)
|
http-cookie (~> 1.0.0)
|
||||||
|
faraday-em_http (1.0.0)
|
||||||
|
faraday-em_synchrony (1.0.0)
|
||||||
|
faraday-excon (1.1.0)
|
||||||
|
faraday-httpclient (1.0.1)
|
||||||
faraday-net_http (1.0.1)
|
faraday-net_http (1.0.1)
|
||||||
|
faraday-net_http_persistent (1.1.0)
|
||||||
|
faraday-patron (1.0.0)
|
||||||
faraday_middleware (1.0.0)
|
faraday_middleware (1.0.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.2.3)
|
fastimage (2.2.4)
|
||||||
fastlane (2.179.0)
|
fastlane (2.187.0)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.3, < 3.0.0)
|
addressable (>= 2.3, < 3.0.0)
|
||||||
artifactory (~> 3.0)
|
artifactory (~> 3.0)
|
||||||
@@ -56,7 +67,7 @@ GEM
|
|||||||
babosa (>= 1.0.3, < 2.0.0)
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
colored
|
colored
|
||||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
commander (~> 4.6)
|
||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
emoji_regex (>= 0.1, < 4.0)
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
excon (>= 0.71.0, < 1.0.0)
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
@@ -65,9 +76,10 @@ GEM
|
|||||||
faraday_middleware (~> 1.0)
|
faraday_middleware (~> 1.0)
|
||||||
fastimage (>= 2.1.0, < 3.0.0)
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
google-api-client (>= 0.37.0, < 0.39.0)
|
google-apis-androidpublisher_v3 (~> 0.1)
|
||||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
highline (>= 1.7.2, < 2.0.0)
|
google-cloud-storage (~> 1.31)
|
||||||
|
highline (~> 2.0)
|
||||||
json (< 3.0.0)
|
json (< 3.0.0)
|
||||||
jwt (>= 2.1.0, < 3)
|
jwt (>= 2.1.0, < 3)
|
||||||
mini_magick (>= 4.9.4, < 5.0.0)
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
@@ -77,7 +89,6 @@ GEM
|
|||||||
rubyzip (>= 2.0.0, < 3.0.0)
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
security (= 0.1.3)
|
security (= 0.1.3)
|
||||||
simctl (~> 1.6.3)
|
simctl (~> 1.6.3)
|
||||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
|
||||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
terminal-table (>= 1.4.5, < 2.0.0)
|
terminal-table (>= 1.4.5, < 2.0.0)
|
||||||
tty-screen (>= 0.6.3, < 1.0.0)
|
tty-screen (>= 0.6.3, < 1.0.0)
|
||||||
@@ -86,61 +97,56 @@ GEM
|
|||||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||||
xcpretty (~> 0.3.0)
|
xcpretty (~> 0.3.0)
|
||||||
xcpretty-travis-formatter (>= 0.0.3)
|
xcpretty-travis-formatter (>= 0.0.3)
|
||||||
fastlane-plugin-firebase_app_distribution (0.2.3)
|
fastlane-plugin-firebase_app_distribution (0.2.9)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-api-client (0.38.0)
|
google-apis-androidpublisher_v3 (0.8.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-apis-core (0.4.0)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (~> 0.9)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
httpclient (>= 2.8.1, < 3.0)
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.0)
|
retriable (>= 2.0, < 4.a)
|
||||||
signet (~> 0.12)
|
|
||||||
google-apis-core (0.3.0)
|
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
|
||||||
googleauth (~> 0.14)
|
|
||||||
httpclient (>= 2.8.1, < 3.0)
|
|
||||||
mini_mime (~> 1.0)
|
|
||||||
representable (~> 3.0)
|
|
||||||
retriable (>= 2.0, < 4.0)
|
|
||||||
rexml
|
rexml
|
||||||
signet (~> 0.14)
|
|
||||||
webrick
|
webrick
|
||||||
google-apis-iamcredentials_v1 (0.2.0)
|
google-apis-iamcredentials_v1 (0.6.0)
|
||||||
google-apis-core (~> 0.1)
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
google-apis-storage_v1 (0.3.0)
|
google-apis-playcustomapp_v1 (0.5.0)
|
||||||
google-apis-core (~> 0.1)
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-apis-storage_v1 (0.6.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
google-cloud-core (1.6.0)
|
google-cloud-core (1.6.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.5.0)
|
google-cloud-env (1.5.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
google-cloud-errors (1.1.0)
|
google-cloud-errors (1.1.0)
|
||||||
google-cloud-storage (1.31.0)
|
google-cloud-storage (1.34.0)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-apis-iamcredentials_v1 (~> 0.1)
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
google-apis-storage_v1 (~> 0.1)
|
google-apis-storage_v1 (~> 0.1)
|
||||||
google-cloud-core (~> 1.2)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (~> 0.9)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (0.16.0)
|
googleauth (0.16.2)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (~> 0.14)
|
signet (~> 0.14)
|
||||||
highline (1.7.10)
|
highline (2.0.3)
|
||||||
http-cookie (1.0.3)
|
http-cookie (1.0.4)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
json (2.5.1)
|
json (2.5.1)
|
||||||
jwt (2.2.2)
|
jwt (2.2.3)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mini_magick (4.11.0)
|
mini_magick (4.11.0)
|
||||||
mini_mime (1.0.3)
|
mini_mime (1.1.0)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
nanaimo (0.3.0)
|
nanaimo (0.3.0)
|
||||||
@@ -148,16 +154,16 @@ GEM
|
|||||||
os (1.1.1)
|
os (1.1.1)
|
||||||
plist (3.6.0)
|
plist (3.6.0)
|
||||||
public_suffix (4.0.6)
|
public_suffix (4.0.6)
|
||||||
rake (13.0.3)
|
rake (13.0.4)
|
||||||
representable (3.0.4)
|
representable (3.1.1)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
declarative-option (< 0.2.0)
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rexml (3.2.4)
|
rexml (3.2.5)
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
ruby2_keywords (0.0.4)
|
ruby2_keywords (0.0.4)
|
||||||
rubyzip (2.3.0)
|
rubyzip (2.3.2)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.15.0)
|
signet (0.15.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
@@ -167,10 +173,10 @@ GEM
|
|||||||
simctl (1.6.8)
|
simctl (1.6.8)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
slack-notifier (2.3.2)
|
|
||||||
terminal-notifier (2.0.0)
|
terminal-notifier (2.0.0)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
|
trailblazer-option (0.1.1)
|
||||||
tty-cursor (0.7.1)
|
tty-cursor (0.7.1)
|
||||||
tty-screen (0.8.1)
|
tty-screen (0.8.1)
|
||||||
tty-spinner (0.9.3)
|
tty-spinner (0.9.3)
|
||||||
@@ -182,12 +188,13 @@ GEM
|
|||||||
unicode-display_width (1.7.0)
|
unicode-display_width (1.7.0)
|
||||||
webrick (1.7.0)
|
webrick (1.7.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.19.0)
|
xcodeproj (1.20.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
nanaimo (~> 0.3.0)
|
nanaimo (~> 0.3.0)
|
||||||
|
rexml (~> 3.2.4)
|
||||||
xcpretty (0.3.0)
|
xcpretty (0.3.0)
|
||||||
rouge (~> 2.0.7)
|
rouge (~> 2.0.7)
|
||||||
xcpretty-travis-formatter (1.0.1)
|
xcpretty-travis-formatter (1.0.1)
|
||||||
|
|||||||
@@ -245,8 +245,10 @@
|
|||||||
"${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}/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}/file_picker/file_picker.framework",
|
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/flutter_app_badger/flutter_app_badger.framework",
|
"${BUILT_PRODUCTS_DIR}/flutter_app_badger/flutter_app_badger.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/flutter_keyboard_visibility/flutter_keyboard_visibility.framework",
|
"${BUILT_PRODUCTS_DIR}/flutter_keyboard_visibility/flutter_keyboard_visibility.framework",
|
||||||
@@ -273,8 +275,10 @@
|
|||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework",
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_app_badger.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_app_badger.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_keyboard_visibility.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_keyboard_visibility.framework",
|
||||||
@@ -447,7 +451,7 @@
|
|||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter;
|
PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "match AdHoc io.getstream.flutter";
|
PROVISIONING_PROFILE_SPECIFIER = "match AdHoc io.getstream.flutter 1620032657";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
@@ -586,7 +590,7 @@
|
|||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter;
|
PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "match AdHoc io.getstream.flutter";
|
PROVISIONING_PROFILE_SPECIFIER = "match AdHoc io.getstream.flutter 1620032657";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -620,7 +624,7 @@
|
|||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter;
|
PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "match AdHoc io.getstream.flutter";
|
PROVISIONING_PROFILE_SPECIFIER = "match AdHoc io.getstream.flutter 1620032657";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
fastlane_version "2.179.0"
|
fastlane_version "2.187.0"
|
||||||
default_platform :ios
|
default_platform :ios
|
||||||
|
|
||||||
before_all do
|
before_all do
|
||||||
@@ -62,7 +62,7 @@ platform :ios do
|
|||||||
|
|
||||||
settings_to_override = {
|
settings_to_override = {
|
||||||
:BUNDLE_IDENTIFIER => "io.getstream.flutter",
|
:BUNDLE_IDENTIFIER => "io.getstream.flutter",
|
||||||
:PROVISIONING_PROFILE_SPECIFIER => "match AppStore io.getstream.flutter"
|
:PROVISIONING_PROFILE_SPECIFIER => "match AppStore io.getstream.flutter 1620032657"
|
||||||
}
|
}
|
||||||
|
|
||||||
gym(
|
gym(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<key>provisioningProfiles</key>
|
<key>provisioningProfiles</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>io.getstream.flutter</key>
|
<key>io.getstream.flutter</key>
|
||||||
<string>match AdHoc io.getstream.flutter</string>
|
<string>match AdHoc io.getstream.flutter 1620032657</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="0: Verifying fastlane version" time="0.000404">
|
<testcase classname="fastlane.lanes" name="0: Verifying fastlane version" time="0.000512">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: default_platform" time="0.000191">
|
<testcase classname="fastlane.lanes" name="1: default_platform" time="0.00019">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
@@ -20,24 +20,22 @@
|
|||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="3: Switch to ios match_appstore lane" time="0.000191">
|
<testcase classname="fastlane.lanes" name="3: Switch to ios match_me lane" time="0.000207">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="4: is_ci" time="0.000169">
|
<testcase classname="fastlane.lanes" name="4: is_ci" time="0.000177">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="5: match" time="4.754179">
|
<testcase classname="fastlane.lanes" name="5: match" time="8.690712">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="6: gym" time="17.780547">
|
<testcase classname="fastlane.lanes" name="6: gym" time="23.786307">
|
||||||
|
|
||||||
<failure message="/Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/actions/actions_helper.rb:67:in `execute_action' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/runner.rb:255:in `block in execute_action' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/runner.rb:229:in `chdir' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/runner.rb:229:in `execute_action' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/runner.rb:157:in `trigger_action_by_name' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/fast_file.rb:159:in `method_missing' Fastfile:68:in `block (2 levels) in parsing_binding' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/lane.rb:33:in `call' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/runner.rb:49:in `block in execute' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/runner.rb:45:in `chdir' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/runner.rb:45:in `execute' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/lane_manager.rb:47:in `cruise_lane' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/command_line_handler.rb:36:in `handle' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/commands_generator.rb:108:in `block (2 levels) in run' /Library/Ruby/Gems/2.6.0/gems/commander-fastlane-4.4.6/lib/commander/command.rb:178:in `call' /Library/Ruby/Gems/2.6.0/gems/commander-fastlane-4.4.6/lib/commander/command.rb:153:in `run' /Library/Ruby/Gems/2.6.0/gems/commander-fastlane-4.4.6/lib/commander/runner.rb:476:in `run_active_command' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:76:in `run!' /Library/Ruby/Gems/2.6.0/gems/commander-fastlane-4.4.6/lib/commander/delegates.rb:15:in `run!' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/commands_generator.rb:352:in `run' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/commands_generator.rb:41:in `start' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/fastlane/lib/fastlane/cli_tools_distributor.rb:119:in `take_off' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.171.0/bin/fastlane:23:in `<top (required)>' /usr/local/bin/fastlane:23:in `load' /usr/local/bin/fastlane:23:in `<main>' Error building the application - see the log above" />
|
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:example/home_page.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';
|
||||||
@@ -17,13 +18,13 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
final TextEditingController _apiKeyController = TextEditingController();
|
final TextEditingController _apiKeyController = TextEditingController();
|
||||||
String _apiKeyError;
|
String? _apiKeyError;
|
||||||
|
|
||||||
final TextEditingController _userIdController = TextEditingController();
|
final TextEditingController _userIdController = TextEditingController();
|
||||||
String _userIdError;
|
String? _userIdError;
|
||||||
|
|
||||||
final TextEditingController _userTokenController = TextEditingController();
|
final TextEditingController _userTokenController = TextEditingController();
|
||||||
String _userTokenError;
|
String? _userTokenError;
|
||||||
|
|
||||||
final TextEditingController _usernameController = TextEditingController();
|
final TextEditingController _usernameController = TextEditingController();
|
||||||
|
|
||||||
@@ -32,22 +33,20 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
brightness: Theme.of(context).brightness,
|
brightness: Theme.of(context).brightness,
|
||||||
title: Text(
|
title: Text(
|
||||||
'Advanced Options',
|
'Advanced Options',
|
||||||
style: StreamChatTheme.of(context)
|
style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith(
|
||||||
.textTheme
|
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis),
|
||||||
.headlineBold
|
|
||||||
.copyWith(color: StreamChatTheme.of(context).colorTheme.black),
|
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: StreamSvgIcon.left(
|
icon: StreamSvgIcon.left(
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -73,7 +72,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value.isEmpty) {
|
if (value!.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_apiKeyError =
|
_apiKeyError =
|
||||||
'Please enter the Chat API Key'.toUpperCase();
|
'Please enter the Chat API Key'.toUpperCase();
|
||||||
@@ -84,7 +83,9 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
},
|
},
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis,
|
||||||
),
|
),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
errorStyle: TextStyle(height: 0, fontSize: 0),
|
errorStyle: TextStyle(height: 0, fontSize: 0),
|
||||||
@@ -92,15 +93,16 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: _apiKeyError != null
|
color: _apiKeyError != null
|
||||||
? StreamChatTheme.of(context).colorTheme.accentRed
|
? StreamChatTheme.of(context).colorTheme.accentError
|
||||||
: StreamChatTheme.of(context).colorTheme.grey,
|
: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
border: UnderlineInputBorder(
|
border: UnderlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
fillColor:
|
fillColor: StreamChatTheme.of(context).colorTheme.inputBg,
|
||||||
StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
|
||||||
filled: true,
|
filled: true,
|
||||||
labelText: _apiKeyError != null
|
labelText: _apiKeyError != null
|
||||||
? 'CHAT API KEY: $_apiKeyError'
|
? 'CHAT API KEY: $_apiKeyError'
|
||||||
@@ -119,7 +121,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value.isEmpty) {
|
if (value!.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_userIdError =
|
_userIdError =
|
||||||
'Please enter the User ID'.toUpperCase();
|
'Please enter the User ID'.toUpperCase();
|
||||||
@@ -130,7 +132,9 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
},
|
},
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis,
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -139,15 +143,16 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: _userIdError != null
|
color: _userIdError != null
|
||||||
? StreamChatTheme.of(context).colorTheme.accentRed
|
? StreamChatTheme.of(context).colorTheme.accentError
|
||||||
: StreamChatTheme.of(context).colorTheme.grey,
|
: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
border: UnderlineInputBorder(
|
border: UnderlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
fillColor:
|
fillColor: StreamChatTheme.of(context).colorTheme.inputBg,
|
||||||
StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
|
||||||
filled: true,
|
filled: true,
|
||||||
labelText: _userIdError != null
|
labelText: _userIdError != null
|
||||||
? 'USER ID: $_userIdError'
|
? 'USER ID: $_userIdError'
|
||||||
@@ -165,7 +170,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
},
|
},
|
||||||
controller: _userTokenController,
|
controller: _userTokenController,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value.isEmpty) {
|
if (value!.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_userTokenError =
|
_userTokenError =
|
||||||
'Please enter the user token'.toUpperCase();
|
'Please enter the user token'.toUpperCase();
|
||||||
@@ -176,7 +181,9 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
},
|
},
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis,
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -185,15 +192,16 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: _userTokenError != null
|
color: _userTokenError != null
|
||||||
? StreamChatTheme.of(context).colorTheme.accentRed
|
? StreamChatTheme.of(context).colorTheme.accentError
|
||||||
: StreamChatTheme.of(context).colorTheme.grey,
|
: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
border: UnderlineInputBorder(
|
border: UnderlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
fillColor:
|
fillColor: StreamChatTheme.of(context).colorTheme.inputBg,
|
||||||
StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
|
||||||
filled: true,
|
filled: true,
|
||||||
labelText: _userTokenError != null
|
labelText: _userTokenError != null
|
||||||
? 'USER TOKEN: $_userTokenError'
|
? 'USER TOKEN: $_userTokenError'
|
||||||
@@ -208,34 +216,45 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
border: UnderlineInputBorder(
|
border: UnderlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
fillColor:
|
fillColor: StreamChatTheme.of(context).colorTheme.inputBg,
|
||||||
StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
|
||||||
filled: true,
|
filled: true,
|
||||||
labelText: 'Username (optional)',
|
labelText: 'Username (optional)',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
RaisedButton(
|
ElevatedButton(
|
||||||
color: Theme.of(context).brightness == Brightness.light
|
style: ButtonStyle(
|
||||||
? StreamChatTheme.of(context).colorTheme.accentBlue
|
backgroundColor: MaterialStateProperty.all<Color>(
|
||||||
: Colors.white,
|
Theme.of(context).brightness == Brightness.light
|
||||||
elevation: 0,
|
? StreamChatTheme.of(context)
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
.colorTheme
|
||||||
shape: RoundedRectangleBorder(
|
.accentPrimary
|
||||||
borderRadius: BorderRadius.circular(26),
|
: Colors.white),
|
||||||
|
elevation: MaterialStateProperty.all<double>(0),
|
||||||
|
padding: MaterialStateProperty.all<EdgeInsets>(
|
||||||
|
const EdgeInsets.symmetric(vertical: 16)),
|
||||||
|
shape: MaterialStateProperty.all(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(26),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Login',
|
'Login',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: Theme.of(context).brightness != Brightness.light
|
color: Theme.of(context).brightness != Brightness.light
|
||||||
? StreamChatTheme.of(context).colorTheme.accentBlue
|
? StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentPrimary
|
||||||
: Colors.white,
|
: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -243,7 +262,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_formKey.currentState.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
final apiKey = _apiKeyController.text;
|
final apiKey = _apiKeyController.text;
|
||||||
final userId = _userIdController.text;
|
final userId = _userIdController.text;
|
||||||
final userToken = _userTokenController.text;
|
final userToken = _userTokenController.text;
|
||||||
@@ -261,7 +280,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.white,
|
.barsBg,
|
||||||
),
|
),
|
||||||
height: 100,
|
height: 100,
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -298,7 +317,6 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
key: kStreamToken,
|
key: kStreamToken,
|
||||||
value: userToken,
|
value: userToken,
|
||||||
);
|
);
|
||||||
await client.disconnect();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
var errorText = 'Error connecting, retry';
|
var errorText = 'Error connecting, retry';
|
||||||
if (e is Map) {
|
if (e is Map) {
|
||||||
@@ -309,15 +327,14 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
_apiKeyError = errorText.toUpperCase();
|
_apiKeyError = errorText.toUpperCase();
|
||||||
});
|
});
|
||||||
loading = false;
|
loading = false;
|
||||||
await client.disconnect();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loading = false;
|
loading = false;
|
||||||
await Navigator.pushNamedAndRemoveUntil(
|
await Navigator.pushNamedAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
Routes.APP,
|
Routes.HOME,
|
||||||
ModalRoute.withName(Routes.APP),
|
ModalRoute.withName(Routes.HOME),
|
||||||
arguments: client,
|
arguments: HomePageArgs(client),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
class ChannelFileDisplayScreen extends StatefulWidget {
|
||||||
|
/// The sorting used for the channels matching the filters.
|
||||||
|
/// Sorting is based on field and direction, multiple sorting options can be provided.
|
||||||
|
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
|
||||||
|
/// Direction can be ascending or descending.
|
||||||
|
final List<SortOption>? sortOptions;
|
||||||
|
|
||||||
|
/// Pagination parameters
|
||||||
|
/// limit: the number of users to return (max is 30)
|
||||||
|
/// offset: the offset (max is 1000)
|
||||||
|
/// message_limit: how many messages should be included to each channel
|
||||||
|
final PaginationParams? paginationParams;
|
||||||
|
|
||||||
|
/// The builder used when the file list is empty.
|
||||||
|
final WidgetBuilder? emptyBuilder;
|
||||||
|
|
||||||
|
const ChannelFileDisplayScreen({
|
||||||
|
this.sortOptions,
|
||||||
|
this.paginationParams,
|
||||||
|
this.emptyBuilder,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChannelFileDisplayScreenState createState() =>
|
||||||
|
_ChannelFileDisplayScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
|
messageSearchBloc.search(
|
||||||
|
filter: Filter.in_(
|
||||||
|
'cid',
|
||||||
|
[StreamChannel.of(context).channel.cid!],
|
||||||
|
),
|
||||||
|
messageFilter: Filter.in_(
|
||||||
|
'attachments.type',
|
||||||
|
['file'],
|
||||||
|
),
|
||||||
|
sort: widget.sortOptions,
|
||||||
|
pagination: widget.paginationParams,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
|
appBar: AppBar(
|
||||||
|
brightness: Theme.of(context).brightness,
|
||||||
|
elevation: 1,
|
||||||
|
centerTitle: true,
|
||||||
|
title: Text(
|
||||||
|
'Files',
|
||||||
|
style: TextStyle(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
|
),
|
||||||
|
body: _buildMediaGrid(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMediaGrid() {
|
||||||
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
|
|
||||||
|
return StreamBuilder<List<GetMessageResponse>>(
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return Center(
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.data!.isEmpty) {
|
||||||
|
if (widget.emptyBuilder != null) {
|
||||||
|
return widget.emptyBuilder!(context);
|
||||||
|
}
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
StreamSvgIcon.files(
|
||||||
|
size: 136.0,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.0),
|
||||||
|
Text(
|
||||||
|
'No Files',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.0),
|
||||||
|
Text(
|
||||||
|
'Files sent in this chat will appear here',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final media = <Attachment, Message>{};
|
||||||
|
|
||||||
|
for (var item in snapshot.data!) {
|
||||||
|
item.message.attachments.where((e) => e.type == 'file').forEach((e) {
|
||||||
|
media[e] = item.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return LazyLoadScrollView(
|
||||||
|
onEndOfPage: () => messageSearchBloc.search(
|
||||||
|
filter: Filter.in_(
|
||||||
|
'cid',
|
||||||
|
[StreamChannel.of(context).channel.cid!],
|
||||||
|
),
|
||||||
|
messageFilter: Filter.in_(
|
||||||
|
'attachments.type',
|
||||||
|
['file'],
|
||||||
|
),
|
||||||
|
sort: widget.sortOptions,
|
||||||
|
pagination: widget.paginationParams!.copyWith(
|
||||||
|
offset: messageSearchBloc.messageResponses?.length ?? 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
stream: messageSearchBloc.messagesStream,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:example/routes/routes.dart';
|
||||||
|
import 'package:example/search_text_field.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
import 'channel_page.dart';
|
||||||
|
import 'chat_info_screen.dart';
|
||||||
|
import 'group_info_screen.dart';
|
||||||
|
|
||||||
|
class ChannelList extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_ChannelList createState() => _ChannelList();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelList extends State<ChannelList> {
|
||||||
|
TextEditingController? _controller;
|
||||||
|
|
||||||
|
String _channelQuery = '';
|
||||||
|
|
||||||
|
bool _isSearchActive = false;
|
||||||
|
|
||||||
|
Timer? _debounce;
|
||||||
|
|
||||||
|
void _channelQueryListener() {
|
||||||
|
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||||
|
_debounce = Timer(const Duration(milliseconds: 350), () {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_channelQuery = _controller!.text;
|
||||||
|
_isSearchActive = _channelQuery.isNotEmpty;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = TextEditingController()..addListener(_channelQueryListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller?.removeListener(_channelQueryListener);
|
||||||
|
_controller?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final user = StreamChat.of(context).user;
|
||||||
|
return WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
if (_isSearchActive) {
|
||||||
|
_controller!.clear();
|
||||||
|
setState(() => _isSearchActive = false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
child: NestedScrollView(
|
||||||
|
floatHeaderSlivers: true,
|
||||||
|
headerSliverBuilder: (_, __) => [
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: SearchTextField(
|
||||||
|
controller: _controller,
|
||||||
|
showCloseButton: _isSearchActive,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
body: AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 350),
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onPanDown: (_) => FocusScope.of(context).unfocus(),
|
||||||
|
child: _isSearchActive
|
||||||
|
? MessageSearchBloc(
|
||||||
|
child: MessageSearchListView(
|
||||||
|
showErrorTile: true,
|
||||||
|
messageQuery: _channelQuery,
|
||||||
|
filters: Filter.in_('members', [user!.id]),
|
||||||
|
sortOptions: [
|
||||||
|
SortOption(
|
||||||
|
'created_at',
|
||||||
|
direction: SortOption.ASC,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
pullToRefresh: false,
|
||||||
|
paginationParams: PaginationParams(limit: 20),
|
||||||
|
emptyBuilder: (_) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, viewportConstraints) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minHeight: viewportConstraints.maxHeight,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: StreamSvgIcon.search(
|
||||||
|
size: 96,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'No results...',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onItemTap: (messageResponse) async {
|
||||||
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
|
final client = StreamChat.of(context).client;
|
||||||
|
final message = messageResponse.message;
|
||||||
|
final channel = client.channel(
|
||||||
|
messageResponse.channel!.type,
|
||||||
|
id: messageResponse.channel!.id,
|
||||||
|
);
|
||||||
|
if (channel.state == null) {
|
||||||
|
await channel.watch();
|
||||||
|
}
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
Routes.CHANNEL_PAGE,
|
||||||
|
arguments: ChannelPageArgs(
|
||||||
|
channel: channel,
|
||||||
|
initialMessage: message,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ChannelsBloc(
|
||||||
|
child: ChannelListView(
|
||||||
|
onStartChatPressed: () {
|
||||||
|
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
||||||
|
},
|
||||||
|
swipeToAction: true,
|
||||||
|
filter: Filter.in_('members', [user!.id]),
|
||||||
|
presence: true,
|
||||||
|
pagination: PaginationParams(
|
||||||
|
limit: 20,
|
||||||
|
),
|
||||||
|
channelWidget: ChannelPage(),
|
||||||
|
onViewInfoTap: (channel) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (channel.memberCount == 2 && channel.isDistinct) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: ChatInfoScreen(
|
||||||
|
messageTheme: StreamChatTheme.of(context)
|
||||||
|
.ownMessageTheme,
|
||||||
|
user: channel.state!.members
|
||||||
|
.where((m) =>
|
||||||
|
m.userId !=
|
||||||
|
channel.client.state.user!.id)
|
||||||
|
.first
|
||||||
|
.user,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: GroupInfoScreen(
|
||||||
|
messageTheme: StreamChatTheme.of(context)
|
||||||
|
.ownMessageTheme,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:example/routes/routes.dart';
|
||||||
|
import 'package:example/user_mentions_page.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_app_badger/flutter_app_badger.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
|
||||||
|
|
||||||
|
import 'channel_list.dart';
|
||||||
|
|
||||||
|
class ChannelListPage extends StatefulWidget {
|
||||||
|
const ChannelListPage({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChannelListPageState createState() => _ChannelListPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelListPageState extends State<ChannelListPage> {
|
||||||
|
int _currentIndex = 0;
|
||||||
|
|
||||||
|
bool _isSelected(int index) => _currentIndex == index;
|
||||||
|
|
||||||
|
List<BottomNavigationBarItem> get _navBarItems {
|
||||||
|
return <BottomNavigationBarItem>[
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
StreamSvgIcon.message(
|
||||||
|
color: _isSelected(0)
|
||||||
|
? StreamChatTheme.of(context).colorTheme.textHighEmphasis
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: -3,
|
||||||
|
right: -16,
|
||||||
|
child: UnreadIndicator(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
label: 'Chats',
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
StreamSvgIcon.mentions(
|
||||||
|
color: _isSelected(1)
|
||||||
|
? StreamChatTheme.of(context).colorTheme.textHighEmphasis
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
label: 'Mentions',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final user = StreamChat.of(context).user;
|
||||||
|
if (user == null) {
|
||||||
|
return Offstage();
|
||||||
|
}
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
|
appBar: ChannelListHeader(
|
||||||
|
onNewChatButtonTap: () {
|
||||||
|
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
||||||
|
},
|
||||||
|
preNavigationCallback: () {
|
||||||
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
drawer: LeftDrawer(
|
||||||
|
user: user,
|
||||||
|
),
|
||||||
|
drawerEdgeDragWidth: 50,
|
||||||
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
|
currentIndex: _currentIndex,
|
||||||
|
items: _navBarItems,
|
||||||
|
selectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold,
|
||||||
|
unselectedLabelStyle:
|
||||||
|
StreamChatTheme.of(context).textTheme.footnoteBold,
|
||||||
|
type: BottomNavigationBarType.fixed,
|
||||||
|
selectedItemColor:
|
||||||
|
StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
|
unselectedItemColor: Colors.grey,
|
||||||
|
onTap: (index) {
|
||||||
|
setState(() => _currentIndex = index);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
body: IndexedStack(
|
||||||
|
index: _currentIndex,
|
||||||
|
children: [
|
||||||
|
ChannelList(),
|
||||||
|
UserMentionsPage(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription<int>? badgeListener;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
if (!kIsWeb) {
|
||||||
|
badgeListener = StreamChat.of(context)
|
||||||
|
.client
|
||||||
|
.state
|
||||||
|
.totalUnreadCountStream
|
||||||
|
.listen((count) {
|
||||||
|
if (count > 0) {
|
||||||
|
FlutterAppBadger.updateBadgeCount(count);
|
||||||
|
} else {
|
||||||
|
FlutterAppBadger.removeBadge();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
badgeListener?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LeftDrawer extends StatelessWidget {
|
||||||
|
const LeftDrawer({
|
||||||
|
Key? key,
|
||||||
|
required this.user,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final User user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Drawer(
|
||||||
|
child: Container(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
|
child: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: MediaQuery.of(context).viewPadding.top + 8,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
bottom: 20.0,
|
||||||
|
left: 8,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
UserAvatar(
|
||||||
|
user: user,
|
||||||
|
showOnlineStatus: false,
|
||||||
|
constraints: BoxConstraints.tight(Size.fromRadius(20)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
|
child: Text(
|
||||||
|
user.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: StreamSvgIcon.penWrite(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(.5),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.popAndPushNamed(
|
||||||
|
context,
|
||||||
|
Routes.NEW_CHAT,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
title: Text(
|
||||||
|
'New direct message',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: StreamSvgIcon.contacts(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(.5),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.popAndPushNamed(
|
||||||
|
context,
|
||||||
|
Routes.NEW_GROUP_CHAT,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
title: Text(
|
||||||
|
'New group',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: ListTile(
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
|
||||||
|
if (!kIsWeb) {
|
||||||
|
final secureStorage = FlutterSecureStorage();
|
||||||
|
await secureStorage.deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
final client = StreamChat.of(context).client;
|
||||||
|
client.disconnectUser();
|
||||||
|
await client.dispose();
|
||||||
|
|
||||||
|
await Navigator.of(
|
||||||
|
context,
|
||||||
|
rootNavigator: true,
|
||||||
|
).pushNamedAndRemoveUntil(
|
||||||
|
Routes.CHOOSE_USER,
|
||||||
|
ModalRoute.withName(Routes.CHOOSE_USER),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
leading: StreamSvgIcon.user(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(.5),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'Sign out',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: StreamSvgIcon.iconMoon(
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textLowEmphasis,
|
||||||
|
onPressed: () async {
|
||||||
|
final sp = await StreamingSharedPreferences.instance;
|
||||||
|
sp.setInt(
|
||||||
|
'theme',
|
||||||
|
Theme.of(context).brightness == Brightness.dark
|
||||||
|
? 1
|
||||||
|
: -1,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
|
class ChannelMediaDisplayScreen extends StatefulWidget {
|
||||||
|
/// The sorting used for the channels matching the filters.
|
||||||
|
/// Sorting is based on field and direction, multiple sorting options can be provided.
|
||||||
|
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
|
||||||
|
/// Direction can be ascending or descending.
|
||||||
|
final List<SortOption>? sortOptions;
|
||||||
|
|
||||||
|
/// Pagination parameters
|
||||||
|
/// limit: the number of users to return (max is 30)
|
||||||
|
/// offset: the offset (max is 1000)
|
||||||
|
/// message_limit: how many messages should be included to each channel
|
||||||
|
final PaginationParams? paginationParams;
|
||||||
|
|
||||||
|
/// The builder used when the file list is empty.
|
||||||
|
final WidgetBuilder? emptyBuilder;
|
||||||
|
|
||||||
|
final ShowMessageCallback? onShowMessage;
|
||||||
|
|
||||||
|
final MessageTheme messageTheme;
|
||||||
|
|
||||||
|
const ChannelMediaDisplayScreen({
|
||||||
|
required this.messageTheme,
|
||||||
|
this.sortOptions,
|
||||||
|
this.paginationParams,
|
||||||
|
this.emptyBuilder,
|
||||||
|
this.onShowMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChannelMediaDisplayScreenState createState() =>
|
||||||
|
_ChannelMediaDisplayScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
||||||
|
Map<String?, VideoPlayerController?> controllerCache = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
|
messageSearchBloc.search(
|
||||||
|
filter: Filter.in_(
|
||||||
|
'cid',
|
||||||
|
[StreamChannel.of(context).channel.cid!],
|
||||||
|
),
|
||||||
|
messageFilter: Filter.in_(
|
||||||
|
'attachments.type',
|
||||||
|
['image', 'video'],
|
||||||
|
),
|
||||||
|
sort: widget.sortOptions,
|
||||||
|
pagination: widget.paginationParams,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
|
appBar: AppBar(
|
||||||
|
brightness: Theme.of(context).brightness,
|
||||||
|
elevation: 1,
|
||||||
|
centerTitle: true,
|
||||||
|
title: Text(
|
||||||
|
'Photos & Videos',
|
||||||
|
style: TextStyle(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
|
fontSize: 16.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: StreamBackButton(),
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
|
),
|
||||||
|
body: _buildMediaGrid(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMediaGrid() {
|
||||||
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
|
|
||||||
|
return StreamBuilder<List<GetMessageResponse>>(
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return Center(
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.data!.isEmpty) {
|
||||||
|
if (widget.emptyBuilder != null) {
|
||||||
|
return widget.emptyBuilder!(context);
|
||||||
|
}
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
StreamSvgIcon.pictures(
|
||||||
|
size: 136.0,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.0),
|
||||||
|
Text(
|
||||||
|
'No Media',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.0),
|
||||||
|
Text(
|
||||||
|
'Photos or video sent in this chat will \nappear here',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: media[position].attachment.type == 'image'
|
||||||
|
? IgnorePointer(
|
||||||
|
child: ImageAttachment(
|
||||||
|
attachment: media[position].attachment,
|
||||||
|
message: media[position].message,
|
||||||
|
showTitle: false,
|
||||||
|
size: Size(
|
||||||
|
MediaQuery.of(context).size.width * 0.8,
|
||||||
|
MediaQuery.of(context).size.height * 0.3,
|
||||||
|
),
|
||||||
|
messageTheme: widget.messageTheme,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: VideoPlayer(media[position].videoPlayer!),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: media.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
stream: messageSearchBloc.messagesStream,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
for (var c in controllerCache.values) {
|
||||||
|
c!.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AssetPackage {
|
||||||
|
Attachment attachment;
|
||||||
|
Message message;
|
||||||
|
VideoPlayerController? videoPlayer;
|
||||||
|
|
||||||
|
_AssetPackage(this.attachment, this.message, this.videoPlayer);
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:example/routes/routes.dart';
|
||||||
|
import 'package:example/thread_page.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
import 'chat_info_screen.dart';
|
||||||
|
import 'group_info_screen.dart';
|
||||||
|
|
||||||
|
class ChannelPageArgs {
|
||||||
|
final Channel? channel;
|
||||||
|
final Message? initialMessage;
|
||||||
|
|
||||||
|
const ChannelPageArgs({
|
||||||
|
this.channel,
|
||||||
|
this.initialMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChannelPage extends StatefulWidget {
|
||||||
|
final int? initialScrollIndex;
|
||||||
|
final double? initialAlignment;
|
||||||
|
final bool highlightInitialMessage;
|
||||||
|
|
||||||
|
const ChannelPage({
|
||||||
|
Key? key,
|
||||||
|
this.initialScrollIndex,
|
||||||
|
this.initialAlignment,
|
||||||
|
this.highlightInitialMessage = false,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChannelPageState createState() => _ChannelPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelPageState extends State<ChannelPage> {
|
||||||
|
Message? _quotedMessage;
|
||||||
|
FocusNode? _focusNode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_focusNode = FocusNode();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_focusNode!.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reply(Message message) {
|
||||||
|
setState(() => _quotedMessage = message);
|
||||||
|
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
|
||||||
|
_focusNode!.requestFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
|
appBar: ChannelHeader(
|
||||||
|
showTypingIndicator: false,
|
||||||
|
onImageTap: () async {
|
||||||
|
var channel = StreamChannel.of(context).channel;
|
||||||
|
|
||||||
|
if (channel.memberCount == 2 && channel.isDistinct) {
|
||||||
|
final currentUser = StreamChat.of(context).user;
|
||||||
|
final otherUser = channel.state!.members.firstWhereOrNull(
|
||||||
|
(element) => element.user!.id != currentUser!.id,
|
||||||
|
);
|
||||||
|
if (otherUser != null) {
|
||||||
|
final pop = await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
child: ChatInfoScreen(
|
||||||
|
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
|
||||||
|
user: otherUser.user,
|
||||||
|
),
|
||||||
|
channel: channel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pop == true) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
child: GroupInfoScreen(
|
||||||
|
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
|
||||||
|
),
|
||||||
|
channel: channel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
MessageListView(
|
||||||
|
initialScrollIndex: widget.initialScrollIndex,
|
||||||
|
initialAlignment: widget.initialAlignment,
|
||||||
|
highlightInitialMessage: widget.highlightInitialMessage,
|
||||||
|
onMessageSwiped: _reply,
|
||||||
|
messageBuilder: (context, details, messages, defaultMessage) {
|
||||||
|
return defaultMessage.copyWith(
|
||||||
|
onReplyTap: _reply,
|
||||||
|
onShowMessage: (m, c) async {
|
||||||
|
final client = StreamChat.of(context).client;
|
||||||
|
final message = m;
|
||||||
|
final channel = client.channel(
|
||||||
|
c.type,
|
||||||
|
id: c.id,
|
||||||
|
);
|
||||||
|
if (channel.state == null) {
|
||||||
|
await channel.watch();
|
||||||
|
}
|
||||||
|
Navigator.pushReplacementNamed(
|
||||||
|
context,
|
||||||
|
Routes.CHANNEL_PAGE,
|
||||||
|
arguments: ChannelPageArgs(
|
||||||
|
channel: channel,
|
||||||
|
initialMessage: message,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
threadBuilder: (_, parentMessage) {
|
||||||
|
return ThreadPage(
|
||||||
|
parent: parentMessage,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
pinPermissions: ['owner', 'admin', 'member'],
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.appBg
|
||||||
|
.withOpacity(.9),
|
||||||
|
child: TypingIndicator(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
style: StreamChatTheme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.footnote
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textLowEmphasis),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MessageInput(
|
||||||
|
focusNode: _focusNode,
|
||||||
|
quotedMessage: _quotedMessage,
|
||||||
|
onQuotedMessageCleared: () {
|
||||||
|
setState(() => _quotedMessage = null);
|
||||||
|
_focusNode!.unfocus();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,34 @@
|
|||||||
|
import 'package:collection/collection.dart' show IterableExtension;
|
||||||
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:jiffy/jiffy.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
import 'main.dart';
|
import 'channel_file_display_screen.dart';
|
||||||
|
import 'channel_media_display_screen.dart';
|
||||||
|
import 'channel_page.dart';
|
||||||
|
import 'pinned_messages_screen.dart';
|
||||||
import 'routes/routes.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;
|
||||||
|
|
||||||
const ChatInfoScreen({Key key, this.user}) : super(key: key);
|
final MessageTheme messageTheme;
|
||||||
|
|
||||||
|
const ChatInfoScreen({
|
||||||
|
Key? key,
|
||||||
|
required this.messageTheme,
|
||||||
|
this.user,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ChatInfoScreenState createState() => _ChatInfoScreenState();
|
_ChatInfoScreenState createState() => _ChatInfoScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
||||||
ValueNotifier<bool> mutedBool = ValueNotifier(false);
|
ValueNotifier<bool?> mutedBool = ValueNotifier(false);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -30,25 +40,25 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
_buildUserHeader(),
|
_buildUserHeader(),
|
||||||
Container(
|
Container(
|
||||||
height: 8.0,
|
height: 8.0,
|
||||||
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
color: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
),
|
),
|
||||||
_buildOptionListTiles(),
|
_buildOptionListTiles(),
|
||||||
Container(
|
Container(
|
||||||
height: 8.0,
|
height: 8.0,
|
||||||
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
color: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
),
|
),
|
||||||
if ([
|
if ([
|
||||||
'admin',
|
'admin',
|
||||||
'owner',
|
'owner',
|
||||||
].contains(channel.state.members
|
].contains(channel.state!.members
|
||||||
.firstWhere((m) => m.userId == channel.client.state.user.id,
|
.firstWhereOrNull(
|
||||||
orElse: () => null)
|
(m) => m.userId == channel.client.state.user!.id)
|
||||||
?.role))
|
?.role))
|
||||||
_buildDeleteListTile(),
|
_buildDeleteListTile(),
|
||||||
],
|
],
|
||||||
@@ -58,7 +68,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
|
|
||||||
Widget _buildUserHeader() {
|
Widget _buildUserHeader() {
|
||||||
return Material(
|
return Material(
|
||||||
color: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
color: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
@@ -68,7 +78,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: UserAvatar(
|
child: UserAvatar(
|
||||||
user: widget.user,
|
user: widget.user!,
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: 72.0,
|
maxWidth: 72.0,
|
||||||
maxHeight: 72.0,
|
maxHeight: 72.0,
|
||||||
@@ -78,23 +88,23 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
widget.user.name,
|
widget.user!.name,
|
||||||
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
SizedBox(height: 7.0),
|
SizedBox(height: 7.0),
|
||||||
_buildConnectedTitleState(),
|
_buildConnectedTitleState(),
|
||||||
SizedBox(height: 15.0),
|
SizedBox(height: 15.0),
|
||||||
OptionListTile(
|
OptionListTile(
|
||||||
title: '@${widget.user.id}',
|
title: '@${widget.user!.id}',
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
trailing: Padding(
|
trailing: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.user.name,
|
widget.user!.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(0.5),
|
.withOpacity(0.5),
|
||||||
fontSize: 16.0),
|
fontSize: 16.0),
|
||||||
),
|
),
|
||||||
@@ -124,7 +134,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
// title: 'Notifications',
|
// title: 'Notifications',
|
||||||
// leading: StreamSvgIcon.Icon_notification(
|
// leading: StreamSvgIcon.Icon_notification(
|
||||||
// size: 24.0,
|
// size: 24.0,
|
||||||
// color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
// color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5),
|
||||||
// ),
|
// ),
|
||||||
// trailing: CupertinoSwitch(
|
// trailing: CupertinoSwitch(
|
||||||
// value: true,
|
// value: true,
|
||||||
@@ -138,7 +148,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
mutedBool.value = snapshot.data;
|
mutedBool.value = snapshot.data;
|
||||||
|
|
||||||
return OptionListTile(
|
return OptionListTile(
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
title: 'Mute user',
|
title: 'Mute user',
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
leading: Padding(
|
leading: Padding(
|
||||||
@@ -147,21 +157,21 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
size: 24.0,
|
size: 24.0,
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(0.5),
|
.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.channel.unmute();
|
||||||
} else {
|
} else {
|
||||||
channel.channel.mute();
|
channel.channel.mute();
|
||||||
@@ -176,7 +186,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
// title: 'Block User',
|
// title: 'Block User',
|
||||||
// leading: StreamSvgIcon.Icon_user_delete(
|
// leading: StreamSvgIcon.Icon_user_delete(
|
||||||
// size: 24.0,
|
// size: 24.0,
|
||||||
// color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
// color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5),
|
||||||
// ),
|
// ),
|
||||||
// trailing: CupertinoSwitch(
|
// trailing: CupertinoSwitch(
|
||||||
// value: widget.user.banned,
|
// value: widget.user.banned,
|
||||||
@@ -190,20 +200,83 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
// ),
|
// ),
|
||||||
// onTap: () {},
|
// onTap: () {},
|
||||||
// ),
|
// ),
|
||||||
|
OptionListTile(
|
||||||
|
title: 'Pinned Messages',
|
||||||
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
|
leading: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
||||||
|
child: StreamSvgIcon.pin(
|
||||||
|
size: 24.0,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: StreamSvgIcon.right(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.textLowEmphasis,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
final channel = StreamChannel.of(context).channel;
|
||||||
|
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: MessageSearchBloc(
|
||||||
|
child: PinnedMessagesScreen(
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
Routes.CHANNEL_PAGE,
|
||||||
|
arguments: ChannelPageArgs(
|
||||||
|
channel: channel,
|
||||||
|
initialMessage: message,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
OptionListTile(
|
OptionListTile(
|
||||||
title: 'Photos & Videos',
|
title: 'Photos & Videos',
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
leading: Padding(
|
leading: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: StreamSvgIcon.pictures(
|
child: StreamSvgIcon.pictures(
|
||||||
size: 36.0,
|
size: 36.0,
|
||||||
color:
|
color: StreamChatTheme.of(context)
|
||||||
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: StreamSvgIcon.right(
|
trailing: StreamSvgIcon.right(
|
||||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
color: StreamChatTheme.of(context).colorTheme.textLowEmphasis,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
@@ -215,6 +288,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
channel: channel,
|
channel: channel,
|
||||||
child: MessageSearchBloc(
|
child: MessageSearchBloc(
|
||||||
child: ChannelMediaDisplayScreen(
|
child: ChannelMediaDisplayScreen(
|
||||||
|
messageTheme: widget.messageTheme,
|
||||||
sortOptions: [
|
sortOptions: [
|
||||||
SortOption(
|
SortOption(
|
||||||
'created_at',
|
'created_at',
|
||||||
@@ -250,18 +324,20 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
),
|
),
|
||||||
OptionListTile(
|
OptionListTile(
|
||||||
title: 'Files',
|
title: 'Files',
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
leading: Padding(
|
leading: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||||
child: StreamSvgIcon.files(
|
child: StreamSvgIcon.files(
|
||||||
size: 32.0,
|
size: 32.0,
|
||||||
color:
|
color: StreamChatTheme.of(context)
|
||||||
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: StreamSvgIcon.right(
|
trailing: StreamSvgIcon.right(
|
||||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
color: StreamChatTheme.of(context).colorTheme.textLowEmphasis,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
@@ -289,18 +365,20 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
),
|
),
|
||||||
OptionListTile(
|
OptionListTile(
|
||||||
title: 'Shared groups',
|
title: 'Shared groups',
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
leading: Padding(
|
leading: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
||||||
child: StreamSvgIcon.iconGroup(
|
child: StreamSvgIcon.iconGroup(
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
color:
|
color: StreamChatTheme.of(context)
|
||||||
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: StreamSvgIcon.right(
|
trailing: StreamSvgIcon.right(
|
||||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
color: StreamChatTheme.of(context).colorTheme.textLowEmphasis,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@@ -317,21 +395,21 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
Widget _buildDeleteListTile() {
|
Widget _buildDeleteListTile() {
|
||||||
return OptionListTile(
|
return OptionListTile(
|
||||||
title: 'Delete Conversation',
|
title: 'Delete Conversation',
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
tileColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
titleTextStyle: StreamChatTheme.of(context).textTheme.body.copyWith(
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body.copyWith(
|
||||||
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
color: StreamChatTheme.of(context).colorTheme.accentError,
|
||||||
),
|
),
|
||||||
leading: Padding(
|
leading: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
||||||
child: StreamSvgIcon.delete(
|
child: StreamSvgIcon.delete(
|
||||||
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
color: StreamChatTheme.of(context).colorTheme.accentError,
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_showDeleteDialog();
|
_showDeleteDialog();
|
||||||
},
|
},
|
||||||
titleColor: StreamChatTheme.of(context).colorTheme.accentRed,
|
titleColor: StreamChatTheme.of(context).colorTheme.accentError,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,7 +421,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
question: 'Are you sure you want to delete this conversation?',
|
question: 'Are you sure you want to delete this conversation?',
|
||||||
cancelText: 'CANCEL',
|
cancelText: 'CANCEL',
|
||||||
icon: StreamSvgIcon.delete(
|
icon: StreamSvgIcon.delete(
|
||||||
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
color: StreamChatTheme.of(context).colorTheme.accentError,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
var channel = StreamChannel.of(context).channel;
|
var channel = StreamChannel.of(context).channel;
|
||||||
@@ -367,7 +445,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(0.5)),
|
.withOpacity(0.5)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -376,7 +454,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(0.5)),
|
.withOpacity(0.5)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -385,7 +463,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (widget.user.online)
|
if (widget.user!.online)
|
||||||
Material(
|
Material(
|
||||||
type: MaterialType.circle,
|
type: MaterialType.circle,
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -396,13 +474,13 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
),
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
shape: CircleBorder(),
|
shape: CircleBorder(),
|
||||||
color: StreamChatTheme.of(context).colorTheme.accentGreen,
|
color: StreamChatTheme.of(context).colorTheme.accentInfo,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
color: StreamChatTheme.of(context).colorTheme.white,
|
color: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
),
|
),
|
||||||
alternativeWidget,
|
alternativeWidget,
|
||||||
if (widget.user.online)
|
if (widget.user!.online)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 24.0,
|
width: 24.0,
|
||||||
),
|
),
|
||||||
@@ -412,8 +490,8 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SharedGroupsScreen extends StatefulWidget {
|
class _SharedGroupsScreen extends StatefulWidget {
|
||||||
final User mainUser;
|
final User? mainUser;
|
||||||
final User otherUser;
|
final User? otherUser;
|
||||||
|
|
||||||
_SharedGroupsScreen(this.mainUser, this.otherUser);
|
_SharedGroupsScreen(this.mainUser, this.otherUser);
|
||||||
|
|
||||||
@@ -427,7 +505,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
var chat = StreamChat.of(context);
|
var chat = StreamChat.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
brightness: Theme.of(context).brightness,
|
brightness: Theme.of(context).brightness,
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
@@ -435,28 +513,18 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
title: Text(
|
title: Text(
|
||||||
'Shared Groups',
|
'Shared Groups',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
fontSize: 16.0),
|
fontSize: 16.0),
|
||||||
),
|
),
|
||||||
leading: StreamBackButton(),
|
leading: StreamBackButton(),
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
),
|
),
|
||||||
body: StreamBuilder<List<Channel>>(
|
body: StreamBuilder<List<Channel>>(
|
||||||
stream: chat.client.queryChannels(
|
stream: chat.client.queryChannels(
|
||||||
filter: {
|
filter: Filter.and([
|
||||||
r'$and': [
|
Filter.in_('members', [widget.otherUser!.id]),
|
||||||
{
|
Filter.in_('members', [widget.mainUser!.id]),
|
||||||
'members': {
|
]),
|
||||||
r'$in': [widget.otherUser.id],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'members': {
|
|
||||||
r'$in': [widget.mainUser.id],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
@@ -465,21 +533,23 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.data.isEmpty) {
|
if (snapshot.data!.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
StreamSvgIcon.message(
|
StreamSvgIcon.message(
|
||||||
size: 136.0,
|
size: 136.0,
|
||||||
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
color: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.0),
|
SizedBox(height: 16.0),
|
||||||
Text(
|
Text(
|
||||||
'No Shared Groups',
|
'No Shared Groups',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.0,
|
fontSize: 14.0,
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 8.0),
|
SizedBox(height: 8.0),
|
||||||
@@ -490,7 +560,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
fontSize: 14.0,
|
fontSize: 14.0,
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(0.5),
|
.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -499,11 +569,11 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final channels = snapshot.data
|
final channels = snapshot.data!
|
||||||
.where((c) =>
|
.where((c) =>
|
||||||
c.state.members.any((m) =>
|
c.state!.members.any((m) =>
|
||||||
m.userId != widget.mainUser.id &&
|
m.userId != widget.mainUser!.id &&
|
||||||
m.userId != widget.otherUser.id) ||
|
m.userId != widget.otherUser!.id) ||
|
||||||
!c.isDistinct)
|
!c.isDistinct)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
@@ -523,24 +593,24 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
|
|
||||||
Widget _buildListTile(Channel channel) {
|
Widget _buildListTile(Channel channel) {
|
||||||
var extraData = channel.extraData;
|
var extraData = channel.extraData;
|
||||||
var members = channel.state.members;
|
var members = channel.state!.members;
|
||||||
|
|
||||||
var textStyle = TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold);
|
var textStyle = TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
height: 64.0,
|
height: 64.0,
|
||||||
child: LayoutBuilder(builder: (context, constraints) {
|
child: LayoutBuilder(builder: (context, constraints) {
|
||||||
String title;
|
String? title;
|
||||||
if (extraData['name'] == null) {
|
if (extraData['name'] == null) {
|
||||||
final otherMembers = members.where(
|
final otherMembers = members.where(
|
||||||
(member) => member.userId != StreamChat.of(context).user.id);
|
(member) => member.userId != StreamChat.of(context).user!.id);
|
||||||
if (otherMembers.isNotEmpty) {
|
if (otherMembers.isNotEmpty) {
|
||||||
final maxWidth = constraints.maxWidth;
|
final maxWidth = constraints.maxWidth;
|
||||||
final maxChars = maxWidth / textStyle.fontSize;
|
final maxChars = maxWidth / textStyle.fontSize!;
|
||||||
var currentChars = 0;
|
var currentChars = 0;
|
||||||
final currentMembers = <Member>[];
|
final currentMembers = <Member>[];
|
||||||
otherMembers.forEach((element) {
|
otherMembers.forEach((element) {
|
||||||
final newLength = currentChars + element.user.name.length;
|
final newLength = currentChars + element.user!.name.length;
|
||||||
if (newLength < maxChars) {
|
if (newLength < maxChars) {
|
||||||
currentChars = newLength;
|
currentChars = newLength;
|
||||||
currentMembers.add(element);
|
currentMembers.add(element);
|
||||||
@@ -550,12 +620,12 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
final exceedingMembers =
|
final exceedingMembers =
|
||||||
otherMembers.length - currentMembers.length;
|
otherMembers.length - currentMembers.length;
|
||||||
title =
|
title =
|
||||||
'${currentMembers.map((e) => e.user.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
|
'${currentMembers.map((e) => e.user!.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
|
||||||
} else {
|
} else {
|
||||||
title = 'No title';
|
title = 'No title';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
title = extraData['name'];
|
title = extraData['name'] as String;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@@ -565,7 +635,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: ChannelImage(
|
child: ChannelAvatar(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
constraints:
|
constraints:
|
||||||
BoxConstraints(maxWidth: 40.0, maxHeight: 40.0),
|
BoxConstraints(maxWidth: 40.0, maxHeight: 40.0),
|
||||||
@@ -583,7 +653,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(0.5)),
|
.withOpacity(0.5)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -592,8 +662,10 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 1.0,
|
height: 1.0,
|
||||||
color:
|
color: StreamChatTheme.of(context)
|
||||||
StreamChatTheme.of(context).colorTheme.black.withOpacity(.08),
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(.08),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,18 +6,18 @@ typedef OnChipAdded<T> = void Function(T chip);
|
|||||||
typedef OnChipRemoved<T> = void Function(T chip);
|
typedef OnChipRemoved<T> = void Function(T chip);
|
||||||
|
|
||||||
class ChipsInputTextField<T> extends StatefulWidget {
|
class ChipsInputTextField<T> extends StatefulWidget {
|
||||||
final TextEditingController controller;
|
final TextEditingController? controller;
|
||||||
final FocusNode focusNode;
|
final FocusNode? focusNode;
|
||||||
final ValueChanged<String> onInputChanged;
|
final ValueChanged<String>? onInputChanged;
|
||||||
final ChipBuilder<T> chipBuilder;
|
final ChipBuilder<T> chipBuilder;
|
||||||
final OnChipAdded<T> onChipAdded;
|
final OnChipAdded<T>? onChipAdded;
|
||||||
final OnChipRemoved<T> onChipRemoved;
|
final OnChipRemoved<T>? onChipRemoved;
|
||||||
final String hint;
|
final String hint;
|
||||||
|
|
||||||
const ChipsInputTextField({
|
const ChipsInputTextField({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.chipBuilder,
|
required this.chipBuilder,
|
||||||
@required this.controller,
|
required this.controller,
|
||||||
this.onInputChanged,
|
this.onInputChanged,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.onChipAdded,
|
this.onChipAdded,
|
||||||
@@ -35,7 +35,7 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
|
|||||||
|
|
||||||
void addItem(T item) {
|
void addItem(T item) {
|
||||||
setState(() => _chips.add(item));
|
setState(() => _chips.add(item));
|
||||||
if (widget.onChipAdded != null) widget.onChipAdded(item);
|
if (widget.onChipAdded != null) widget.onChipAdded!(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeItem(T item) {
|
void removeItem(T item) {
|
||||||
@@ -43,7 +43,7 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
|
|||||||
_chips.remove(item);
|
_chips.remove(item);
|
||||||
if (_chips.isEmpty) resumeItemAddition();
|
if (_chips.isEmpty) resumeItemAddition();
|
||||||
});
|
});
|
||||||
if (widget.onChipRemoved != null) widget.onChipRemoved(item);
|
if (widget.onChipRemoved != null) widget.onChipRemoved!(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pauseItemAddition() {
|
void pauseItemAddition() {
|
||||||
@@ -66,7 +66,7 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
|
|||||||
onTap: _pauseItemAddition ? resumeItemAddition : null,
|
onTap: _pauseItemAddition ? resumeItemAddition : null,
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
color: StreamChatTheme.of(context).colorTheme.white,
|
color: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
child: Container(
|
child: Container(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
|
||||||
@@ -82,7 +82,7 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
|
|||||||
.copyWith(
|
.copyWith(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(.5)),
|
.withOpacity(.5)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -119,7 +119,7 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
|
|||||||
.copyWith(
|
.copyWith(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(.5)),
|
.withOpacity(.5)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -134,14 +134,14 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
|
|||||||
? StreamSvgIcon.user(
|
? StreamSvgIcon.user(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(0.5),
|
.withOpacity(0.5),
|
||||||
size: 24,
|
size: 24,
|
||||||
)
|
)
|
||||||
: StreamSvgIcon.userAdd(
|
: StreamSvgIcon.userAdd(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(0.5),
|
.withOpacity(0.5),
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:example/app_config.dart';
|
import 'package:example/app_config.dart';
|
||||||
|
import 'package:example/home_page.dart';
|
||||||
import 'package:example/stream_version.dart';
|
import 'package:example/stream_version.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -18,7 +19,7 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
final users = defaultUsers;
|
final users = defaultUsers;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@@ -32,7 +33,7 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
'assets/logo.svg',
|
'assets/logo.svg',
|
||||||
height: 40,
|
height: 40,
|
||||||
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -54,7 +55,7 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
separatorBuilder: (context, i) {
|
separatorBuilder: (context, i) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
|
color: StreamChatTheme.of(context).colorTheme.borders,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
itemCount: users.length + 1,
|
itemCount: users.length + 1,
|
||||||
@@ -78,7 +79,7 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.white,
|
.barsBg,
|
||||||
),
|
),
|
||||||
height: 100,
|
height: 100,
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -89,8 +90,11 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final client = StreamChat.of(context).client;
|
final client = StreamChatClient(
|
||||||
client.apiKey = kDefaultStreamApiKey;
|
kDefaultStreamApiKey,
|
||||||
|
logLevel: Level.INFO,
|
||||||
|
);
|
||||||
|
|
||||||
await client.connectUser(
|
await client.connectUser(
|
||||||
user,
|
user,
|
||||||
token,
|
token,
|
||||||
@@ -115,6 +119,7 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
Routes.HOME,
|
Routes.HOME,
|
||||||
ModalRoute.withName(Routes.HOME),
|
ModalRoute.withName(Routes.HOME),
|
||||||
|
arguments: HomePageArgs(client),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
leading: UserAvatar(
|
leading: UserAvatar(
|
||||||
@@ -136,13 +141,13 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
.copyWith(
|
.copyWith(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.grey,
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: StreamSvgIcon.arrowRight(
|
trailing: StreamSvgIcon.arrowRight(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.accentBlue,
|
.accentPrimary,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -152,11 +157,12 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
child: StreamSvgIcon.settings(
|
child: StreamSvgIcon.settings(
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis,
|
||||||
),
|
),
|
||||||
backgroundColor: StreamChatTheme.of(context)
|
backgroundColor:
|
||||||
.colorTheme
|
StreamChatTheme.of(context).colorTheme.borders,
|
||||||
.greyWhisper,
|
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'Advanced Options',
|
'Advanced Options',
|
||||||
@@ -168,8 +174,9 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
.textTheme
|
.textTheme
|
||||||
.footnote
|
.footnote
|
||||||
.copyWith(
|
.copyWith(
|
||||||
color:
|
color: StreamChatTheme.of(context)
|
||||||
StreamChatTheme.of(context).colorTheme.grey,
|
.colorTheme
|
||||||
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: SvgPicture.asset(
|
trailing: SvgPicture.asset(
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ 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 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import 'main.dart';
|
import 'channel_page.dart';
|
||||||
import 'routes/routes.dart';
|
import 'routes/routes.dart';
|
||||||
|
|
||||||
class GroupChatDetailsScreen extends StatefulWidget {
|
class GroupChatDetailsScreen extends StatefulWidget {
|
||||||
final List<User> selectedUsers;
|
final List<User>? selectedUsers;
|
||||||
|
|
||||||
const GroupChatDetailsScreen({
|
const GroupChatDetailsScreen({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.selectedUsers,
|
required this.selectedUsers,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -20,14 +20,14 @@ class GroupChatDetailsScreen extends StatefulWidget {
|
|||||||
class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
||||||
final _selectedUsers = <User>[];
|
final _selectedUsers = <User>[];
|
||||||
|
|
||||||
TextEditingController _groupNameController;
|
TextEditingController? _groupNameController;
|
||||||
|
|
||||||
bool _isGroupNameEmpty = true;
|
bool _isGroupNameEmpty = true;
|
||||||
|
|
||||||
int get _totalUsers => _selectedUsers.length;
|
int get _totalUsers => _selectedUsers.length;
|
||||||
|
|
||||||
void _groupNameListener() {
|
void _groupNameListener() {
|
||||||
final name = _groupNameController.text;
|
final name = _groupNameController!.text;
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isGroupNameEmpty = name.isEmpty;
|
_isGroupNameEmpty = name.isEmpty;
|
||||||
@@ -38,7 +38,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_selectedUsers.addAll(widget.selectedUsers);
|
_selectedUsers.addAll(widget.selectedUsers!);
|
||||||
_groupNameController = TextEditingController()
|
_groupNameController = TextEditingController()
|
||||||
..addListener(_groupNameListener);
|
..addListener(_groupNameListener);
|
||||||
}
|
}
|
||||||
@@ -59,16 +59,16 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
brightness: Theme.of(context).brightness,
|
brightness: Theme.of(context).brightness,
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
leading: const StreamBackButton(),
|
leading: const StreamBackButton(),
|
||||||
title: Text(
|
title: Text(
|
||||||
'Name of Group Chat',
|
'Name of Group Chat',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -83,7 +83,9 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
'NAME',
|
'NAME',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 16),
|
SizedBox(width: 16),
|
||||||
@@ -101,7 +103,9 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
hintText: 'Choose a group chat name',
|
hintText: 'Choose a group chat name',
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -117,20 +121,20 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
icon: StreamSvgIcon.check(
|
icon: StreamSvgIcon.check(
|
||||||
size: 24,
|
size: 24,
|
||||||
color: _isGroupNameEmpty
|
color: _isGroupNameEmpty
|
||||||
? StreamChatTheme.of(context).colorTheme.grey
|
? StreamChatTheme.of(context).colorTheme.textLowEmphasis
|
||||||
: StreamChatTheme.of(context).colorTheme.accentBlue,
|
: StreamChatTheme.of(context).colorTheme.accentPrimary,
|
||||||
),
|
),
|
||||||
onPressed: _isGroupNameEmpty
|
onPressed: _isGroupNameEmpty
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
try {
|
try {
|
||||||
final groupName = _groupNameController.text;
|
final groupName = _groupNameController!.text;
|
||||||
final client = StreamChat.of(context).client;
|
final client = StreamChat.of(context).client;
|
||||||
final channel = client.channel('messaging',
|
final channel = client.channel('messaging',
|
||||||
id: Uuid().v4(),
|
id: Uuid().v4(),
|
||||||
extraData: {
|
extraData: {
|
||||||
'members': [
|
'members': [
|
||||||
client.state.user.id,
|
client.state.user!.id,
|
||||||
..._selectedUsers.map((e) => e.id),
|
..._selectedUsers.map((e) => e.id),
|
||||||
],
|
],
|
||||||
'name': groupName,
|
'name': groupName,
|
||||||
@@ -188,7 +192,9 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'$_totalUsers ${_totalUsers > 1 ? 'Members' : 'Member'}',
|
'$_totalUsers ${_totalUsers > 1 ? 'Members' : 'Member'}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -201,9 +207,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
itemCount: _selectedUsers.length + 1,
|
itemCount: _selectedUsers.length + 1,
|
||||||
separatorBuilder: (_, __) => Container(
|
separatorBuilder: (_, __) => Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context).colorTheme.borders,
|
||||||
.colorTheme
|
|
||||||
.greyWhisper,
|
|
||||||
),
|
),
|
||||||
itemBuilder: (_, index) {
|
itemBuilder: (_, index) {
|
||||||
if (index == _selectedUsers.length) {
|
if (index == _selectedUsers.length) {
|
||||||
@@ -211,7 +215,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
height: 1,
|
height: 1,
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.greyWhisper,
|
.borders,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final user = _selectedUsers[index];
|
final user = _selectedUsers[index];
|
||||||
@@ -237,7 +241,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
Icons.clear_rounded,
|
Icons.clear_rounded,
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black,
|
.textHighEmphasis,
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
splashRadius: 24,
|
splashRadius: 24,
|
||||||
@@ -266,7 +270,8 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
|
|
||||||
void _showErrorAlert() {
|
void _showErrorAlert() {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
useRootNavigator: false,
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
context: context,
|
context: context,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
@@ -281,7 +286,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
height: 26.0,
|
height: 26.0,
|
||||||
),
|
),
|
||||||
StreamSvgIcon.error(
|
StreamSvgIcon.error(
|
||||||
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
color: StreamChatTheme.of(context).colorTheme.accentError,
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@@ -299,14 +304,16 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
height: 36.0,
|
height: 36.0,
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color:
|
color: StreamChatTheme.of(context)
|
||||||
StreamChatTheme.of(context).colorTheme.black.withOpacity(.08),
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(.08),
|
||||||
height: 1.0,
|
height: 1.0,
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
FlatButton(
|
TextButton(
|
||||||
child: Text(
|
child: Text(
|
||||||
'OK',
|
'OK',
|
||||||
style: StreamChatTheme.of(context)
|
style: StreamChatTheme.of(context)
|
||||||
@@ -315,7 +322,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
.copyWith(
|
.copyWith(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.accentBlue),
|
.accentPrimary),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
|||||||
|
import 'package:example/routes/app_routes.dart';
|
||||||
|
import 'package:example/routes/routes.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
class HomePageArgs {
|
||||||
|
final StreamChatClient chatClient;
|
||||||
|
|
||||||
|
HomePageArgs(this.chatClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomePage extends StatefulWidget {
|
||||||
|
HomePage({
|
||||||
|
Key? key,
|
||||||
|
required this.chatClient,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final StreamChatClient chatClient;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_HomePageState createState() => _HomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomePageState extends State<HomePage> {
|
||||||
|
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StreamChat(
|
||||||
|
client: widget.chatClient,
|
||||||
|
child: WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
final canPop = await _navigatorKey.currentState?.maybePop() ?? false;
|
||||||
|
return !canPop;
|
||||||
|
},
|
||||||
|
child: Navigator(
|
||||||
|
key: _navigatorKey,
|
||||||
|
onGenerateRoute: AppRoutes.generateRoute,
|
||||||
|
initialRoute: Routes.CHANNEL_LIST_PAGE,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,8 @@ import 'dart:async';
|
|||||||
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 'channel_page.dart';
|
||||||
import 'chips_input_text_field.dart';
|
import 'chips_input_text_field.dart';
|
||||||
import 'main.dart';
|
|
||||||
import 'routes/routes.dart';
|
import 'routes/routes.dart';
|
||||||
|
|
||||||
class NewChatScreen extends StatefulWidget {
|
class NewChatScreen extends StatefulWidget {
|
||||||
@@ -16,9 +16,9 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
final _chipInputTextFieldStateKey =
|
final _chipInputTextFieldStateKey =
|
||||||
GlobalKey<ChipInputTextFieldState<User>>();
|
GlobalKey<ChipInputTextFieldState<User>>();
|
||||||
|
|
||||||
TextEditingController _controller;
|
late TextEditingController _controller;
|
||||||
|
|
||||||
ChipInputTextFieldState get _chipInputTextFieldState =>
|
ChipInputTextFieldState? get _chipInputTextFieldState =>
|
||||||
_chipInputTextFieldStateKey.currentState;
|
_chipInputTextFieldStateKey.currentState;
|
||||||
|
|
||||||
String _userNameQuery = '';
|
String _userNameQuery = '';
|
||||||
@@ -30,14 +30,14 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
|
|
||||||
bool _isSearchActive = false;
|
bool _isSearchActive = false;
|
||||||
|
|
||||||
Channel channel;
|
Channel? channel;
|
||||||
|
|
||||||
Timer _debounce;
|
Timer? _debounce;
|
||||||
|
|
||||||
bool _showUserList = true;
|
bool _showUserList = true;
|
||||||
|
|
||||||
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), () {
|
||||||
if (mounted)
|
if (mounted)
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -66,17 +66,15 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
final chatState = StreamChat.of(context);
|
final chatState = StreamChat.of(context);
|
||||||
|
|
||||||
final res = await chatState.client.queryChannelsOnline(
|
final res = await chatState.client.queryChannelsOnline(
|
||||||
options: {
|
state: false,
|
||||||
'state': false,
|
watch: false,
|
||||||
'watch': false,
|
filter: Filter.raw(value: {
|
||||||
},
|
|
||||||
filter: {
|
|
||||||
'members': [
|
'members': [
|
||||||
..._selectedUsers.map((e) => e.id),
|
..._selectedUsers.map((e) => e.id),
|
||||||
chatState.user.id,
|
chatState.user!.id,
|
||||||
],
|
],
|
||||||
'distinct': true,
|
'distinct': true,
|
||||||
},
|
}),
|
||||||
messageLimit: 0,
|
messageLimit: 0,
|
||||||
paginationParams: PaginationParams(
|
paginationParams: PaginationParams(
|
||||||
limit: 1,
|
limit: 1,
|
||||||
@@ -86,14 +84,14 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
final _channelExisted = res.length == 1;
|
final _channelExisted = res.length == 1;
|
||||||
if (_channelExisted) {
|
if (_channelExisted) {
|
||||||
channel = res.first;
|
channel = res.first;
|
||||||
await channel.watch();
|
await channel!.watch();
|
||||||
} else {
|
} else {
|
||||||
channel = chatState.client.channel(
|
channel = chatState.client.channel(
|
||||||
'messaging',
|
'messaging',
|
||||||
extraData: {
|
extraData: {
|
||||||
'members': [
|
'members': [
|
||||||
..._selectedUsers.map((e) => e.id),
|
..._selectedUsers.map((e) => e.id),
|
||||||
chatState.user.id,
|
chatState.user!.id,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -110,27 +108,25 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_searchFocusNode.dispose();
|
_searchFocusNode.dispose();
|
||||||
_messageInputFocusNode.dispose();
|
_messageInputFocusNode.dispose();
|
||||||
_controller?.clear();
|
_controller.clear();
|
||||||
_controller?.removeListener(_userNameListener);
|
_controller.removeListener(_userNameListener);
|
||||||
_controller?.dispose();
|
_controller.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
brightness: Theme.of(context).brightness,
|
brightness: Theme.of(context).brightness,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
leading: const StreamBackButton(),
|
leading: const StreamBackButton(),
|
||||||
title: Text(
|
title: Text(
|
||||||
'New Chat',
|
'New Chat',
|
||||||
style: StreamChatTheme.of(context)
|
style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith(
|
||||||
.textTheme
|
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis),
|
||||||
.headlineBold
|
|
||||||
.copyWith(color: StreamChatTheme.of(context).colorTheme.black),
|
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
@@ -158,7 +154,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
message: statusString,
|
message: statusString,
|
||||||
child: StreamChannel(
|
child: StreamChannel(
|
||||||
showLoading: false,
|
showLoading: false,
|
||||||
channel: channel,
|
channel: channel!,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -169,7 +165,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
chipBuilder: (context, user) {
|
chipBuilder: (context, user) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_chipInputTextFieldState.removeItem(user);
|
_chipInputTextFieldState?.removeItem(user);
|
||||||
_searchFocusNode.requestFocus();
|
_searchFocusNode.requestFocus();
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Stack(
|
||||||
@@ -179,7 +175,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.greyGainsboro,
|
.disabled,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.only(left: 24),
|
padding: const EdgeInsets.only(left: 24),
|
||||||
@@ -191,7 +187,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black,
|
.textHighEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -242,7 +238,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
child: StreamSvgIcon.contacts(
|
child: StreamSvgIcon.contacts(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.accentBlue,
|
.accentPrimary,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -281,7 +277,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
.copyWith(
|
.copyWith(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(.5))),
|
.withOpacity(.5))),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -299,24 +295,21 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
_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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pagination: PaginationParams(
|
pagination: PaginationParams(
|
||||||
limit: 25,
|
limit: 25,
|
||||||
),
|
),
|
||||||
filter: {
|
filter: Filter.and([
|
||||||
if (_userNameQuery.isNotEmpty)
|
if (_userNameQuery.isNotEmpty)
|
||||||
'name': {
|
Filter.autoComplete('name', _userNameQuery),
|
||||||
r'$autocomplete': _userNameQuery,
|
Filter.notEqual(
|
||||||
},
|
'id', StreamChat.of(context).user!.id),
|
||||||
'id': {
|
]),
|
||||||
r'$ne': StreamChat.of(context).user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sort: [
|
sort: [
|
||||||
SortOption(
|
SortOption(
|
||||||
'name',
|
'name',
|
||||||
@@ -355,7 +348,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
color: StreamChatTheme
|
color: StreamChatTheme
|
||||||
.of(context)
|
.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(.5)),
|
.withOpacity(.5)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -370,7 +363,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: 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 MessageListView();
|
||||||
@@ -383,7 +376,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(.5),
|
.withOpacity(.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -394,14 +387,14 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
MessageInput(
|
MessageInput(
|
||||||
focusNode: _messageInputFocusNode,
|
focusNode: _messageInputFocusNode,
|
||||||
preMessageSending: (message) async {
|
preMessageSending: (message) async {
|
||||||
await channel.watch();
|
await channel!.watch();
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
onMessageSent: (m) {
|
onMessageSent: (m) {
|
||||||
Navigator.pushNamedAndRemoveUntil(
|
Navigator.pushNamedAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
Routes.CHANNEL_PAGE,
|
Routes.CHANNEL_PAGE,
|
||||||
ModalRoute.withName(Routes.HOME),
|
ModalRoute.withName(Routes.CHANNEL_LIST_PAGE),
|
||||||
arguments: ChannelPageArgs(channel: channel),
|
arguments: ChannelPageArgs(channel: channel),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class NewGroupChatScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
||||||
TextEditingController _controller;
|
TextEditingController? _controller;
|
||||||
|
|
||||||
String _userNameQuery = '';
|
String _userNameQuery = '';
|
||||||
|
|
||||||
@@ -20,14 +20,14 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
|
|
||||||
bool _isSearchActive = false;
|
bool _isSearchActive = false;
|
||||||
|
|
||||||
Timer _debounce;
|
Timer? _debounce;
|
||||||
|
|
||||||
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), () {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_userNameQuery = _controller.text;
|
_userNameQuery = _controller!.text;
|
||||||
_isSearchActive = _userNameQuery.isNotEmpty;
|
_isSearchActive = _userNameQuery.isNotEmpty;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -51,15 +51,15 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
leading: const StreamBackButton(),
|
leading: const StreamBackButton(),
|
||||||
title: Text(
|
title: Text(
|
||||||
'Add Group Members',
|
'Add Group Members',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -68,7 +68,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
if (_selectedUsers.isNotEmpty)
|
if (_selectedUsers.isNotEmpty)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: StreamSvgIcon.arrowRight(
|
icon: StreamSvgIcon.arrowRight(
|
||||||
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final updatedList = await Navigator.pushNamed(
|
final updatedList = await Navigator.pushNamed(
|
||||||
@@ -80,7 +80,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_selectedUsers
|
_selectedUsers
|
||||||
..clear()
|
..clear()
|
||||||
..addAll(updatedList);
|
..addAll(updatedList as Iterable<User>);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -159,18 +159,18 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.white,
|
.appBg,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.whiteSnow,
|
.appBg,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: StreamSvgIcon.close(
|
child: StreamSvgIcon.close(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black,
|
.textHighEmphasis,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -212,8 +212,9 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
? 'Matches for \"$_userNameQuery\"'
|
? 'Matches for \"$_userNameQuery\"'
|
||||||
: 'On the platform',
|
: 'On the platform',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color: StreamChatTheme.of(context)
|
||||||
StreamChatTheme.of(context).colorTheme.grey,
|
.colorTheme
|
||||||
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -244,15 +245,11 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
pagination: PaginationParams(
|
pagination: PaginationParams(
|
||||||
limit: 25,
|
limit: 25,
|
||||||
),
|
),
|
||||||
filter: {
|
filter: Filter.and([
|
||||||
if (_userNameQuery.isNotEmpty)
|
if (_userNameQuery.isNotEmpty)
|
||||||
'name': {
|
Filter.autoComplete('name', _userNameQuery),
|
||||||
r'$autocomplete': _userNameQuery,
|
Filter.notEqual('id', StreamChat.of(context).user!.id),
|
||||||
},
|
]),
|
||||||
'id': {
|
|
||||||
r'$ne': StreamChat.of(context).user.id,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sort: [
|
sort: [
|
||||||
SortOption(
|
SortOption(
|
||||||
'name',
|
'name',
|
||||||
@@ -277,7 +274,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
size: 96,
|
size: 96,
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.grey,
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
@@ -288,7 +285,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
.copyWith(
|
.copyWith(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.grey,
|
.textLowEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -315,15 +312,15 @@ class _HeaderDelegate extends SliverPersistentHeaderDelegate {
|
|||||||
final double height;
|
final double height;
|
||||||
|
|
||||||
const _HeaderDelegate({
|
const _HeaderDelegate({
|
||||||
@required this.child,
|
required this.child,
|
||||||
@required this.height,
|
required this.height,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(
|
Widget build(
|
||||||
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||||
return Container(
|
return Container(
|
||||||
color: StreamChatTheme.of(context).colorTheme.white,
|
color: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ void showLocalNotification(Event event, String currentUserId) async {
|
|||||||
EventType.messageNew,
|
EventType.messageNew,
|
||||||
EventType.notificationMessageNew,
|
EventType.notificationMessageNew,
|
||||||
].contains(event.type) ||
|
].contains(event.type) ||
|
||||||
event.user.id == currentUserId) {
|
event.user!.id == currentUserId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.message == null) return;
|
if (event.message == null) return;
|
||||||
@@ -21,9 +21,9 @@ void showLocalNotification(Event event, String currentUserId) async {
|
|||||||
);
|
);
|
||||||
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
event.message.id.hashCode,
|
event.message!.id.hashCode,
|
||||||
event.message.user.name,
|
event.message!.user!.name,
|
||||||
event.message.text,
|
event.message!.text,
|
||||||
NotificationDetails(
|
NotificationDetails(
|
||||||
android: AndroidNotificationDetails(
|
android: AndroidNotificationDetails(
|
||||||
'message channel',
|
'message channel',
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
|
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 MessageTheme messageTheme;
|
||||||
|
|
||||||
|
const PinnedMessagesScreen({
|
||||||
|
required this.messageTheme,
|
||||||
|
this.sortOptions,
|
||||||
|
this.paginationParams,
|
||||||
|
this.emptyBuilder,
|
||||||
|
this.onShowMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PinnedMessagesScreenState createState() => _PinnedMessagesScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
|
||||||
|
Map<String?, VideoPlayerController?> controllerCache = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
|
messageSearchBloc.search(
|
||||||
|
filter: Filter.in_(
|
||||||
|
'cid',
|
||||||
|
[StreamChannel.of(context).channel.cid!],
|
||||||
|
),
|
||||||
|
messageFilter: Filter.equal(
|
||||||
|
'pinned',
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
sort: widget.sortOptions,
|
||||||
|
pagination: widget.paginationParams,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
|
appBar: AppBar(
|
||||||
|
brightness: Theme.of(context).brightness,
|
||||||
|
elevation: 1,
|
||||||
|
centerTitle: true,
|
||||||
|
title: Text(
|
||||||
|
'Pinned Messages',
|
||||||
|
style: TextStyle(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
|
fontSize: 16.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: StreamBackButton(),
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
|
),
|
||||||
|
body: _buildMediaGrid(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMediaGrid() {
|
||||||
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
|
|
||||||
|
return StreamBuilder<List<GetMessageResponse>>(
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return Center(
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.data!.isEmpty) {
|
||||||
|
if (widget.emptyBuilder != null) {
|
||||||
|
return widget.emptyBuilder!(context);
|
||||||
|
}
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
StreamSvgIcon.pin(
|
||||||
|
size: 136.0,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.0),
|
||||||
|
Text(
|
||||||
|
'No pinned items',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 17.0,
|
||||||
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.0),
|
||||||
|
RichText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
text: TextSpan(children: [
|
||||||
|
TextSpan(
|
||||||
|
text: 'Long-press an important message and\nchoose ',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: 'Pin to conversation',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textHighEmphasis
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = snapshot.data ?? [];
|
||||||
|
|
||||||
|
return LazyLoadScrollView(
|
||||||
|
onEndOfPage: () => messageSearchBloc.search(
|
||||||
|
filter: Filter.in_(
|
||||||
|
'cid',
|
||||||
|
[StreamChannel.of(context).channel.cid!],
|
||||||
|
),
|
||||||
|
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(
|
||||||
|
maxWidth: 40.0,
|
||||||
|
minHeight: 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} attachment${attachments.length > 1 ? 's' : ''}'
|
||||||
|
: ''),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
widget.onShowMessage?.call(data[position].message,
|
||||||
|
StreamChannel.of(context).channel);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: snapshot.data!.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
stream: messageSearchBloc.messagesStream,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
for (var c in controllerCache.values) {
|
||||||
|
c!.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,22 @@
|
|||||||
import 'routes.dart';
|
import 'package:example/channel_list_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../choose_user_page.dart';
|
|
||||||
import '../advanced_options_page.dart';
|
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import '../main.dart';
|
|
||||||
import '../group_chat_details_screen.dart';
|
import '../advanced_options_page.dart';
|
||||||
import '../new_group_chat_screen.dart';
|
import '../channel_page.dart';
|
||||||
import '../new_chat_screen.dart';
|
|
||||||
import '../chat_info_screen.dart';
|
import '../chat_info_screen.dart';
|
||||||
|
import '../choose_user_page.dart';
|
||||||
|
import '../group_chat_details_screen.dart';
|
||||||
import '../group_info_screen.dart';
|
import '../group_info_screen.dart';
|
||||||
|
import '../home_page.dart';
|
||||||
|
import '../main.dart';
|
||||||
|
import '../new_chat_screen.dart';
|
||||||
|
import '../new_group_chat_screen.dart';
|
||||||
|
import 'routes.dart';
|
||||||
|
|
||||||
class AppRoutes {
|
class AppRoutes {
|
||||||
/// Add entry for new route here
|
/// Add entry for new route here
|
||||||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
static Route<dynamic>? generateRoute(RouteSettings settings) {
|
||||||
final args = settings.arguments;
|
final args = settings.arguments;
|
||||||
switch (settings.name) {
|
switch (settings.name) {
|
||||||
case Routes.APP:
|
case Routes.APP:
|
||||||
@@ -25,7 +29,10 @@ class AppRoutes {
|
|||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: Routes.HOME),
|
settings: const RouteSettings(name: Routes.HOME),
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
return HomePage();
|
final homePageArgs = args as HomePageArgs;
|
||||||
|
return HomePage(
|
||||||
|
chatClient: homePageArgs.chatClient,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
case Routes.CHOOSE_USER:
|
case Routes.CHOOSE_USER:
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
@@ -42,12 +49,13 @@ class AppRoutes {
|
|||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: Routes.CHANNEL_PAGE),
|
settings: const RouteSettings(name: Routes.CHANNEL_PAGE),
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
final arg = args as ChannelPageArgs;
|
final channelPageArgs = args as ChannelPageArgs;
|
||||||
return StreamChannel(
|
return StreamChannel(
|
||||||
channel: arg.channel,
|
channel: channelPageArgs.channel!,
|
||||||
initialMessageId: arg.initialMessage?.id,
|
initialMessageId: channelPageArgs.initialMessage?.id,
|
||||||
child: ChannelPage(
|
child: ChannelPage(
|
||||||
highlightInitialMessage: arg.initialMessage != null,
|
highlightInitialMessage:
|
||||||
|
channelPageArgs.initialMessage != null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -68,22 +76,31 @@ class AppRoutes {
|
|||||||
settings: const RouteSettings(name: Routes.NEW_GROUP_CHAT_DETAILS),
|
settings: const RouteSettings(name: Routes.NEW_GROUP_CHAT_DETAILS),
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
return GroupChatDetailsScreen(
|
return GroupChatDetailsScreen(
|
||||||
selectedUsers: args,
|
selectedUsers: args as List<User>?,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
case Routes.CHAT_INFO_SCREEN:
|
case Routes.CHAT_INFO_SCREEN:
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: Routes.CHAT_INFO_SCREEN),
|
settings: const RouteSettings(name: Routes.CHAT_INFO_SCREEN),
|
||||||
builder: (_) {
|
builder: (context) {
|
||||||
return ChatInfoScreen(
|
return ChatInfoScreen(
|
||||||
user: args,
|
user: args as User?,
|
||||||
|
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
case Routes.GROUP_INFO_SCREEN:
|
case Routes.GROUP_INFO_SCREEN:
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: Routes.GROUP_INFO_SCREEN),
|
settings: const RouteSettings(name: Routes.GROUP_INFO_SCREEN),
|
||||||
builder: (_) {
|
builder: (context) {
|
||||||
return GroupInfoScreen();
|
return GroupInfoScreen(
|
||||||
|
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
case Routes.CHANNEL_LIST_PAGE:
|
||||||
|
return MaterialPageRoute(
|
||||||
|
settings: const RouteSettings(name: Routes.CHANNEL_LIST_PAGE),
|
||||||
|
builder: (context) {
|
||||||
|
return ChannelListPage();
|
||||||
});
|
});
|
||||||
// Default case, should not reach here.
|
// Default case, should not reach here.
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ class Routes {
|
|||||||
static const String NEW_GROUP_CHAT_DETAILS = '/new_group_chat_details';
|
static const String NEW_GROUP_CHAT_DETAILS = '/new_group_chat_details';
|
||||||
static const String CHAT_INFO_SCREEN = '/chat_info_screen';
|
static const String CHAT_INFO_SCREEN = '/chat_info_screen';
|
||||||
static const String GROUP_INFO_SCREEN = '/group_info_screen';
|
static const String GROUP_INFO_SCREEN = '/group_info_screen';
|
||||||
|
static const String CHANNEL_LIST_PAGE = '/channel_list_page';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
class SearchTextField extends StatelessWidget {
|
class SearchTextField extends StatelessWidget {
|
||||||
final TextEditingController controller;
|
final TextEditingController? controller;
|
||||||
final ValueChanged<String> onChanged;
|
final ValueChanged<String>? onChanged;
|
||||||
final String hintText;
|
final String hintText;
|
||||||
final VoidCallback onTap;
|
final VoidCallback? onTap;
|
||||||
final bool showCloseButton;
|
final bool showCloseButton;
|
||||||
|
|
||||||
const SearchTextField({
|
const SearchTextField({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.controller,
|
required this.controller,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.hintText = 'Search',
|
this.hintText = 'Search',
|
||||||
@@ -22,9 +22,9 @@ class SearchTextField extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
height: 36,
|
height: 36,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: StreamChatTheme.of(context).colorTheme.white,
|
color: StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
|
color: StreamChatTheme.of(context).colorTheme.borders,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
),
|
),
|
||||||
@@ -48,7 +48,8 @@ class SearchTextField extends StatelessWidget {
|
|||||||
right: 8,
|
right: 8,
|
||||||
),
|
),
|
||||||
child: StreamSvgIcon.search(
|
child: StreamSvgIcon.search(
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.textHighEmphasis,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -56,7 +57,7 @@ class SearchTextField extends StatelessWidget {
|
|||||||
hintStyle: StreamChatTheme.of(context).textTheme.body.copyWith(
|
hintStyle: StreamChatTheme.of(context).textTheme.body.copyWith(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.black
|
.textHighEmphasis
|
||||||
.withOpacity(.5)),
|
.withOpacity(.5)),
|
||||||
contentPadding: const EdgeInsets.all(0),
|
contentPadding: const EdgeInsets.all(0),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
@@ -76,11 +77,11 @@ class SearchTextField extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
splashRadius: 24,
|
splashRadius: 24,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (controller.text.isNotEmpty) {
|
if (controller!.text.isNotEmpty) {
|
||||||
Future.microtask(
|
Future.microtask(
|
||||||
() => [
|
() => [
|
||||||
controller.clear(),
|
controller!.clear(),
|
||||||
if (onChanged != null) onChanged(''),
|
if (onChanged != null) onChanged!(''),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:lottie/lottie.dart';
|
||||||
|
|
||||||
|
mixin SplashScreenStateMixin<T extends StatefulWidget> on State<T>
|
||||||
|
implements TickerProvider {
|
||||||
|
late Animation<double> animation, scaleAnimation;
|
||||||
|
late AnimationController _animationController, _scaleAnimationController;
|
||||||
|
late Animation<Color?> colorAnimation;
|
||||||
|
bool animationCompleted = false;
|
||||||
|
|
||||||
|
void _createAnimations() {
|
||||||
|
_scaleAnimationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
value: 0,
|
||||||
|
duration: Duration(
|
||||||
|
milliseconds: 500,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
scaleAnimation = Tween(
|
||||||
|
begin: 1.0,
|
||||||
|
end: 1.5,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _scaleAnimationController,
|
||||||
|
curve: Curves.easeInOutBack,
|
||||||
|
));
|
||||||
|
|
||||||
|
_animationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: Duration(
|
||||||
|
milliseconds: 1000,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
animation = Tween(
|
||||||
|
begin: 0.0,
|
||||||
|
end: 1000.0,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
));
|
||||||
|
colorAnimation = ColorTween(
|
||||||
|
begin: Color(0xff005FFF),
|
||||||
|
end: Color(0xff005FFF),
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
));
|
||||||
|
colorAnimation = ColorTween(
|
||||||
|
begin: Color(0xff005FFF),
|
||||||
|
end: Colors.transparent,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void forwardAnimations() {
|
||||||
|
_scaleAnimationController.forward().whenComplete(() {
|
||||||
|
_animationController.forward();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildAnimation() => Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: scaleAnimation,
|
||||||
|
builder: (context, _) {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: scaleAnimation.value,
|
||||||
|
child: AnimatedBuilder(
|
||||||
|
animation: colorAnimation,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
constraints: BoxConstraints.expand(),
|
||||||
|
color: colorAnimation.value,
|
||||||
|
child: !_animationController.isAnimating
|
||||||
|
? Lottie.asset(
|
||||||
|
'assets/floating_boat.json',
|
||||||
|
alignment: Alignment.center,
|
||||||
|
)
|
||||||
|
: SizedBox(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: animation,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: animation.value,
|
||||||
|
child: Container(
|
||||||
|
width: 1.0,
|
||||||
|
height: 1.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white
|
||||||
|
.withOpacity(1 - _animationController.value),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_createAnimations();
|
||||||
|
_animationController.addStatusListener((status) {
|
||||||
|
if (status == AnimationStatus.completed) {
|
||||||
|
setState(() {
|
||||||
|
animationCompleted = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|||||||
|
|
||||||
class StreamVersion extends StatelessWidget {
|
class StreamVersion extends StatelessWidget {
|
||||||
const StreamVersion({
|
const StreamVersion({
|
||||||
Key key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -20,16 +20,16 @@ class StreamVersion extends StatelessWidget {
|
|||||||
return SizedBox();
|
return SizedBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
final pubspec = snapshot.data;
|
final pubspec = snapshot.data!;
|
||||||
final yaml = loadYaml(pubspec);
|
final yaml = loadYaml(pubspec);
|
||||||
final streamChatDep =
|
final streamChatDep =
|
||||||
yaml['packages']['stream_chat_flutter']['version'];
|
yaml['packages']['stream_chat_flutter']['version'];
|
||||||
|
|
||||||
return Text(
|
return Text(
|
||||||
'Stream SDK v ${streamChatDep}',
|
'Stream SDK v $streamChatDep',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
color: StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
class ThreadPage extends StatefulWidget {
|
||||||
|
final Message? parent;
|
||||||
|
final int? initialScrollIndex;
|
||||||
|
final double? initialAlignment;
|
||||||
|
|
||||||
|
ThreadPage({
|
||||||
|
Key? key,
|
||||||
|
this.parent,
|
||||||
|
this.initialScrollIndex,
|
||||||
|
this.initialAlignment,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ThreadPageState createState() => _ThreadPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ThreadPageState extends State<ThreadPage> {
|
||||||
|
Message? _quotedMessage;
|
||||||
|
FocusNode _focusNode = FocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reply(Message message) {
|
||||||
|
setState(() => _quotedMessage = message);
|
||||||
|
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
|
||||||
|
appBar: ThreadHeader(
|
||||||
|
parent: widget.parent!,
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: MessageListView(
|
||||||
|
parentMessage: widget.parent,
|
||||||
|
initialScrollIndex: widget.initialScrollIndex,
|
||||||
|
initialAlignment: widget.initialAlignment,
|
||||||
|
onMessageSwiped: _reply,
|
||||||
|
messageBuilder: (context, details, messages, defaultMessage) {
|
||||||
|
return defaultMessage.copyWith(
|
||||||
|
onReplyTap: _reply,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
pinPermissions: ['owner', 'admin', 'member'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.parent!.type != 'deleted')
|
||||||
|
MessageInput(
|
||||||
|
parentMessage: widget.parent,
|
||||||
|
focusNode: _focusNode,
|
||||||
|
quotedMessage: _quotedMessage,
|
||||||
|
onQuotedMessageCleared: () {
|
||||||
|
setState(() => _quotedMessage = null);
|
||||||
|
_focusNode.unfocus();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import 'package:example/routes/routes.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
import 'channel_page.dart';
|
||||||
|
|
||||||
|
class UserMentionsPage extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final user = StreamChat.of(context).user!;
|
||||||
|
return MessageSearchBloc(
|
||||||
|
child: MessageSearchListView(
|
||||||
|
filters: Filter.in_('members', [user.id]),
|
||||||
|
messageFilters: Filter.custom(
|
||||||
|
operator: r'$contains',
|
||||||
|
key: 'mentioned_users.id',
|
||||||
|
value: user.id,
|
||||||
|
),
|
||||||
|
sortOptions: [
|
||||||
|
SortOption(
|
||||||
|
'created_at',
|
||||||
|
direction: SortOption.ASC,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
paginationParams: PaginationParams(limit: 20),
|
||||||
|
showResultCount: false,
|
||||||
|
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.mentions(
|
||||||
|
size: 96,
|
||||||
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.disabled,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'No mentions exist yet...',
|
||||||
|
style: StreamChatTheme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.body
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.textLowEmphasis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onItemTap: (messageResponse) async {
|
||||||
|
final client = StreamChat.of(context).client;
|
||||||
|
final message = messageResponse.message;
|
||||||
|
final channel = client.channel(
|
||||||
|
messageResponse.channel!.type,
|
||||||
|
id: messageResponse.channel!.id,
|
||||||
|
);
|
||||||
|
if (channel.state == null) {
|
||||||
|
await channel.watch();
|
||||||
|
}
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
Routes.CHANNEL_PAGE,
|
||||||
|
arguments: ChannelPageArgs(
|
||||||
|
channel: channel,
|
||||||
|
initialMessage: message,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +1,37 @@
|
|||||||
name: example
|
name: example
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.5.6
|
version: 1.6.1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.2.2 <3.0.0"
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter_app_badger: ^1.1.2
|
flutter_app_badger: ^1.2.0
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
stream_chat_flutter: ^1.5.2
|
stream_chat_flutter:
|
||||||
stream_chat_persistence: ^1.5.1
|
git:
|
||||||
flutter_local_notifications: ^2.0.2
|
url: https://github.com/GetStream/stream-chat-flutter.git
|
||||||
flutter_svg: ^0.19.3
|
ref: develop
|
||||||
flutter_secure_storage: ^3.3.5
|
path: packages/stream_chat_flutter
|
||||||
yaml: ^2.2.1
|
stream_chat_persistence:
|
||||||
uuid: ^2.2.2
|
git:
|
||||||
streaming_shared_preferences: ^1.0.2
|
url: https://github.com/GetStream/stream-chat-flutter.git
|
||||||
lottie: ^0.7.0+1
|
ref: develop
|
||||||
|
path: packages/stream_chat_persistence
|
||||||
|
flutter_local_notifications: ^5.0.0+4
|
||||||
|
flutter_svg: ^0.22.0
|
||||||
|
flutter_secure_storage: ^4.2.0
|
||||||
|
yaml: ^3.1.0
|
||||||
|
uuid: ^3.0.4
|
||||||
|
streaming_shared_preferences: ^2.0.0
|
||||||
|
lottie: ^1.0.1
|
||||||
|
collection: ^1.15.0-nullsafety.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_launcher_icons: ^0.8.1
|
flutter_launcher_icons: ^0.9.0
|
||||||
test: any
|
test: any
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
assets:
|
assets:
|
||||||
- assets/
|
- assets/
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
// This is a basic Flutter widget test.
|
|
||||||
//
|
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
||||||
// utility that Flutter provides. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
import 'package:stream_chat_v1/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
|
||||||
// Build our app and trigger a frame.
|
|
||||||
await tester.pumpWidget(MyApp());
|
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
|
||||||
expect(find.text('0'), findsOneWidget);
|
|
||||||
expect(find.text('1'), findsNothing);
|
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame.
|
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Verify that our counter has incremented.
|
|
||||||
expect(find.text('0'), findsNothing);
|
|
||||||
expect(find.text('1'), findsOneWidget);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user