💄 Fixes of call and optimizations
This commit is contained in:
@@ -355,6 +355,8 @@
|
|||||||
"postTitle": "Title",
|
"postTitle": "Title",
|
||||||
"postDescription": "Description",
|
"postDescription": "Description",
|
||||||
"call": "Call",
|
"call": "Call",
|
||||||
|
"callLeave": "Leave",
|
||||||
|
"callEnd": "End this call",
|
||||||
"done": "Done",
|
"done": "Done",
|
||||||
"loginResetPasswordSent": "Password reset link sent, please check your email inbox.",
|
"loginResetPasswordSent": "Password reset link sent, please check your email inbox.",
|
||||||
"accountDeletion": "Delete Account",
|
"accountDeletion": "Delete Account",
|
||||||
@@ -712,7 +714,7 @@
|
|||||||
"translating": "Translating",
|
"translating": "Translating",
|
||||||
"translated": "Translated",
|
"translated": "Translated",
|
||||||
"reactionThumbUp": "Thumbs Up",
|
"reactionThumbUp": "Thumbs Up",
|
||||||
"reactionThumbDown": "Thumbs Down",
|
"reactionThumbDown": "Thumbs Down",
|
||||||
"reactionJustOkay": "Just Okay",
|
"reactionJustOkay": "Just Okay",
|
||||||
"reactionCry": "Cry",
|
"reactionCry": "Cry",
|
||||||
"reactionConfuse": "Confused",
|
"reactionConfuse": "Confused",
|
||||||
@@ -721,5 +723,8 @@
|
|||||||
"reactionAngry": "Angry",
|
"reactionAngry": "Angry",
|
||||||
"reactionParty": "Party",
|
"reactionParty": "Party",
|
||||||
"reactionPray": "Pray",
|
"reactionPray": "Pray",
|
||||||
"reactionHeart": "Heart"
|
"reactionHeart": "Heart",
|
||||||
|
"selectMicrophone": "Select Microphone",
|
||||||
|
"selectCamera": "Select Camera",
|
||||||
|
"switchedTo": "Switched to {}"
|
||||||
}
|
}
|
||||||
|
@@ -162,8 +162,6 @@ sealed class CallParticipant with _$CallParticipant {
|
|||||||
required String identity,
|
required String identity,
|
||||||
required String name,
|
required String name,
|
||||||
required DateTime joinedAt,
|
required DateTime joinedAt,
|
||||||
required String? accountId,
|
|
||||||
required SnChatMember? profile,
|
|
||||||
}) = _CallParticipant;
|
}) = _CallParticipant;
|
||||||
|
|
||||||
factory CallParticipant.fromJson(Map<String, dynamic> json) =>
|
factory CallParticipant.fromJson(Map<String, dynamic> json) =>
|
||||||
|
@@ -2498,7 +2498,7 @@ as List<CallParticipant>,
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$CallParticipant {
|
mixin _$CallParticipant {
|
||||||
|
|
||||||
String get identity; String get name; DateTime get joinedAt; String? get accountId; SnChatMember? get profile;
|
String get identity; String get name; DateTime get joinedAt;
|
||||||
/// Create a copy of CallParticipant
|
/// Create a copy of CallParticipant
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -2511,16 +2511,16 @@ $CallParticipantCopyWith<CallParticipant> get copyWith => _$CallParticipantCopyW
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.profile, profile) || other.profile == profile));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,identity,name,joinedAt,accountId,profile);
|
int get hashCode => Object.hash(runtimeType,identity,name,joinedAt);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt, accountId: $accountId, profile: $profile)';
|
return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2531,11 +2531,11 @@ abstract mixin class $CallParticipantCopyWith<$Res> {
|
|||||||
factory $CallParticipantCopyWith(CallParticipant value, $Res Function(CallParticipant) _then) = _$CallParticipantCopyWithImpl;
|
factory $CallParticipantCopyWith(CallParticipant value, $Res Function(CallParticipant) _then) = _$CallParticipantCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile
|
String identity, String name, DateTime joinedAt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
$SnChatMemberCopyWith<$Res>? get profile;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -2548,29 +2548,15 @@ class _$CallParticipantCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of CallParticipant
|
/// Create a copy of CallParticipant
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,Object? accountId = freezed,Object? profile = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable
|
identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
as String,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
as String,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
as DateTime,
|
||||||
as String?,profile: freezed == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnChatMember?,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
/// Create a copy of CallParticipant
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnChatMemberCopyWith<$Res>? get profile {
|
|
||||||
if (_self.profile == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnChatMemberCopyWith<$Res>(_self.profile!, (value) {
|
|
||||||
return _then(_self.copyWith(profile: value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2649,10 +2635,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String identity, String name, DateTime joinedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _CallParticipant() when $default != null:
|
case _CallParticipant() when $default != null:
|
||||||
return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.profile);case _:
|
return $default(_that.identity,_that.name,_that.joinedAt);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2670,10 +2656,10 @@ return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.p
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String identity, String name, DateTime joinedAt) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _CallParticipant():
|
case _CallParticipant():
|
||||||
return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.profile);}
|
return $default(_that.identity,_that.name,_that.joinedAt);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -2687,10 +2673,10 @@ return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.p
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String identity, String name, DateTime joinedAt)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _CallParticipant() when $default != null:
|
case _CallParticipant() when $default != null:
|
||||||
return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.profile);case _:
|
return $default(_that.identity,_that.name,_that.joinedAt);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2702,14 +2688,12 @@ return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.p
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _CallParticipant implements CallParticipant {
|
class _CallParticipant implements CallParticipant {
|
||||||
const _CallParticipant({required this.identity, required this.name, required this.joinedAt, required this.accountId, required this.profile});
|
const _CallParticipant({required this.identity, required this.name, required this.joinedAt});
|
||||||
factory _CallParticipant.fromJson(Map<String, dynamic> json) => _$CallParticipantFromJson(json);
|
factory _CallParticipant.fromJson(Map<String, dynamic> json) => _$CallParticipantFromJson(json);
|
||||||
|
|
||||||
@override final String identity;
|
@override final String identity;
|
||||||
@override final String name;
|
@override final String name;
|
||||||
@override final DateTime joinedAt;
|
@override final DateTime joinedAt;
|
||||||
@override final String? accountId;
|
|
||||||
@override final SnChatMember? profile;
|
|
||||||
|
|
||||||
/// Create a copy of CallParticipant
|
/// Create a copy of CallParticipant
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -2724,16 +2708,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.profile, profile) || other.profile == profile));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,identity,name,joinedAt,accountId,profile);
|
int get hashCode => Object.hash(runtimeType,identity,name,joinedAt);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt, accountId: $accountId, profile: $profile)';
|
return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2744,11 +2728,11 @@ abstract mixin class _$CallParticipantCopyWith<$Res> implements $CallParticipant
|
|||||||
factory _$CallParticipantCopyWith(_CallParticipant value, $Res Function(_CallParticipant) _then) = __$CallParticipantCopyWithImpl;
|
factory _$CallParticipantCopyWith(_CallParticipant value, $Res Function(_CallParticipant) _then) = __$CallParticipantCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile
|
String identity, String name, DateTime joinedAt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@override $SnChatMemberCopyWith<$Res>? get profile;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -2761,30 +2745,16 @@ class __$CallParticipantCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of CallParticipant
|
/// Create a copy of CallParticipant
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,Object? accountId = freezed,Object? profile = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,}) {
|
||||||
return _then(_CallParticipant(
|
return _then(_CallParticipant(
|
||||||
identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable
|
identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
as String,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
as String,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
as DateTime,
|
||||||
as String?,profile: freezed == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnChatMember?,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a copy of CallParticipant
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnChatMemberCopyWith<$Res>? get profile {
|
|
||||||
if (_self.profile == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnChatMemberCopyWith<$Res>(_self.profile!, (value) {
|
|
||||||
return _then(_self.copyWith(profile: value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -285,11 +285,6 @@ _CallParticipant _$CallParticipantFromJson(Map<String, dynamic> json) =>
|
|||||||
identity: json['identity'] as String,
|
identity: json['identity'] as String,
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
joinedAt: DateTime.parse(json['joined_at'] as String),
|
joinedAt: DateTime.parse(json['joined_at'] as String),
|
||||||
accountId: json['account_id'] as String?,
|
|
||||||
profile:
|
|
||||||
json['profile'] == null
|
|
||||||
? null
|
|
||||||
: SnChatMember.fromJson(json['profile'] as Map<String, dynamic>),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$CallParticipantToJson(_CallParticipant instance) =>
|
Map<String, dynamic> _$CallParticipantToJson(_CallParticipant instance) =>
|
||||||
@@ -297,8 +292,6 @@ Map<String, dynamic> _$CallParticipantToJson(_CallParticipant instance) =>
|
|||||||
'identity': instance.identity,
|
'identity': instance.identity,
|
||||||
'name': instance.name,
|
'name': instance.name,
|
||||||
'joined_at': instance.joinedAt.toIso8601String(),
|
'joined_at': instance.joinedAt.toIso8601String(),
|
||||||
'account_id': instance.accountId,
|
|
||||||
'profile': instance.profile?.toJson(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnRealtimeCall _$SnRealtimeCallFromJson(Map<String, dynamic> json) =>
|
_SnRealtimeCall _$SnRealtimeCallFromJson(Map<String, dynamic> json) =>
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
import 'package:island/pods/userinfo.dart';
|
import 'dart:async';
|
||||||
import 'package:island/screens/chat/chat.dart';
|
|
||||||
import 'package:island/widgets/chat/call_button.dart';
|
import 'package:island/widgets/chat/call_button.dart';
|
||||||
import 'package:livekit_client/livekit_client.dart';
|
import 'package:livekit_client/livekit_client.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/models/chat.dart';
|
import 'package:island/models/chat.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
|
||||||
|
|
||||||
part 'call.g.dart';
|
part 'call.g.dart';
|
||||||
part 'call.freezed.dart';
|
part 'call.freezed.dart';
|
||||||
@@ -42,7 +39,8 @@ sealed class CallParticipantLive with _$CallParticipantLive {
|
|||||||
}) = _CallParticipantLive;
|
}) = _CallParticipantLive;
|
||||||
|
|
||||||
bool get isSpeaking => remoteParticipant.isSpeaking;
|
bool get isSpeaking => remoteParticipant.isSpeaking;
|
||||||
bool get isMuted => remoteParticipant.isMuted;
|
bool get isMuted =>
|
||||||
|
remoteParticipant.isMuted || !remoteParticipant.isMicrophoneEnabled();
|
||||||
bool get isScreenSharing => remoteParticipant.isScreenShareEnabled();
|
bool get isScreenSharing => remoteParticipant.isScreenShareEnabled();
|
||||||
bool get isScreenSharingWithAudio =>
|
bool get isScreenSharingWithAudio =>
|
||||||
remoteParticipant.isScreenShareAudioEnabled();
|
remoteParticipant.isScreenShareAudioEnabled();
|
||||||
@@ -57,7 +55,6 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
LocalParticipant? _localParticipant;
|
LocalParticipant? _localParticipant;
|
||||||
List<CallParticipantLive> _participants = [];
|
List<CallParticipantLive> _participants = [];
|
||||||
final Map<String, CallParticipant> _participantInfoByIdentity = {};
|
final Map<String, CallParticipant> _participantInfoByIdentity = {};
|
||||||
StreamSubscription? _wsSubscription;
|
|
||||||
EventsListener? _roomListener;
|
EventsListener? _roomListener;
|
||||||
|
|
||||||
List<CallParticipantLive> get participants =>
|
List<CallParticipantLive> get participants =>
|
||||||
@@ -71,7 +68,6 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
@override
|
@override
|
||||||
CallState build() {
|
CallState build() {
|
||||||
// Subscribe to websocket updates
|
// Subscribe to websocket updates
|
||||||
_subscribeToParticipantsUpdate();
|
|
||||||
return const CallState(
|
return const CallState(
|
||||||
isConnected: false,
|
isConnected: false,
|
||||||
isMicrophoneEnabled: true,
|
isMicrophoneEnabled: true,
|
||||||
@@ -80,27 +76,6 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _subscribeToParticipantsUpdate() {
|
|
||||||
// Only subscribe once
|
|
||||||
if (_wsSubscription != null) return;
|
|
||||||
final ws = ref.read(websocketProvider);
|
|
||||||
_wsSubscription = ws.dataStream.listen((packet) {
|
|
||||||
if (packet.type == 'call.participants.update' && packet.data != null) {
|
|
||||||
final participantsData = packet.data!["participants"];
|
|
||||||
if (participantsData is List) {
|
|
||||||
final parsed =
|
|
||||||
participantsData
|
|
||||||
.map(
|
|
||||||
(e) =>
|
|
||||||
CallParticipant.fromJson(Map<String, dynamic>.from(e)),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
_updateLiveParticipants(parsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initRoomListeners() {
|
void _initRoomListeners() {
|
||||||
if (_room == null) return;
|
if (_room == null) return;
|
||||||
_roomListener?.dispose();
|
_roomListener?.dispose();
|
||||||
@@ -143,8 +118,6 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
identity: remote.identity,
|
identity: remote.identity,
|
||||||
name: remote.identity,
|
name: remote.identity,
|
||||||
joinedAt: DateTime.now(),
|
joinedAt: DateTime.now(),
|
||||||
accountId: null,
|
|
||||||
profile: null,
|
|
||||||
);
|
);
|
||||||
return CallParticipantLive(
|
return CallParticipantLive(
|
||||||
participant: match,
|
participant: match,
|
||||||
@@ -169,16 +142,12 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
if (idx != -1) return participants[idx];
|
if (idx != -1) return participants[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
final userInfo = ref.read(userInfoProvider);
|
|
||||||
final roomIdentity = ref.read(chatroomIdentityProvider(_roomId));
|
|
||||||
// Otherwise, use info from the identity map or fallback to minimal
|
// Otherwise, use info from the identity map or fallback to minimal
|
||||||
return _participantInfoByIdentity[_localParticipant!.identity] ??
|
return _participantInfoByIdentity[_localParticipant!.identity] ??
|
||||||
CallParticipant(
|
CallParticipant(
|
||||||
identity: _localParticipant!.identity,
|
identity: _localParticipant!.identity,
|
||||||
name: _localParticipant!.identity,
|
name: _localParticipant!.identity,
|
||||||
joinedAt: DateTime.now(),
|
joinedAt: DateTime.now(),
|
||||||
accountId: userInfo.value?.id,
|
|
||||||
profile: roomIdentity.value,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,6 +174,7 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
remoteParticipant: _localParticipant!,
|
remoteParticipant: _localParticipant!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
state = state.copyWith();
|
||||||
}
|
}
|
||||||
// Add remote participants
|
// Add remote participants
|
||||||
_participants.addAll(
|
_participants.addAll(
|
||||||
@@ -264,7 +234,8 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
duration: Duration(
|
duration: Duration(
|
||||||
milliseconds:
|
milliseconds:
|
||||||
(DateTime.now().millisecondsSinceEpoch -
|
(DateTime.now().millisecondsSinceEpoch -
|
||||||
(ongoingCall?.createdAt.millisecondsSinceEpoch ?? 0)),
|
(ongoingCall?.createdAt.millisecondsSinceEpoch ??
|
||||||
|
DateTime.now().millisecondsSinceEpoch)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -318,6 +289,7 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
stopOnMute: autostop,
|
stopOnMute: autostop,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
state = state.copyWith();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,6 +298,7 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
final target = !_localParticipant!.isCameraEnabled();
|
final target = !_localParticipant!.isCameraEnabled();
|
||||||
state = state.copyWith(isCameraEnabled: target);
|
state = state.copyWith(isCameraEnabled: target);
|
||||||
await _localParticipant!.setCameraEnabled(target);
|
await _localParticipant!.setCameraEnabled(target);
|
||||||
|
state = state.copyWith();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,6 +307,7 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
final target = !_localParticipant!.isScreenShareEnabled();
|
final target = !_localParticipant!.isScreenShareEnabled();
|
||||||
state = state.copyWith(isScreenSharing: target);
|
state = state.copyWith(isScreenSharing: target);
|
||||||
await _localParticipant!.setScreenShareEnabled(target);
|
await _localParticipant!.setScreenShareEnabled(target);
|
||||||
|
state = state.copyWith();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,7 +324,13 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_wsSubscription?.cancel();
|
state = state.copyWith(
|
||||||
|
error: null,
|
||||||
|
isConnected: false,
|
||||||
|
isMicrophoneEnabled: false,
|
||||||
|
isCameraEnabled: false,
|
||||||
|
isScreenSharing: false,
|
||||||
|
);
|
||||||
_roomListener?.dispose();
|
_roomListener?.dispose();
|
||||||
_room?.removeListener(_onRoomChange);
|
_room?.removeListener(_onRoomChange);
|
||||||
_room?.dispose();
|
_room?.dispose();
|
||||||
|
@@ -6,7 +6,7 @@ part of 'call.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$callNotifierHash() => r'107174cd6cfab6bfafe44f8c4a72a67bcb93217b';
|
String _$callNotifierHash() => r'e4312feadb5b34f186b5349a7ee8b671b842dafc';
|
||||||
|
|
||||||
/// See also [CallNotifier].
|
/// See also [CallNotifier].
|
||||||
@ProviderFor(CallNotifier)
|
@ProviderFor(CallNotifier)
|
||||||
|
@@ -4,7 +4,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/call.dart';
|
import 'package:island/pods/call.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/chat/call_button.dart';
|
import 'package:island/widgets/chat/call_button.dart';
|
||||||
import 'package:island/widgets/chat/call_overlay.dart';
|
import 'package:island/widgets/chat/call_overlay.dart';
|
||||||
@@ -21,14 +20,20 @@ class CallScreen extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final ongoingCall = ref.watch(ongoingCallProvider(roomId));
|
final ongoingCall = ref.watch(ongoingCallProvider(roomId));
|
||||||
final callState = ref.watch(callNotifierProvider);
|
final callState = ref.watch(callNotifierProvider);
|
||||||
final callNotifier = ref.read(callNotifierProvider.notifier);
|
final callNotifier = ref.watch(callNotifierProvider.notifier);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
callNotifier.joinRoom(roomId);
|
callNotifier.joinRoom(roomId);
|
||||||
return null;
|
return null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
final viewMode = useState<String>('grid');
|
final allAudioOnly = callNotifier.participants.every(
|
||||||
|
(p) =>
|
||||||
|
!(p.hasVideo &&
|
||||||
|
p.remoteParticipant.trackPublications.values.any(
|
||||||
|
(pub) => pub.track != null && pub.kind == TrackType.VIDEO,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
noBackground: false,
|
||||||
@@ -50,39 +55,50 @@ class CallScreen extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
Row(
|
if (!allAudioOnly)
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
SingleChildScrollView(
|
||||||
children: [
|
child: Row(
|
||||||
IconButton(
|
spacing: 4,
|
||||||
icon: Icon(Symbols.grid_view),
|
children: [
|
||||||
tooltip: 'Grid View',
|
for (final live in callNotifier.participants)
|
||||||
onPressed: () => viewMode.value = 'grid',
|
SpeakingRippleAvatar(
|
||||||
color:
|
isSpeaking: live.isSpeaking,
|
||||||
viewMode.value == 'grid'
|
isMuted: live.isMuted,
|
||||||
? Theme.of(context).colorScheme.primary
|
audioLevel: live.remoteParticipant.audioLevel,
|
||||||
: null,
|
identity: live.participant.identity,
|
||||||
|
size: 30,
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
IconButton(
|
),
|
||||||
icon: Icon(Symbols.view_agenda),
|
|
||||||
tooltip: 'Stage View',
|
|
||||||
onPressed: () => viewMode.value = 'stage',
|
|
||||||
color:
|
|
||||||
viewMode.value == 'stage'
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body:
|
body:
|
||||||
callState.error != null
|
callState.error != null
|
||||||
? Center(
|
? Center(
|
||||||
child: Text(
|
child: ConstrainedBox(
|
||||||
callState.error!,
|
constraints: const BoxConstraints(maxWidth: 320),
|
||||||
textAlign: TextAlign.center,
|
child: Column(
|
||||||
style: const TextStyle(color: Colors.red),
|
children: [
|
||||||
|
const Icon(Symbols.error_outline, size: 48),
|
||||||
|
const Gap(4),
|
||||||
|
Text(
|
||||||
|
callState.error!,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(color: Color(0xFF757575)),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
callNotifier.disconnect();
|
||||||
|
callNotifier.dispose();
|
||||||
|
callNotifier.joinRoom(roomId);
|
||||||
|
},
|
||||||
|
child: Text('retry').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
@@ -100,17 +116,8 @@ class CallScreen extends HookConsumerWidget {
|
|||||||
child: Text('No participants in call'),
|
child: Text('No participants in call'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final participants = callNotifier.participants;
|
final participants = callNotifier.participants;
|
||||||
final allAudioOnly = participants.every(
|
|
||||||
(p) =>
|
|
||||||
!(p.hasVideo &&
|
|
||||||
p.remoteParticipant.trackPublications.values
|
|
||||||
.any(
|
|
||||||
(pub) =>
|
|
||||||
pub.track != null &&
|
|
||||||
pub.kind == TrackType.VIDEO,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
if (allAudioOnly) {
|
if (allAudioOnly) {
|
||||||
// Audio-only: show avatars in a compact row
|
// Audio-only: show avatars in a compact row
|
||||||
return Center(
|
return Center(
|
||||||
@@ -123,138 +130,45 @@ class CallScreen extends HookConsumerWidget {
|
|||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: [
|
children: [
|
||||||
for (final live in participants)
|
for (final live in participants)
|
||||||
Padding(
|
SpeakingRippleAvatar(
|
||||||
padding: const EdgeInsets.symmetric(
|
isSpeaking: live.isSpeaking,
|
||||||
horizontal: 8,
|
isMuted: live.isMuted,
|
||||||
),
|
audioLevel:
|
||||||
child: SpeakingRippleAvatar(
|
live.remoteParticipant.audioLevel,
|
||||||
isSpeaking: live.isSpeaking,
|
identity: live.participant.identity,
|
||||||
audioLevel:
|
size: 72,
|
||||||
live.remoteParticipant.audioLevel,
|
).padding(horizontal: 4),
|
||||||
pictureId:
|
|
||||||
live
|
|
||||||
.participant
|
|
||||||
.profile
|
|
||||||
?.account
|
|
||||||
.profile
|
|
||||||
.picture
|
|
||||||
?.id,
|
|
||||||
size: 72,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (viewMode.value == 'stage') {
|
|
||||||
// Stage view: show main speaker(s) large, others in row
|
// Stage view: show main speaker(s) large, others in row
|
||||||
final mainSpeakers =
|
final mainSpeakers =
|
||||||
participants
|
participants
|
||||||
.where(
|
.where(
|
||||||
(p) => p
|
(p) => p
|
||||||
.remoteParticipant
|
.remoteParticipant
|
||||||
.trackPublications
|
.trackPublications
|
||||||
.values
|
.values
|
||||||
.any(
|
.any(
|
||||||
(pub) =>
|
(pub) =>
|
||||||
pub.track != null &&
|
pub.track != null &&
|
||||||
pub.kind == TrackType.VIDEO,
|
pub.kind == TrackType.VIDEO,
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
if (mainSpeakers.isEmpty && participants.isNotEmpty) {
|
|
||||||
mainSpeakers.add(participants.first);
|
|
||||||
}
|
|
||||||
final others =
|
|
||||||
participants
|
|
||||||
.where((p) => !mainSpeakers.contains(p))
|
|
||||||
.toList();
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
for (final speaker in mainSpeakers)
|
|
||||||
Expanded(
|
|
||||||
child:
|
|
||||||
AspectRatio(
|
|
||||||
aspectRatio: 16 / 9,
|
|
||||||
child: Card(
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(8),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
CallParticipantTile(
|
|
||||||
live: speaker,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).center(),
|
|
||||||
),
|
),
|
||||||
],
|
)
|
||||||
).padding(horizontal: 12),
|
.toList();
|
||||||
),
|
if (mainSpeakers.isEmpty && participants.isNotEmpty) {
|
||||||
if (others.isNotEmpty)
|
mainSpeakers.add(participants.first);
|
||||||
SizedBox(
|
|
||||||
height: 100,
|
|
||||||
child: ListView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
children: [
|
|
||||||
for (final other in others)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 8,
|
|
||||||
),
|
|
||||||
child: CallParticipantTile(
|
|
||||||
live: other,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// Default: grid view
|
return Column(
|
||||||
return GridView.builder(
|
children: [
|
||||||
padding: const EdgeInsets.symmetric(
|
for (final speaker in mainSpeakers)
|
||||||
horizontal: 12,
|
Expanded(
|
||||||
vertical: 8,
|
child: CallParticipantTile(live: speaker),
|
||||||
),
|
|
||||||
gridDelegate:
|
|
||||||
SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount:
|
|
||||||
isWidestScreen(context)
|
|
||||||
? 4
|
|
||||||
: isWiderScreen(context)
|
|
||||||
? 3
|
|
||||||
: 2,
|
|
||||||
childAspectRatio: 16 / 9,
|
|
||||||
crossAxisSpacing: 8,
|
|
||||||
mainAxisSpacing: 8,
|
|
||||||
),
|
),
|
||||||
itemCount: participants.length,
|
],
|
||||||
itemBuilder: (context, idx) {
|
|
||||||
final live = participants[idx];
|
|
||||||
return AspectRatio(
|
|
||||||
aspectRatio: 16 / 9,
|
|
||||||
child: Card(
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
child: Column(
|
|
||||||
children: [CallParticipantTile(live: live)],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).center();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@@ -21,7 +21,6 @@ import 'package:island/services/responsive.dart';
|
|||||||
import 'package:island/widgets/account/account_picker.dart';
|
import 'package:island/widgets/account/account_picker.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/chat/call_overlay.dart';
|
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/realms/selection_dropdown.dart';
|
import 'package:island/widgets/realms/selection_dropdown.dart';
|
||||||
@@ -346,91 +345,79 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
child: const Icon(Symbols.add),
|
child: const Icon(Symbols.add),
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: TabbedFabLocation(context),
|
floatingActionButtonLocation: TabbedFabLocation(context),
|
||||||
body: Stack(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Consumer(
|
||||||
children: [
|
builder: (context, ref, _) {
|
||||||
Consumer(
|
final summaryState = ref.watch(chatSummaryProvider);
|
||||||
builder: (context, ref, _) {
|
return summaryState.maybeWhen(
|
||||||
final summaryState = ref.watch(chatSummaryProvider);
|
loading:
|
||||||
return summaryState.maybeWhen(
|
() => const LinearProgressIndicator(
|
||||||
loading:
|
minHeight: 2,
|
||||||
() => const LinearProgressIndicator(
|
borderRadius: BorderRadius.zero,
|
||||||
minHeight: 2,
|
),
|
||||||
borderRadius: BorderRadius.zero,
|
orElse: () => const SizedBox.shrink(),
|
||||||
),
|
);
|
||||||
orElse: () => const SizedBox.shrink(),
|
},
|
||||||
);
|
),
|
||||||
},
|
Expanded(
|
||||||
),
|
child: chats.when(
|
||||||
Expanded(
|
data:
|
||||||
child: chats.when(
|
(items) => RefreshIndicator(
|
||||||
data:
|
onRefresh:
|
||||||
(items) => RefreshIndicator(
|
() => Future.sync(() {
|
||||||
onRefresh:
|
ref.invalidate(chatroomsJoinedProvider);
|
||||||
() => Future.sync(() {
|
}),
|
||||||
ref.invalidate(chatroomsJoinedProvider);
|
child: ListView.builder(
|
||||||
}),
|
padding: getTabbedPadding(
|
||||||
child: ListView.builder(
|
context,
|
||||||
padding: getTabbedPadding(
|
bottom: callState.isConnected ? 96 : null,
|
||||||
context,
|
),
|
||||||
bottom: callState.isConnected ? 96 : null,
|
itemCount:
|
||||||
),
|
items
|
||||||
itemCount:
|
.where(
|
||||||
items
|
(item) =>
|
||||||
.where(
|
selectedTab.value == 0 ||
|
||||||
(item) =>
|
(selectedTab.value == 1 &&
|
||||||
selectedTab.value == 0 ||
|
item.type == 1) ||
|
||||||
(selectedTab.value == 1 &&
|
(selectedTab.value == 2 && item.type != 1),
|
||||||
item.type == 1) ||
|
)
|
||||||
(selectedTab.value == 2 &&
|
.length,
|
||||||
item.type != 1),
|
itemBuilder: (context, index) {
|
||||||
)
|
final filteredItems =
|
||||||
.length,
|
items
|
||||||
itemBuilder: (context, index) {
|
.where(
|
||||||
final filteredItems =
|
(item) =>
|
||||||
items
|
selectedTab.value == 0 ||
|
||||||
.where(
|
(selectedTab.value == 1 &&
|
||||||
(item) =>
|
item.type == 1) ||
|
||||||
selectedTab.value == 0 ||
|
(selectedTab.value == 2 &&
|
||||||
(selectedTab.value == 1 &&
|
item.type != 1),
|
||||||
item.type == 1) ||
|
)
|
||||||
(selectedTab.value == 2 &&
|
.toList();
|
||||||
item.type != 1),
|
final item = filteredItems[index];
|
||||||
)
|
return ChatRoomListTile(
|
||||||
.toList();
|
room: item,
|
||||||
final item = filteredItems[index];
|
isDirect: item.type == 1,
|
||||||
return ChatRoomListTile(
|
onTap: () {
|
||||||
room: item,
|
context.pushNamed(
|
||||||
isDirect: item.type == 1,
|
'chatRoom',
|
||||||
onTap: () {
|
pathParameters: {'id': item.id},
|
||||||
context.pushNamed(
|
|
||||||
'chatRoom',
|
|
||||||
pathParameters: {'id': item.id},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
loading:
|
),
|
||||||
() => const Center(child: CircularProgressIndicator()),
|
),
|
||||||
error:
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
(error, stack) => ResponseErrorWidget(
|
error:
|
||||||
error: error,
|
(error, stack) => ResponseErrorWidget(
|
||||||
onRetry: () {
|
error: error,
|
||||||
ref.invalidate(chatroomsJoinedProvider);
|
onRetry: () {
|
||||||
},
|
ref.invalidate(chatroomsJoinedProvider);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: getTabbedPadding(context).bottom + 8,
|
|
||||||
child: const CallOverlayBar().padding(horizontal: 16, vertical: 12),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@@ -167,6 +167,7 @@ Future<void> showAccountProfileCard(
|
|||||||
offset: offset ?? Offset.zero,
|
offset: offset ?? Offset.zero,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AccountProfileCard(uname: uname),
|
builder: (context) => AccountProfileCard(uname: uname),
|
||||||
|
alignment: Alignment.center,
|
||||||
dimBackground: true,
|
dimBackground: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -4,9 +4,11 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/call.dart';
|
import 'package:island/pods/call.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/chat/call_participant_tile.dart';
|
import 'package:island/widgets/chat/call_participant_tile.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:livekit_client/livekit_client.dart';
|
import 'package:livekit_client/livekit_client.dart';
|
||||||
|
|
||||||
@@ -55,7 +57,49 @@ class CallControlsBar extends HookConsumerWidget {
|
|||||||
const Gap(16),
|
const Gap(16),
|
||||||
_buildCircularButton(
|
_buildCircularButton(
|
||||||
icon: Icons.call_end,
|
icon: Icons.call_end,
|
||||||
onPressed: () => callNotifier.disconnect(),
|
onPressed:
|
||||||
|
() => showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
useRootNavigator: true,
|
||||||
|
builder:
|
||||||
|
(context) => ClipRRect(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(8),
|
||||||
|
topRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.logout, fill: 1),
|
||||||
|
title: Text('callLeave').tr(),
|
||||||
|
onTap: () {
|
||||||
|
callNotifier.disconnect();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.call_end, fill: 1),
|
||||||
|
iconColor: Colors.red,
|
||||||
|
title: Text('callEnd').tr(),
|
||||||
|
onTap: () async {
|
||||||
|
callNotifier.disconnect();
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
await apiClient.delete(
|
||||||
|
'/sphere/chat/realtime/${callNotifier.roomId}',
|
||||||
|
);
|
||||||
|
callNotifier.dispose();
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Gap(MediaQuery.of(context).padding.bottom),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
backgroundColor: const Color(0xFFE53E3E),
|
backgroundColor: const Color(0xFFE53E3E),
|
||||||
iconColor: Colors.white,
|
iconColor: Colors.white,
|
||||||
),
|
),
|
||||||
@@ -279,7 +323,7 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
child: Card(
|
child: Card(
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -295,16 +339,10 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
child:
|
child:
|
||||||
SpeakingRippleAvatar(
|
SpeakingRippleAvatar(
|
||||||
isSpeaking: lastSpeaker.isSpeaking,
|
isSpeaking: lastSpeaker.isSpeaking,
|
||||||
|
isMuted: lastSpeaker.isMuted,
|
||||||
audioLevel:
|
audioLevel:
|
||||||
lastSpeaker.remoteParticipant.audioLevel,
|
lastSpeaker.remoteParticipant.audioLevel,
|
||||||
pictureId:
|
identity: lastSpeaker.participant.identity,
|
||||||
lastSpeaker
|
|
||||||
.participant
|
|
||||||
.profile
|
|
||||||
?.account
|
|
||||||
.profile
|
|
||||||
.picture
|
|
||||||
?.id,
|
|
||||||
size: 36,
|
size: 36,
|
||||||
).center(),
|
).center(),
|
||||||
);
|
);
|
||||||
@@ -314,10 +352,7 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text('@${lastSpeaker.participant.identity}').bold(),
|
||||||
lastSpeaker.participant.profile?.account.nick ??
|
|
||||||
'unknown'.tr(),
|
|
||||||
).bold(),
|
|
||||||
Text(
|
Text(
|
||||||
formatDuration(callState.duration),
|
formatDuration(callState.duration),
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
@@ -360,7 +395,10 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
).padding(all: 16),
|
).padding(all: 16),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed('chatCall', pathParameters: {'id': callNotifier.roomId!});
|
context.pushNamed(
|
||||||
|
'chatCall',
|
||||||
|
pathParameters: {'id': callNotifier.roomId!},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,69 +1,118 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/call.dart';
|
import 'package:island/pods/call.dart';
|
||||||
|
import 'package:island/screens/account/profile.dart';
|
||||||
|
import 'package:island/widgets/account/account_pfc.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:livekit_client/livekit_client.dart';
|
import 'package:livekit_client/livekit_client.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
class SpeakingRippleAvatar extends StatelessWidget {
|
class SpeakingRippleAvatar extends HookConsumerWidget {
|
||||||
final bool isSpeaking;
|
final bool isSpeaking;
|
||||||
|
final bool isMuted;
|
||||||
final double audioLevel;
|
final double audioLevel;
|
||||||
final String? pictureId;
|
final String identity;
|
||||||
final double size;
|
final double size;
|
||||||
|
|
||||||
const SpeakingRippleAvatar({
|
const SpeakingRippleAvatar({
|
||||||
super.key,
|
super.key,
|
||||||
required this.isSpeaking,
|
required this.isSpeaking,
|
||||||
|
required this.isMuted,
|
||||||
required this.audioLevel,
|
required this.audioLevel,
|
||||||
required this.pictureId,
|
required this.identity,
|
||||||
this.size = 96,
|
this.size = 96,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final account = ref.watch(accountProvider(identity));
|
||||||
|
|
||||||
final avatarRadius = size / 2;
|
final avatarRadius = size / 2;
|
||||||
final clampedLevel = audioLevel.clamp(0.0, 1.0);
|
final clampedLevel = audioLevel.clamp(0.0, 1.0);
|
||||||
final rippleRadius = avatarRadius + clampedLevel * (size * 0.333);
|
final rippleRadius = avatarRadius + clampedLevel * (size * 0.333);
|
||||||
return TweenAnimationBuilder<double>(
|
return SizedBox(
|
||||||
tween: Tween<double>(
|
width: size + 8,
|
||||||
begin: avatarRadius,
|
height: size + 8,
|
||||||
end: isSpeaking ? rippleRadius : avatarRadius,
|
child: TweenAnimationBuilder<double>(
|
||||||
),
|
tween: Tween<double>(
|
||||||
duration: const Duration(milliseconds: 250),
|
begin: avatarRadius,
|
||||||
curve: Curves.easeOut,
|
end: isSpeaking ? rippleRadius : avatarRadius,
|
||||||
builder: (context, animatedRadius, child) {
|
),
|
||||||
return Stack(
|
duration: const Duration(milliseconds: 250),
|
||||||
alignment: Alignment.center,
|
curve: Curves.easeOut,
|
||||||
children: [
|
builder: (context, animatedRadius, child) {
|
||||||
if (isSpeaking)
|
return Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
if (isSpeaking)
|
||||||
|
Container(
|
||||||
|
width: animatedRadius * 2,
|
||||||
|
height: animatedRadius * 2,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Colors.green.withOpacity(0.75 + 0.25 * clampedLevel),
|
||||||
|
),
|
||||||
|
),
|
||||||
Container(
|
Container(
|
||||||
width: animatedRadius * 2,
|
width: size,
|
||||||
height: animatedRadius * 2,
|
height: size,
|
||||||
decoration: BoxDecoration(
|
alignment: Alignment.center,
|
||||||
shape: BoxShape.circle,
|
decoration: BoxDecoration(shape: BoxShape.circle),
|
||||||
color: Colors.green.withOpacity(0.75 + 0.25 * clampedLevel),
|
child: account.when(
|
||||||
|
data:
|
||||||
|
(value) => AccountPfcGestureDetector(
|
||||||
|
uname: identity,
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
file: value.profile.picture,
|
||||||
|
radius: size / 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
error:
|
||||||
|
(_, _) => CircleAvatar(
|
||||||
|
radius: size / 2,
|
||||||
|
child: const Icon(Symbols.person_remove),
|
||||||
|
),
|
||||||
|
loading:
|
||||||
|
() => CircleAvatar(
|
||||||
|
radius: size / 2,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
if (isMuted)
|
||||||
width: size,
|
Positioned(
|
||||||
height: size,
|
bottom: 4,
|
||||||
alignment: Alignment.center,
|
right: 4,
|
||||||
decoration: BoxDecoration(shape: BoxShape.circle),
|
child: Container(
|
||||||
child: ProfilePictureWidget(fileId: pictureId, radius: size / 2),
|
width: 20,
|
||||||
),
|
height: 20,
|
||||||
],
|
decoration: BoxDecoration(
|
||||||
);
|
color: Colors.red,
|
||||||
},
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Symbols.mic_off,
|
||||||
|
size: 14,
|
||||||
|
fill: 1,
|
||||||
|
).padding(left: 1.5, top: 1.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CallParticipantTile extends StatelessWidget {
|
class CallParticipantTile extends HookConsumerWidget {
|
||||||
final CallParticipantLive live;
|
final CallParticipantLive live;
|
||||||
|
|
||||||
const CallParticipantTile({super.key, required this.live});
|
const CallParticipantTile({super.key, required this.live});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final hasVideo =
|
final hasVideo =
|
||||||
live.hasVideo &&
|
live.hasVideo &&
|
||||||
live.remoteParticipant.trackPublications.values
|
live.remoteParticipant.trackPublications.values
|
||||||
@@ -75,18 +124,15 @@ class CallParticipantTile extends StatelessWidget {
|
|||||||
return Stack(
|
return Stack(
|
||||||
fit: StackFit.loose,
|
fit: StackFit.loose,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
AspectRatio(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
aspectRatio: 16 / 9,
|
||||||
child: AspectRatio(
|
child: VideoTrackRenderer(
|
||||||
aspectRatio: 16 / 9,
|
live.remoteParticipant.trackPublications.values
|
||||||
child: VideoTrackRenderer(
|
.where((track) => track.kind == TrackType.VIDEO)
|
||||||
live.remoteParticipant.trackPublications.values
|
.first
|
||||||
.where((track) => track.kind == TrackType.VIDEO)
|
.track
|
||||||
.first
|
as VideoTrack,
|
||||||
.track
|
renderMode: VideoRenderMode.platformView,
|
||||||
as VideoTrack,
|
|
||||||
renderMode: VideoRenderMode.platformView,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
@@ -94,10 +140,20 @@ class CallParticipantTile extends StatelessWidget {
|
|||||||
right: 8,
|
right: 8,
|
||||||
bottom: 8,
|
bottom: 8,
|
||||||
child: Text(
|
child: Text(
|
||||||
live.participant.profile?.account.nick ??
|
'@${live.participant.name}',
|
||||||
'${'unknown'.tr()}\'s video',
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(fontSize: 14, color: Colors.white),
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.white,
|
||||||
|
shadows: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black54,
|
||||||
|
offset: Offset(1, 1),
|
||||||
|
spreadRadius: 8,
|
||||||
|
blurRadius: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -105,8 +161,9 @@ class CallParticipantTile extends StatelessWidget {
|
|||||||
} else {
|
} else {
|
||||||
return SpeakingRippleAvatar(
|
return SpeakingRippleAvatar(
|
||||||
isSpeaking: live.isSpeaking,
|
isSpeaking: live.isSpeaking,
|
||||||
|
isMuted: live.isMuted,
|
||||||
audioLevel: audioLevel,
|
audioLevel: audioLevel,
|
||||||
pictureId: live.participant.profile?.account.profile.picture?.id,
|
identity: live.participant.identity,
|
||||||
size: 84,
|
size: 84,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
341
macos/Podfile.lock
Normal file
341
macos/Podfile.lock
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
PODS:
|
||||||
|
- bitsdojo_window_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- connectivity_plus (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- croppy (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- device_info_plus (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- file_picker (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- file_selector_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- Firebase/CoreOnly (12.0.0):
|
||||||
|
- FirebaseCore (~> 12.0.0)
|
||||||
|
- Firebase/Messaging (12.0.0):
|
||||||
|
- Firebase/CoreOnly
|
||||||
|
- FirebaseMessaging (~> 12.0.0)
|
||||||
|
- firebase_core (4.0.0):
|
||||||
|
- Firebase/CoreOnly (~> 12.0.0)
|
||||||
|
- FlutterMacOS
|
||||||
|
- firebase_messaging (16.0.0):
|
||||||
|
- Firebase/CoreOnly (~> 12.0.0)
|
||||||
|
- Firebase/Messaging (~> 12.0.0)
|
||||||
|
- firebase_core
|
||||||
|
- FlutterMacOS
|
||||||
|
- FirebaseCore (12.0.0):
|
||||||
|
- FirebaseCoreInternal (~> 12.0.0)
|
||||||
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
|
- FirebaseCoreInternal (12.0.0):
|
||||||
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
|
- FirebaseInstallations (12.0.0):
|
||||||
|
- FirebaseCore (~> 12.0.0)
|
||||||
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
|
- PromisesObjC (~> 2.4)
|
||||||
|
- FirebaseMessaging (12.0.0):
|
||||||
|
- FirebaseCore (~> 12.0.0)
|
||||||
|
- FirebaseInstallations (~> 12.0.0)
|
||||||
|
- GoogleDataTransport (~> 10.1)
|
||||||
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
|
- GoogleUtilities/Reachability (~> 8.1)
|
||||||
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
|
- nanopb (~> 3.30910.0)
|
||||||
|
- flutter_inappwebview_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- OrderedSet (~> 6.0.3)
|
||||||
|
- flutter_platform_alert (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- flutter_secure_storage_macos (6.1.3):
|
||||||
|
- FlutterMacOS
|
||||||
|
- flutter_timezone (0.1.0):
|
||||||
|
- FlutterMacOS
|
||||||
|
- flutter_udid (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- SAMKeychain
|
||||||
|
- flutter_webrtc (1.0.0):
|
||||||
|
- FlutterMacOS
|
||||||
|
- WebRTC-SDK (= 137.7151.02)
|
||||||
|
- FlutterMacOS (1.0.0)
|
||||||
|
- gal (1.0.0):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- GoogleDataTransport (10.1.0):
|
||||||
|
- nanopb (~> 3.30910.0)
|
||||||
|
- PromisesObjC (~> 2.4)
|
||||||
|
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
|
||||||
|
- GoogleUtilities/Environment
|
||||||
|
- GoogleUtilities/Logger
|
||||||
|
- GoogleUtilities/Network
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/Environment (8.1.0):
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/Logger (8.1.0):
|
||||||
|
- GoogleUtilities/Environment
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/Network (8.1.0):
|
||||||
|
- GoogleUtilities/Logger
|
||||||
|
- "GoogleUtilities/NSData+zlib"
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/Reachability
|
||||||
|
- "GoogleUtilities/NSData+zlib (8.1.0)":
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/Privacy (8.1.0)
|
||||||
|
- GoogleUtilities/Reachability (8.1.0):
|
||||||
|
- GoogleUtilities/Logger
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/UserDefaults (8.1.0):
|
||||||
|
- GoogleUtilities/Logger
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- irondash_engine_context (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- livekit_client (2.5.0):
|
||||||
|
- flutter_webrtc
|
||||||
|
- FlutterMacOS
|
||||||
|
- WebRTC-SDK (= 137.7151.02)
|
||||||
|
- local_auth_darwin (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- media_kit_libs_macos_video (1.0.4):
|
||||||
|
- FlutterMacOS
|
||||||
|
- media_kit_video (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- nanopb (3.30910.0):
|
||||||
|
- nanopb/decode (= 3.30910.0)
|
||||||
|
- nanopb/encode (= 3.30910.0)
|
||||||
|
- nanopb/decode (3.30910.0)
|
||||||
|
- nanopb/encode (3.30910.0)
|
||||||
|
- OrderedSet (6.0.3)
|
||||||
|
- package_info_plus (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- pasteboard (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- path_provider_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- PromisesObjC (2.4.0)
|
||||||
|
- record_macos (1.0.0):
|
||||||
|
- FlutterMacOS
|
||||||
|
- SAMKeychain (1.5.3)
|
||||||
|
- share_plus (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- shared_preferences_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- sign_in_with_apple (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- sqflite_darwin (0.0.4):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- sqlite3 (3.50.3):
|
||||||
|
- sqlite3/common (= 3.50.3)
|
||||||
|
- sqlite3/common (3.50.3)
|
||||||
|
- sqlite3/dbstatvtab (3.50.3):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/fts5 (3.50.3):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/math (3.50.3):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/perf-threadsafe (3.50.3):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/rtree (3.50.3):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/session (3.50.3):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- sqlite3 (~> 3.50.3)
|
||||||
|
- sqlite3/dbstatvtab
|
||||||
|
- sqlite3/fts5
|
||||||
|
- sqlite3/math
|
||||||
|
- sqlite3/perf-threadsafe
|
||||||
|
- sqlite3/rtree
|
||||||
|
- sqlite3/session
|
||||||
|
- super_native_extensions (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- url_launcher_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- volume_controller (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- wakelock_plus (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- WebRTC-SDK (137.7151.02)
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
|
||||||
|
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
|
||||||
|
- croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`)
|
||||||
|
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||||
|
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
|
||||||
|
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
|
||||||
|
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
|
||||||
|
- firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`)
|
||||||
|
- flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`)
|
||||||
|
- flutter_platform_alert (from `Flutter/ephemeral/.symlinks/plugins/flutter_platform_alert/macos`)
|
||||||
|
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
||||||
|
- flutter_timezone (from `Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos`)
|
||||||
|
- flutter_udid (from `Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos`)
|
||||||
|
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
|
||||||
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
|
- gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`)
|
||||||
|
- irondash_engine_context (from `Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos`)
|
||||||
|
- livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`)
|
||||||
|
- local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
|
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
|
||||||
|
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
|
||||||
|
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||||
|
- pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`)
|
||||||
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
|
- record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`)
|
||||||
|
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||||
|
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
|
- sign_in_with_apple (from `Flutter/ephemeral/.symlinks/plugins/sign_in_with_apple/macos`)
|
||||||
|
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
|
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||||
|
- super_native_extensions (from `Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos`)
|
||||||
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||||
|
- volume_controller (from `Flutter/ephemeral/.symlinks/plugins/volume_controller/macos`)
|
||||||
|
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||||
|
|
||||||
|
SPEC REPOS:
|
||||||
|
trunk:
|
||||||
|
- Firebase
|
||||||
|
- FirebaseCore
|
||||||
|
- FirebaseCoreInternal
|
||||||
|
- FirebaseInstallations
|
||||||
|
- FirebaseMessaging
|
||||||
|
- GoogleDataTransport
|
||||||
|
- GoogleUtilities
|
||||||
|
- nanopb
|
||||||
|
- OrderedSet
|
||||||
|
- PromisesObjC
|
||||||
|
- SAMKeychain
|
||||||
|
- sqlite3
|
||||||
|
- WebRTC-SDK
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
bitsdojo_window_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos
|
||||||
|
connectivity_plus:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
|
||||||
|
croppy:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/croppy/macos
|
||||||
|
device_info_plus:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
||||||
|
file_picker:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
|
||||||
|
file_selector_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
|
||||||
|
firebase_core:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos
|
||||||
|
firebase_messaging:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos
|
||||||
|
flutter_inappwebview_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos
|
||||||
|
flutter_platform_alert:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_platform_alert/macos
|
||||||
|
flutter_secure_storage_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
|
||||||
|
flutter_timezone:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos
|
||||||
|
flutter_udid:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos
|
||||||
|
flutter_webrtc:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos
|
||||||
|
FlutterMacOS:
|
||||||
|
:path: Flutter/ephemeral
|
||||||
|
gal:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/gal/darwin
|
||||||
|
irondash_engine_context:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos
|
||||||
|
livekit_client:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/livekit_client/macos
|
||||||
|
local_auth_darwin:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin
|
||||||
|
media_kit_libs_macos_video:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos
|
||||||
|
media_kit_video:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos
|
||||||
|
package_info_plus:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||||
|
pasteboard:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/pasteboard/macos
|
||||||
|
path_provider_foundation:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||||
|
record_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/record_macos/macos
|
||||||
|
share_plus:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||||
|
shared_preferences_foundation:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||||
|
sign_in_with_apple:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/sign_in_with_apple/macos
|
||||||
|
sqflite_darwin:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||||
|
sqlite3_flutter_libs:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
|
||||||
|
super_native_extensions:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos
|
||||||
|
url_launcher_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||||
|
volume_controller:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/volume_controller/macos
|
||||||
|
wakelock_plus:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
bitsdojo_window_macos: 7959fb0ca65a3ccda30095c181ecb856fae48ea9
|
||||||
|
connectivity_plus: 4adf20a405e25b42b9c9f87feff8f4b6fde18a4e
|
||||||
|
croppy: d9bfc8c02f3cd1851f669a421df298a474b78f43
|
||||||
|
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||||
|
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
||||||
|
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
|
||||||
|
Firebase: 800d487043c0557d9faed71477a38d9aafb08a41
|
||||||
|
firebase_core: eeea10f64026b68cd0bc3dee079ab4717e22909e
|
||||||
|
firebase_messaging: 5eefcd5bde556bfacdd9968e11c52f39032dfbe5
|
||||||
|
FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a
|
||||||
|
FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55
|
||||||
|
FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988
|
||||||
|
FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde
|
||||||
|
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
|
||||||
|
flutter_platform_alert: 8fa7a7c21f95b26d08b4a3891936ca27e375f284
|
||||||
|
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
||||||
|
flutter_timezone: d59eea86178cbd7943cd2431cc2eaa9850f935d8
|
||||||
|
flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495
|
||||||
|
flutter_webrtc: 0d70bd8782c19bde286dc52f766eebbea26de201
|
||||||
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
|
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||||
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
|
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
|
||||||
|
livekit_client: 0b0515e03858b86a7c14cc7fd6f772331f6ee84c
|
||||||
|
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||||
|
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
|
||||||
|
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
|
||||||
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
|
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||||
|
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||||
|
pasteboard: 278d8100149f940fb795d6b3a74f0720c890ecb7
|
||||||
|
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||||
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
|
record_macos: 295d70bd5fb47145df78df7b80e6697cd18403c0
|
||||||
|
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||||
|
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
||||||
|
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||||
|
sign_in_with_apple: 6673c03c9e3643f6c8d33601943fbfa9ae99f94e
|
||||||
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
|
sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81
|
||||||
|
sqlite3_flutter_libs: 616267f2fca40e9c6af8c5d82324e05667040b6e
|
||||||
|
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
|
||||||
|
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
||||||
|
volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd
|
||||||
|
wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497
|
||||||
|
WebRTC-SDK: d20de357dcbf7c9696b124b39f3ff62125107e4b
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 346bfb2deb41d4a6ebd6f6799f92188bde2d246f
|
||||||
|
|
||||||
|
COCOAPODS: 1.16.2
|
Reference in New Issue
Block a user