Merge pull request #1295 from GetStream/3rd-party-video-integration
This commit is contained in:
+2
-2
@@ -54,11 +54,11 @@ scripts:
|
|||||||
description: Build all generated files for Dart & Flutter packages in this project.
|
description: Build all generated files for Dart & Flutter packages in this project.
|
||||||
|
|
||||||
generate:dart:
|
generate:dart:
|
||||||
run: melos exec -c 1 --depends-on="build_runner" --no-flutter -- "dart run build_runner build --delete-conflicting-outputs"
|
run: melos exec -c 1 --depends-on="build_runner" --no-flutter -- "dart run build_runner build --delete-conflicting-outputs --enable-experiment=super-parameters,enhanced-enums"
|
||||||
description: Build all generated files for Dart packages in this project.
|
description: Build all generated files for Dart packages in this project.
|
||||||
|
|
||||||
generate:flutter:
|
generate:flutter:
|
||||||
run: melos exec -c 1 --depends-on="build_runner" --flutter -- "flutter pub run build_runner build --delete-conflicting-outputs"
|
run: melos exec -c 1 --depends-on="build_runner" --flutter -- "flutter run build_runner build --delete-conflicting-outputs --enable-experiment=super-parameters,enhanced-enums"
|
||||||
description: Build all generated files for Flutter packages in this project.
|
description: Build all generated files for Flutter packages in this project.
|
||||||
|
|
||||||
test:all:
|
test:all:
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
## Upcoming
|
## Upcoming
|
||||||
|
|
||||||
|
✅ Added
|
||||||
|
|
||||||
|
- Added `StreamChatClient.getCallToken` and `StreamChatClient.createCall` methods.
|
||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
|
|
||||||
- Only listen to client events when the user is connected to the websocket.
|
- Only listen to client events when the user is connected to the websocket.
|
||||||
|
|||||||
@@ -592,6 +592,25 @@ class StreamChatClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a token associated with the [callId].
|
||||||
|
Future<CallTokenPayload> getCallToken(String callId) async =>
|
||||||
|
_chatApi.call.getCallToken(callId);
|
||||||
|
|
||||||
|
/// Creates a new call.
|
||||||
|
Future<CreateCallPayload> createCall({
|
||||||
|
required String callId,
|
||||||
|
required String callType,
|
||||||
|
required String channelType,
|
||||||
|
required String channelId,
|
||||||
|
}) {
|
||||||
|
return _chatApi.call.createCall(
|
||||||
|
callId: callId,
|
||||||
|
callType: callType,
|
||||||
|
channelType: channelType,
|
||||||
|
channelId: channelId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Requests channels with a given query from the API.
|
/// Requests channels with a given query from the API.
|
||||||
Future<List<Channel>> queryChannelsOnline({
|
Future<List<Channel>> queryChannelsOnline({
|
||||||
Filter? filter,
|
Filter? filter,
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import 'package:stream_chat/src/core/api/responses.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||||
|
|
||||||
|
/// Defines the api dedicated to call operations.
|
||||||
|
class CallApi {
|
||||||
|
/// Initialize a new call api
|
||||||
|
CallApi(this._client);
|
||||||
|
|
||||||
|
final StreamHttpClient _client;
|
||||||
|
|
||||||
|
/// Returns a token dedicated to the [callId]
|
||||||
|
Future<CallTokenPayload> getCallToken(String callId) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/calls/$callId',
|
||||||
|
data: {},
|
||||||
|
);
|
||||||
|
// return response.data;
|
||||||
|
return CallTokenPayload.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new call
|
||||||
|
Future<CreateCallPayload> createCall({
|
||||||
|
required String callId,
|
||||||
|
required String callType,
|
||||||
|
required String channelType,
|
||||||
|
required String channelId,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
_getChannelUrl(channelId, channelType),
|
||||||
|
data: {
|
||||||
|
'id': callId,
|
||||||
|
'type': callType,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// return response.data;
|
||||||
|
return CreateCallPayload.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getChannelUrl(String channelId, String channelType) =>
|
||||||
|
'/channels/$channelType/$channelId/call';
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/client/client.dart';
|
import 'package:stream_chat/src/client/client.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/call_api.dart';
|
||||||
import 'package:stream_chat/src/core/error/error.dart';
|
import 'package:stream_chat/src/core/error/error.dart';
|
||||||
import 'package:stream_chat/src/core/models/banned_user.dart';
|
import 'package:stream_chat/src/core/models/banned_user.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/call_payload.dart';
|
||||||
import 'package:stream_chat/src/core/models/channel_model.dart';
|
import 'package:stream_chat/src/core/models/channel_model.dart';
|
||||||
import 'package:stream_chat/src/core/models/channel_state.dart';
|
import 'package:stream_chat/src/core/models/channel_state.dart';
|
||||||
import 'package:stream_chat/src/core/models/device.dart';
|
import 'package:stream_chat/src/core/models/device.dart';
|
||||||
@@ -495,3 +497,31 @@ class OGAttachmentResponse extends _BaseResponse {
|
|||||||
static OGAttachmentResponse fromJson(Map<String, dynamic> json) =>
|
static OGAttachmentResponse fromJson(Map<String, dynamic> json) =>
|
||||||
_$OGAttachmentResponseFromJson(json);
|
_$OGAttachmentResponseFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The response to [CallApi.getCallToken]
|
||||||
|
@JsonSerializable(createToJson: false)
|
||||||
|
class CallTokenPayload extends _BaseResponse {
|
||||||
|
/// Create a new instance from a [json].
|
||||||
|
static CallTokenPayload fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CallTokenPayloadFromJson(json);
|
||||||
|
|
||||||
|
/// The token to use for the call.
|
||||||
|
String? token;
|
||||||
|
|
||||||
|
/// The user id specific to Agora.
|
||||||
|
int? agoraUid;
|
||||||
|
|
||||||
|
/// The appId specific to Agora.
|
||||||
|
String? agoraAppId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The response to [CallApi.createCall]
|
||||||
|
@JsonSerializable(createToJson: false)
|
||||||
|
class CreateCallPayload extends _BaseResponse {
|
||||||
|
/// Create a new instance from a [json].
|
||||||
|
static CreateCallPayload fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CreateCallPayloadFromJson(json);
|
||||||
|
|
||||||
|
/// The call object.
|
||||||
|
CallPayload? call;
|
||||||
|
}
|
||||||
|
|||||||
@@ -297,3 +297,17 @@ OGAttachmentResponse _$OGAttachmentResponseFromJson(
|
|||||||
..title = json['title'] as String?
|
..title = json['title'] as String?
|
||||||
..titleLink = json['title_link'] as String?
|
..titleLink = json['title_link'] as String?
|
||||||
..type = json['type'] as String?;
|
..type = json['type'] as String?;
|
||||||
|
|
||||||
|
CallTokenPayload _$CallTokenPayloadFromJson(Map<String, dynamic> json) =>
|
||||||
|
CallTokenPayload()
|
||||||
|
..duration = json['duration'] as String?
|
||||||
|
..token = json['token'] as String?
|
||||||
|
..agoraUid = json['agora_uid'] as int?
|
||||||
|
..agoraAppId = json['agora_app_id'] as String?;
|
||||||
|
|
||||||
|
CreateCallPayload _$CreateCallPayloadFromJson(Map<String, dynamic> json) =>
|
||||||
|
CreateCallPayload()
|
||||||
|
..duration = json['duration'] as String?
|
||||||
|
..call = json['call'] == null
|
||||||
|
? null
|
||||||
|
: CallPayload.fromJson(json['call'] as Map<String, dynamic>);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:stream_chat/src/core/api/attachment_file_uploader.dart';
|
import 'package:stream_chat/src/core/api/attachment_file_uploader.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/call_api.dart';
|
||||||
import 'package:stream_chat/src/core/api/channel_api.dart';
|
import 'package:stream_chat/src/core/api/channel_api.dart';
|
||||||
import 'package:stream_chat/src/core/api/device_api.dart';
|
import 'package:stream_chat/src/core/api/device_api.dart';
|
||||||
import 'package:stream_chat/src/core/api/general_api.dart';
|
import 'package:stream_chat/src/core/api/general_api.dart';
|
||||||
@@ -53,6 +54,11 @@ class StreamChatApi {
|
|||||||
/// Api dedicated to message operations
|
/// Api dedicated to message operations
|
||||||
MessageApi get message => _message ??= MessageApi(_client);
|
MessageApi get message => _message ??= MessageApi(_client);
|
||||||
|
|
||||||
|
CallApi? _call;
|
||||||
|
|
||||||
|
/// Api dedicated to call operations
|
||||||
|
CallApi get call => _call ??= CallApi(_client);
|
||||||
|
|
||||||
ChannelApi? _channel;
|
ChannelApi? _channel;
|
||||||
|
|
||||||
/// Api dedicated to channel operations
|
/// Api dedicated to channel operations
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'call_payload.g.dart';
|
||||||
|
|
||||||
|
/// Model containing the information about a call.
|
||||||
|
@JsonSerializable(createToJson: false)
|
||||||
|
class CallPayload extends Equatable {
|
||||||
|
/// Create a new instance.
|
||||||
|
const CallPayload({
|
||||||
|
required this.id,
|
||||||
|
required this.provider,
|
||||||
|
this.agora,
|
||||||
|
this.hms,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Create a new instance from a [json].
|
||||||
|
factory CallPayload.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CallPayloadFromJson(json);
|
||||||
|
|
||||||
|
/// The call id.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
/// The call provider.
|
||||||
|
final String provider;
|
||||||
|
|
||||||
|
/// The payload specific to Agora.
|
||||||
|
final AgoraPayload? agora;
|
||||||
|
|
||||||
|
/// The payload specific to 100ms.
|
||||||
|
final HMSPayload? hms;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [id, provider, agora, hms];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Payload for Agora call.
|
||||||
|
@JsonSerializable(createToJson: false)
|
||||||
|
class AgoraPayload extends Equatable {
|
||||||
|
/// Create a new instance.
|
||||||
|
const AgoraPayload({required this.channel});
|
||||||
|
|
||||||
|
/// Create a new instance from a [json].
|
||||||
|
factory AgoraPayload.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$AgoraPayloadFromJson(json);
|
||||||
|
|
||||||
|
/// The Agora channel.
|
||||||
|
final String channel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [channel];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Payload for 100ms call.
|
||||||
|
@JsonSerializable(createToJson: false)
|
||||||
|
class HMSPayload extends Equatable {
|
||||||
|
/// Create a new instance.
|
||||||
|
const HMSPayload({required this.roomId, required this.roomName});
|
||||||
|
|
||||||
|
/// Create a new instance from a [json].
|
||||||
|
factory HMSPayload.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$HMSPayloadFromJson(json);
|
||||||
|
|
||||||
|
/// The id of the 100ms room.
|
||||||
|
final String roomId;
|
||||||
|
|
||||||
|
/// The name of the 100ms room.
|
||||||
|
final String roomName;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [roomId, roomName];
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'call_payload.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
CallPayload _$CallPayloadFromJson(Map<String, dynamic> json) => CallPayload(
|
||||||
|
id: json['id'] as String,
|
||||||
|
provider: json['provider'] as String,
|
||||||
|
agora: json['agora'] == null
|
||||||
|
? null
|
||||||
|
: AgoraPayload.fromJson(json['agora'] as Map<String, dynamic>),
|
||||||
|
hms: json['hms'] == null
|
||||||
|
? null
|
||||||
|
: HMSPayload.fromJson(json['hms'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
AgoraPayload _$AgoraPayloadFromJson(Map<String, dynamic> json) => AgoraPayload(
|
||||||
|
channel: json['channel'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
HMSPayload _$HMSPayloadFromJson(Map<String, dynamic> json) => HMSPayload(
|
||||||
|
roomId: json['room_id'] as String,
|
||||||
|
roomName: json['room_name'] as String,
|
||||||
|
);
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/call_api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../../mocks.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Response successResponse(String path, {Object? data}) => Response(
|
||||||
|
data: data,
|
||||||
|
requestOptions: RequestOptions(path: path),
|
||||||
|
statusCode: 200,
|
||||||
|
);
|
||||||
|
|
||||||
|
late final client = MockHttpClient();
|
||||||
|
late CallApi callApi;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
callApi = CallApi(client);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getCallToken should work', () async {
|
||||||
|
const callId = 'test-call-id';
|
||||||
|
const path = '/calls/$callId';
|
||||||
|
|
||||||
|
when(() => client.post(path, data: {})).thenAnswer(
|
||||||
|
(_) async => successResponse(path, data: <String, dynamic>{}));
|
||||||
|
|
||||||
|
final res = await callApi.getCallToken(callId);
|
||||||
|
|
||||||
|
expect(res, isNotNull);
|
||||||
|
|
||||||
|
verify(() => client.post(path, data: any(named: 'data'))).called(1);
|
||||||
|
verifyNoMoreInteractions(client);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createCall should work', () async {
|
||||||
|
const callId = 'test-call-id';
|
||||||
|
const callType = 'test-call-type';
|
||||||
|
const channelType = 'test-channel-type';
|
||||||
|
const channelId = 'test-channel-id';
|
||||||
|
const path = '/channels/$channelType/$channelId/call';
|
||||||
|
|
||||||
|
when(() => client.post(
|
||||||
|
path,
|
||||||
|
data: {
|
||||||
|
'id': callId,
|
||||||
|
'type': callType,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.thenAnswer(
|
||||||
|
(_) async => successResponse(path, data: <String, dynamic>{}));
|
||||||
|
|
||||||
|
final res = await callApi.createCall(
|
||||||
|
callId: callId,
|
||||||
|
callType: callType,
|
||||||
|
channelType: channelType,
|
||||||
|
channelId: channelId,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res, isNotNull);
|
||||||
|
|
||||||
|
verify(() => client.post(path, data: any(named: 'data'))).called(1);
|
||||||
|
verifyNoMoreInteractions(client);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -26,6 +26,56 @@ void main() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test('copyWith', () {
|
||||||
|
final params = PaginationParams(
|
||||||
|
offset: 10,
|
||||||
|
limit: 20,
|
||||||
|
createdAtAfter: DateTime.now(),
|
||||||
|
createdAtAfterOrEqual: DateTime.now(),
|
||||||
|
createdAtAround: DateTime.now(),
|
||||||
|
createdAtBefore: DateTime.now(),
|
||||||
|
createdAtBeforeOrEqual: DateTime.now(),
|
||||||
|
greaterThan: 'greater-than',
|
||||||
|
greaterThanOrEqual: 'greater-than-or-equal',
|
||||||
|
lessThan: 'less-than',
|
||||||
|
lessThanOrEqual: 'less-than-or-equal',
|
||||||
|
idAround: 'id-around',
|
||||||
|
);
|
||||||
|
|
||||||
|
final sameOld = params.copyWith();
|
||||||
|
expect(sameOld, equals(params));
|
||||||
|
|
||||||
|
final newDateTime = DateTime.now().add(const Duration(days: 2));
|
||||||
|
const newTestString = 'test';
|
||||||
|
final newParams = params.copyWith(
|
||||||
|
limit: 2,
|
||||||
|
offset: 2,
|
||||||
|
createdAtAfter: newDateTime,
|
||||||
|
createdAtAfterOrEqual: newDateTime,
|
||||||
|
createdAtAround: newDateTime,
|
||||||
|
createdAtBefore: newDateTime,
|
||||||
|
createdAtBeforeOrEqual: newDateTime,
|
||||||
|
greaterThan: newTestString,
|
||||||
|
greaterThanOrEqual: newTestString,
|
||||||
|
lessThan: newTestString,
|
||||||
|
lessThanOrEqual: newTestString,
|
||||||
|
idAround: newTestString,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(newParams.limit, 2);
|
||||||
|
expect(newParams.offset, 2);
|
||||||
|
expect(newParams.createdAtAfter, newDateTime);
|
||||||
|
expect(newParams.createdAtAfterOrEqual, newDateTime);
|
||||||
|
expect(newParams.createdAtAround, newDateTime);
|
||||||
|
expect(newParams.createdAtBefore, newDateTime);
|
||||||
|
expect(newParams.createdAtBeforeOrEqual, newDateTime);
|
||||||
|
expect(newParams.greaterThan, newTestString);
|
||||||
|
expect(newParams.greaterThanOrEqual, newTestString);
|
||||||
|
expect(newParams.lessThan, newTestString);
|
||||||
|
expect(newParams.lessThanOrEqual, newTestString);
|
||||||
|
expect(newParams.idAround, newTestString);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:stream_chat/src/core/models/call_payload.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
@@ -4340,5 +4341,31 @@ void main() {
|
|||||||
expect(response.members, isA<List<Member>>());
|
expect(response.members, isA<List<Member>>());
|
||||||
expect(response.message, isA<Message>());
|
expect(response.message, isA<Message>());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('CallTokenPayload', () {
|
||||||
|
const jsonExample = '''
|
||||||
|
{"duration": "3ms",
|
||||||
|
"agora_app_id":"test",
|
||||||
|
"agora_uid": 12,
|
||||||
|
"token": "token"}
|
||||||
|
''';
|
||||||
|
final response = CallTokenPayload.fromJson(json.decode(jsonExample));
|
||||||
|
expect(response.agoraAppId, isA<String>());
|
||||||
|
expect(response.agoraUid, isA<int>());
|
||||||
|
expect(response.token, isA<String>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CreateCallPayload', () {
|
||||||
|
const jsonExample = '''
|
||||||
|
{"call":
|
||||||
|
{"id":"test",
|
||||||
|
"provider": "test",
|
||||||
|
"agora": {"channel":"test"},
|
||||||
|
"hms":{"room_id":"test", "room_name":"test"}
|
||||||
|
}}
|
||||||
|
''';
|
||||||
|
final response = CreateCallPayload.fromJson(json.decode(jsonExample));
|
||||||
|
expect(response.call, isA<CallPayload>());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:stream_chat/src/core/models/call_payload.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('CallPayload', () {
|
||||||
|
const jsonExample = '''
|
||||||
|
{"id":"test",
|
||||||
|
"provider": "test",
|
||||||
|
"agora": {"channel":"test"},
|
||||||
|
"hms":{"room_id":"test", "room_name":"test"}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
final response = CallPayload.fromJson(json.decode(jsonExample));
|
||||||
|
expect(response.agora, isA<AgoraPayload>());
|
||||||
|
expect(response.hms, isA<HMSPayload>());
|
||||||
|
expect(response.id, isA<String>());
|
||||||
|
expect(response.provider, isA<String>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AgoraPayload', () {
|
||||||
|
const jsonExample = '''
|
||||||
|
{"channel":"test"}
|
||||||
|
''';
|
||||||
|
final response = AgoraPayload.fromJson(json.decode(jsonExample));
|
||||||
|
expect(response.channel, isA<String>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('HMSPayload', () {
|
||||||
|
const jsonExample = '''
|
||||||
|
{"room_id":"test", "room_name":"test"}
|
||||||
|
''';
|
||||||
|
final response = HMSPayload.fromJson(json.decode(jsonExample));
|
||||||
|
expect(response.roomId, isA<String>());
|
||||||
|
expect(response.roomName, isA<String>());
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user