forked from mirrors/linphone-iphone
Init conference call view
This commit is contained in:
parent
bf4e4042d3
commit
ab3b883442
9 changed files with 713 additions and 330 deletions
|
|
@ -44,6 +44,7 @@
|
|||
D71FCA7F2AE1397200D2E43E /* ContactsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71FCA7E2AE1397200D2E43E /* ContactsListViewModel.swift */; };
|
||||
D71FCA812AE14CFC00D2E43E /* ContactsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71FCA802AE14CFC00D2E43E /* ContactsListFragment.swift */; };
|
||||
D71FCA832AE14D6E00D2E43E /* ContactFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71FCA822AE14D6E00D2E43E /* ContactFragment.swift */; };
|
||||
D720E6AD2BAD822000DDFD87 /* ParticipantModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D720E6AC2BAD822000DDFD87 /* ParticipantModel.swift */; };
|
||||
D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72250622ADE9615008FB426 /* HistoryViewModel.swift */; };
|
||||
D72250692ADFBF2D008FB426 /* SideMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72250682ADFBF2D008FB426 /* SideMenu.swift */; };
|
||||
D72343302ACEFEF8009AA24E /* QrCodeScannerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723432F2ACEFEF8009AA24E /* QrCodeScannerFragment.swift */; };
|
||||
|
|
@ -180,6 +181,7 @@
|
|||
D71FCA7E2AE1397200D2E43E /* ContactsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsListViewModel.swift; sourceTree = "<group>"; };
|
||||
D71FCA802AE14CFC00D2E43E /* ContactsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsListFragment.swift; sourceTree = "<group>"; };
|
||||
D71FCA822AE14D6E00D2E43E /* ContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactFragment.swift; sourceTree = "<group>"; };
|
||||
D720E6AC2BAD822000DDFD87 /* ParticipantModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantModel.swift; sourceTree = "<group>"; };
|
||||
D72250622ADE9615008FB426 /* HistoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryViewModel.swift; sourceTree = "<group>"; };
|
||||
D72250682ADFBF2D008FB426 /* SideMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenu.swift; sourceTree = "<group>"; };
|
||||
D723432F2ACEFEF8009AA24E /* QrCodeScannerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrCodeScannerFragment.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -444,6 +446,14 @@
|
|||
path = Viewmodel;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D720E6AB2BAD81C800DDFD87 /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D720E6AC2BAD822000DDFD87 /* ParticipantModel.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D72250612ADE95E4008FB426 /* ViewModel */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -597,6 +607,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D75759302B56D3CE00E7AC10 /* Fragments */,
|
||||
D720E6AB2BAD81C800DDFD87 /* Model */,
|
||||
D7B99E972B29B37F00BE7BF2 /* ViewModel */,
|
||||
D7B5678D2B28888F00DE63EB /* CallView.swift */,
|
||||
);
|
||||
|
|
@ -866,6 +877,7 @@
|
|||
D70959F12B8DF3EC0014AC0B /* ConversationModel.swift in Sources */,
|
||||
D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */,
|
||||
D777DBB32AE12C5900565A99 /* ContactsManager.swift in Sources */,
|
||||
D720E6AD2BAD822000DDFD87 /* ParticipantModel.swift in Sources */,
|
||||
D796F2002B0BB61A0041115F /* ToastViewModel.swift in Sources */,
|
||||
D7C3650A2AF001C300FE6142 /* EditContactFragment.swift in Sources */,
|
||||
D7A03FBD2ACC2DB60081A588 /* ContactsView.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -270,9 +270,11 @@ final class CoreContext: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func updatePresence(core : Core, presence : ConsolidatedPresence) {
|
||||
func updatePresence(core: Core, presence: ConsolidatedPresence) {
|
||||
if core.config!.getBool(section: "app", key: "publish_presence", defaultValue: true) {
|
||||
core.consolidatedPresence = presence
|
||||
DispatchQueue.main.async {
|
||||
core.consolidatedPresence = presence
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +285,7 @@ final class CoreContext: ObservableObject {
|
|||
|
||||
Log.info("App is in foreground, PUBLISHING presence as Online")
|
||||
self.updatePresence(core: self.mCore, presence: ConsolidatedPresence.Online)
|
||||
try? self.mCore.start()
|
||||
//try? self.mCore.start()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -297,7 +299,10 @@ final class CoreContext: ObservableObject {
|
|||
// Flexisip will handle the Busy status depending on other devices
|
||||
self.updatePresence(core: self.mCore, presence: ConsolidatedPresence.Offline)
|
||||
// self.mCore.iterate()
|
||||
self.mCore.stop()
|
||||
|
||||
if self.mCore.currentCall == nil {
|
||||
//self.mCore.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -244,6 +244,9 @@
|
|||
},
|
||||
"Contacts" : {
|
||||
|
||||
},
|
||||
"Content" : {
|
||||
|
||||
},
|
||||
"Continue" : {
|
||||
|
||||
|
|
@ -485,6 +488,12 @@
|
|||
},
|
||||
"Other actions" : {
|
||||
|
||||
},
|
||||
"Partage d'écran" : {
|
||||
|
||||
},
|
||||
"Participants" : {
|
||||
|
||||
},
|
||||
"password" : {
|
||||
"extractionState" : "manual",
|
||||
|
|
@ -625,6 +634,9 @@
|
|||
},
|
||||
"This contact will be deleted definitively." : {
|
||||
|
||||
},
|
||||
"Title" : {
|
||||
|
||||
},
|
||||
"TLS" : {
|
||||
|
||||
|
|
@ -693,4 +705,4 @@
|
|||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@ class TelecomManager: ObservableObject {
|
|||
@Published var callStarted: Bool = false
|
||||
@Published var outgoingCallStarted: Bool = false
|
||||
@Published var remoteVideo: Bool = false
|
||||
@Published var remoteConfVideo: Bool = false
|
||||
@Published var isRecordingByRemote: Bool = false
|
||||
@Published var isPausedByRemote: Bool = false
|
||||
@Published var refreshCallViewModel: Bool = false
|
||||
|
|
@ -374,7 +375,19 @@ class TelecomManager: ObservableObject {
|
|||
|
||||
DispatchQueue.main.async {
|
||||
let oldRemoteVideo = self.remoteVideo
|
||||
self.remoteVideo = (core.videoActivationPolicy?.automaticallyAccept ?? false) && (call.remoteParams?.videoEnabled ?? false)
|
||||
//self.remoteVideo = (core.videoActivationPolicy?.automaticallyAccept ?? false) && (call.remoteParams?.videoEnabled ?? false)
|
||||
|
||||
if call.conference != nil {
|
||||
if call.conference!.activeSpeakerParticipantDevice != nil {
|
||||
let direction = call.conference?.activeSpeakerParticipantDevice!.getStreamCapability(streamType: StreamType.Video)
|
||||
self.remoteConfVideo = direction == MediaDirection.SendRecv || direction == MediaDirection.SendOnly
|
||||
} else {
|
||||
self.remoteConfVideo = true
|
||||
}
|
||||
} else {
|
||||
self.remoteVideo = call.currentParams!.videoEnabled && call.currentParams!.videoDirection != MediaDirection.Inactive
|
||||
self.remoteConfVideo = false
|
||||
}
|
||||
|
||||
if self.remoteVideo && self.remoteVideo != oldRemoteVideo {
|
||||
do {
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// swiftlint:disable type_body_length
|
||||
// swiftlint:disable line_length
|
||||
import SwiftUI
|
||||
import CallKit
|
||||
import AVFAudio
|
||||
import linphonesw
|
||||
|
||||
// swiftlint:disable type_body_length
|
||||
// swiftlint:disable line_length
|
||||
struct CallView: View {
|
||||
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
|
|
@ -38,7 +38,6 @@ struct CallView: View {
|
|||
let pub = NotificationCenter.default.publisher(for: AVAudioSession.routeChangeNotification)
|
||||
|
||||
@State var audioRouteSheet: Bool = false
|
||||
@State var hideButtonsSheet: Bool = false
|
||||
@State var options: Int = 1
|
||||
@State var imageAudioRoute: String = ""
|
||||
@State var angleDegree = 0.0
|
||||
|
|
@ -47,6 +46,7 @@ struct CallView: View {
|
|||
@State var maxBottomSheetHeight: CGFloat = 0.5
|
||||
@State private var pointingUp: CGFloat = 0.0
|
||||
@State private var currentOffset: CGFloat = 0.0
|
||||
@State private var viewIsDisplayed = false
|
||||
|
||||
@Binding var fullscreenVideo: Bool
|
||||
@Binding var isShowCallsListFragment: Bool
|
||||
|
|
@ -59,7 +59,6 @@ struct CallView: View {
|
|||
innerView(geometry: geo)
|
||||
.sheet(isPresented: $audioRouteSheet, onDismiss: {
|
||||
audioRouteSheet = false
|
||||
hideButtonsSheet = false
|
||||
}) {
|
||||
innerBottomSheet()
|
||||
.presentationDetents([.fraction(0.3)])
|
||||
|
|
@ -77,7 +76,6 @@ struct CallView: View {
|
|||
innerView(geometry: geo)
|
||||
.sheet(isPresented: $audioRouteSheet, onDismiss: {
|
||||
audioRouteSheet = false
|
||||
hideButtonsSheet = false
|
||||
}) {
|
||||
innerBottomSheet()
|
||||
.presentationDetents([.fraction(0.3)])
|
||||
|
|
@ -96,7 +94,6 @@ struct CallView: View {
|
|||
innerBottomSheet()
|
||||
} onDismiss: {
|
||||
audioRouteSheet = false
|
||||
hideButtonsSheet = false
|
||||
}
|
||||
.halfSheet(showSheet: $showingDialer) {
|
||||
DialerBottomSheet(
|
||||
|
|
@ -320,7 +317,18 @@ struct CallView: View {
|
|||
.padding(.all, 10)
|
||||
}
|
||||
|
||||
if telecomManager.remoteVideo {
|
||||
if !callViewModel.isConference && telecomManager.remoteVideo {
|
||||
Button {
|
||||
callViewModel.switchCamera()
|
||||
} label: {
|
||||
Image("camera-rotate")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 30, height: 30)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
} else if callViewModel.isConference && callViewModel.videoDisplayed {
|
||||
Button {
|
||||
callViewModel.switchCamera()
|
||||
} label: {
|
||||
|
|
@ -360,232 +368,7 @@ struct CallView: View {
|
|||
}
|
||||
}
|
||||
|
||||
ZStack {
|
||||
VStack {
|
||||
Spacer()
|
||||
ZStack {
|
||||
|
||||
if callViewModel.isRemoteDeviceTrusted {
|
||||
Circle()
|
||||
.fill(Color.blueInfo500)
|
||||
.frame(width: 206, height: 206)
|
||||
}
|
||||
|
||||
if callViewModel.remoteAddress != nil {
|
||||
let addressFriend = contactsManager.getFriendWithAddress(address: callViewModel.remoteAddress!)
|
||||
|
||||
let contactAvatarModel = addressFriend != nil
|
||||
? ContactsManager.shared.avatarListModel.first(where: {
|
||||
($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy)
|
||||
&& $0.friend!.name == addressFriend!.name
|
||||
&& $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly()
|
||||
})
|
||||
: ContactAvatarModel(friend: nil, name: "", withPresence: false)
|
||||
|
||||
if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty {
|
||||
if contactAvatarModel != nil {
|
||||
Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 200, hidePresence: true)
|
||||
}
|
||||
} else {
|
||||
if callViewModel.remoteAddress!.displayName != nil {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: callViewModel.remoteAddress!.displayName!,
|
||||
lastName: callViewModel.remoteAddress!.displayName!.components(separatedBy: " ").count > 1
|
||||
? callViewModel.remoteAddress!.displayName!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
|
||||
} else {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: callViewModel.remoteAddress!.username ?? "Username Error",
|
||||
lastName: callViewModel.remoteAddress!.username!.components(separatedBy: " ").count > 1
|
||||
? callViewModel.remoteAddress!.username!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
if callViewModel.isRemoteDeviceTrusted {
|
||||
VStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
Image("trusted")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 15)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(width: 200, height: 200)
|
||||
}
|
||||
}
|
||||
|
||||
Text(callViewModel.displayName)
|
||||
.padding(.top)
|
||||
.default_text_style_white(styleSize: 22)
|
||||
|
||||
Text(callViewModel.remoteAddressString)
|
||||
.default_text_style_white_300(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
LinphoneVideoViewHolder { view in
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativeVideoWindow = view
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
width:
|
||||
angleDegree == 0
|
||||
? (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8)
|
||||
: (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom),
|
||||
height:
|
||||
angleDegree == 0
|
||||
? (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom)
|
||||
: (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8)
|
||||
)
|
||||
.scaledToFill()
|
||||
.clipped()
|
||||
.onTapGesture {
|
||||
if telecomManager.remoteVideo {
|
||||
fullscreenVideo.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if telecomManager.remoteVideo {
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
Spacer()
|
||||
LinphoneVideoViewHolder { view in
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativePreviewWindow = view
|
||||
}
|
||||
}
|
||||
.frame(width: angleDegree == 0 ? 120*1.2 : 160*1.2, height: angleDegree == 0 ? 160*1.2 : 120*1.2)
|
||||
.cornerRadius(20)
|
||||
.padding(10)
|
||||
.padding(.trailing, abs(angleDegree/2))
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8,
|
||||
maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom
|
||||
)
|
||||
}
|
||||
|
||||
if callViewModel.isRecording {
|
||||
HStack {
|
||||
VStack {
|
||||
Image("record-fill")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 32, height: 32)
|
||||
.padding(10)
|
||||
.if(fullscreenVideo && !telecomManager.isPausedByRemote) { view in
|
||||
view.padding(.top, 30)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.frame(
|
||||
maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8,
|
||||
maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom
|
||||
)
|
||||
}
|
||||
|
||||
if telecomManager.outgoingCallStarted {
|
||||
VStack {
|
||||
ActivityIndicator()
|
||||
.frame(width: 20, height: 20)
|
||||
.padding(.top, 60)
|
||||
|
||||
Text(callViewModel.counterToMinutes())
|
||||
.onAppear {
|
||||
callViewModel.timeElapsed = 0
|
||||
}
|
||||
.onReceive(callViewModel.timer) { _ in
|
||||
callViewModel.timeElapsed = callViewModel.currentCall?.duration ?? 0
|
||||
|
||||
}
|
||||
.onDisappear {
|
||||
callViewModel.timeElapsed = 0
|
||||
}
|
||||
.padding(.top)
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.background(.clear)
|
||||
.frame(
|
||||
maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8,
|
||||
maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8,
|
||||
maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom
|
||||
)
|
||||
.background(Color.gray900)
|
||||
.cornerRadius(20)
|
||||
.padding(.horizontal, fullscreenVideo && !telecomManager.isPausedByRemote ? 0 : 4)
|
||||
.onRotate { newOrientation in
|
||||
let oldOrientation = orientation
|
||||
orientation = newOrientation
|
||||
if orientation == .portrait || orientation == .portraitUpsideDown {
|
||||
angleDegree = 0
|
||||
} else {
|
||||
if orientation == .landscapeLeft {
|
||||
angleDegree = -90
|
||||
} else if orientation == .landscapeRight {
|
||||
angleDegree = 90
|
||||
}
|
||||
}
|
||||
|
||||
if (oldOrientation != orientation && oldOrientation != .faceUp) || (oldOrientation == .faceUp && (orientation == .landscapeLeft || orientation == .landscapeRight)) {
|
||||
telecomManager.callStarted = false
|
||||
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
|
||||
telecomManager.callStarted = true
|
||||
}
|
||||
}
|
||||
|
||||
callViewModel.orientationUpdate(orientation: orientation)
|
||||
}
|
||||
.onAppear {
|
||||
if orientation == .portrait && orientation == .portraitUpsideDown {
|
||||
angleDegree = 0
|
||||
} else {
|
||||
if orientation == .landscapeLeft {
|
||||
angleDegree = -90
|
||||
} else if orientation == .landscapeRight {
|
||||
angleDegree = 90
|
||||
}
|
||||
}
|
||||
|
||||
telecomManager.callStarted = false
|
||||
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
|
||||
telecomManager.callStarted = true
|
||||
}
|
||||
|
||||
callViewModel.orientationUpdate(orientation: orientation)
|
||||
}
|
||||
simpleCallView(geometry: geometry)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
|
@ -619,6 +402,396 @@ struct CallView: View {
|
|||
}
|
||||
}
|
||||
|
||||
func simpleCallView(geometry: GeometryProxy) -> some View {
|
||||
ZStack {
|
||||
if !callViewModel.isConference {
|
||||
VStack {
|
||||
Spacer()
|
||||
ZStack {
|
||||
|
||||
if callViewModel.isRemoteDeviceTrusted {
|
||||
Circle()
|
||||
.fill(Color.blueInfo500)
|
||||
.frame(width: 206, height: 206)
|
||||
}
|
||||
|
||||
if callViewModel.remoteAddress != nil {
|
||||
let addressFriend = contactsManager.getFriendWithAddress(address: callViewModel.remoteAddress!)
|
||||
|
||||
let contactAvatarModel = addressFriend != nil
|
||||
? ContactsManager.shared.avatarListModel.first(where: {
|
||||
($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy)
|
||||
&& $0.friend!.name == addressFriend!.name
|
||||
&& $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly()
|
||||
})
|
||||
: ContactAvatarModel(friend: nil, name: "", withPresence: false)
|
||||
|
||||
if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty {
|
||||
if contactAvatarModel != nil {
|
||||
Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 200, hidePresence: true)
|
||||
}
|
||||
} else {
|
||||
if callViewModel.remoteAddress!.displayName != nil {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: callViewModel.remoteAddress!.displayName!,
|
||||
lastName: callViewModel.remoteAddress!.displayName!.components(separatedBy: " ").count > 1
|
||||
? callViewModel.remoteAddress!.displayName!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
|
||||
} else {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: callViewModel.remoteAddress!.username ?? "Username Error",
|
||||
lastName: callViewModel.remoteAddress!.username!.components(separatedBy: " ").count > 1
|
||||
? callViewModel.remoteAddress!.username!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
if callViewModel.isRemoteDeviceTrusted {
|
||||
VStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
Image("trusted")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 15)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(width: 200, height: 200)
|
||||
}
|
||||
}
|
||||
|
||||
Text(callViewModel.displayName)
|
||||
.padding(.top)
|
||||
.default_text_style_white(styleSize: 22)
|
||||
|
||||
Text(callViewModel.remoteAddressString)
|
||||
.default_text_style_white_300(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
} else {
|
||||
VStack {
|
||||
Spacer()
|
||||
ZStack {
|
||||
if callViewModel.activeSpeakerParticipant?.address != nil {
|
||||
let addressFriend = contactsManager.getFriendWithAddress(address: callViewModel.activeSpeakerParticipant!.address)
|
||||
|
||||
let contactAvatarModel = addressFriend != nil
|
||||
? ContactsManager.shared.avatarListModel.first(where: {
|
||||
($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy)
|
||||
&& $0.friend!.name == addressFriend!.name
|
||||
&& $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly()
|
||||
})
|
||||
: ContactAvatarModel(friend: nil, name: "", withPresence: false)
|
||||
|
||||
if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty {
|
||||
if contactAvatarModel != nil {
|
||||
Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 200, hidePresence: true)
|
||||
}
|
||||
} else {
|
||||
if callViewModel.activeSpeakerParticipant!.address.displayName != nil {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: callViewModel.activeSpeakerParticipant!.address.displayName!,
|
||||
lastName: callViewModel.activeSpeakerParticipant!.address.displayName!.components(separatedBy: " ").count > 1
|
||||
? callViewModel.activeSpeakerParticipant!.address.displayName!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
|
||||
} else {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: callViewModel.activeSpeakerParticipant!.address.username ?? "Username Error",
|
||||
lastName: callViewModel.activeSpeakerParticipant!.address.username!.components(separatedBy: " ").count > 1
|
||||
? callViewModel.activeSpeakerParticipant!.address.username!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
if !callViewModel.isConference {
|
||||
LinphoneVideoViewHolder { view in
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativeVideoWindowId = UnsafeMutableRawPointer(Unmanaged.passRetained(view).toOpaque())
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
width:
|
||||
angleDegree == 0
|
||||
? (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8)
|
||||
: (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom),
|
||||
height:
|
||||
angleDegree == 0
|
||||
? (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom)
|
||||
: (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8)
|
||||
)
|
||||
.scaledToFill()
|
||||
.clipped()
|
||||
.onTapGesture {
|
||||
if telecomManager.remoteVideo {
|
||||
fullscreenVideo.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if telecomManager.remoteVideo {
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
Spacer()
|
||||
LinphoneVideoViewHolder { view in
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativePreviewWindow = view
|
||||
}
|
||||
}
|
||||
.frame(width: angleDegree == 0 ? 120*1.2 : 160*1.2, height: angleDegree == 0 ? 160*1.2 : 120*1.2)
|
||||
.cornerRadius(20)
|
||||
.padding(10)
|
||||
.padding(.trailing, abs(angleDegree/2))
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8,
|
||||
maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom
|
||||
)
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
if !viewIsDisplayed {
|
||||
VStack {
|
||||
|
||||
}
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
viewIsDisplayed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if viewIsDisplayed && (callViewModel.receiveVideo || telecomManager.remoteConfVideo) {
|
||||
*/
|
||||
if (callViewModel.receiveVideo || telecomManager.remoteConfVideo) {
|
||||
LinphoneVideoViewHolder { view in
|
||||
coreContext.doOnCoreQueue { core in
|
||||
//core.nativeVideoWindow = view
|
||||
core.nativeVideoWindowId = UnsafeMutableRawPointer(Unmanaged.passRetained(view).toOpaque())
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
width:
|
||||
angleDegree == 0
|
||||
? (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8)
|
||||
: (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom),
|
||||
height:
|
||||
angleDegree == 0
|
||||
? (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom)
|
||||
: (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8)
|
||||
)
|
||||
.scaledToFill()
|
||||
.clipped()
|
||||
.onTapGesture {
|
||||
if telecomManager.remoteVideo {
|
||||
fullscreenVideo.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
Spacer()
|
||||
ScrollView(.horizontal) {
|
||||
HStack {
|
||||
ForEach(0..<callViewModel.participantList.count - 1, id: \.self) { index in
|
||||
ZStack {
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
Avatar(contactAvatarModel: callViewModel.participantList[index].avatarModel, avatarSize: 50)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Spacer()
|
||||
|
||||
Text(callViewModel.participantList[index].name)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.white)
|
||||
.default_text_style_500(styleSize: 14)
|
||||
.lineLimit(1)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.bottom, 6)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.frame(width: 140, height: 140)
|
||||
.background(Color.gray600)
|
||||
.cornerRadius(20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8,
|
||||
maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom
|
||||
)
|
||||
.padding(.bottom, 10)
|
||||
/*
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
Spacer()
|
||||
LinphoneVideoViewHolder { view in
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativePreviewWindow = view
|
||||
}
|
||||
}
|
||||
.frame(width: angleDegree == 0 ? 120*1.2 : 160*1.2, height: angleDegree == 0 ? 160*1.2 : 120*1.2)
|
||||
.cornerRadius(20)
|
||||
.padding(10)
|
||||
.padding(.trailing, abs(angleDegree/2))
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8,
|
||||
maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom
|
||||
)
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
if callViewModel.isRecording {
|
||||
HStack {
|
||||
VStack {
|
||||
Image("record-fill")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 32, height: 32)
|
||||
.padding(10)
|
||||
.if(fullscreenVideo && !telecomManager.isPausedByRemote) { view in
|
||||
view.padding(.top, 30)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.frame(
|
||||
maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8,
|
||||
maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom
|
||||
)
|
||||
}
|
||||
|
||||
if telecomManager.outgoingCallStarted {
|
||||
VStack {
|
||||
ActivityIndicator()
|
||||
.frame(width: 20, height: 20)
|
||||
.padding(.top, 60)
|
||||
|
||||
Text(callViewModel.counterToMinutes())
|
||||
.onAppear {
|
||||
callViewModel.timeElapsed = 0
|
||||
}
|
||||
.onReceive(callViewModel.timer) { _ in
|
||||
callViewModel.timeElapsed = callViewModel.currentCall?.duration ?? 0
|
||||
|
||||
}
|
||||
.onDisappear {
|
||||
callViewModel.timeElapsed = 0
|
||||
}
|
||||
.padding(.top)
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.background(.clear)
|
||||
.frame(
|
||||
maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8,
|
||||
maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8,
|
||||
maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom
|
||||
)
|
||||
.background(Color.gray900)
|
||||
.cornerRadius(20)
|
||||
.padding(.horizontal, fullscreenVideo && !telecomManager.isPausedByRemote ? 0 : 4)
|
||||
.onRotate { newOrientation in
|
||||
let oldOrientation = orientation
|
||||
orientation = newOrientation
|
||||
if orientation == .portrait || orientation == .portraitUpsideDown {
|
||||
angleDegree = 0
|
||||
} else {
|
||||
if orientation == .landscapeLeft {
|
||||
angleDegree = -90
|
||||
} else if orientation == .landscapeRight {
|
||||
angleDegree = 90
|
||||
}
|
||||
}
|
||||
|
||||
if (oldOrientation != orientation && oldOrientation != .faceUp) || (oldOrientation == .faceUp && (orientation == .landscapeLeft || orientation == .landscapeRight)) {
|
||||
telecomManager.callStarted = false
|
||||
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
|
||||
telecomManager.callStarted = true
|
||||
}
|
||||
}
|
||||
|
||||
callViewModel.orientationUpdate(orientation: orientation)
|
||||
}
|
||||
.onAppear {
|
||||
if orientation == .portrait && orientation == .portraitUpsideDown {
|
||||
angleDegree = 0
|
||||
} else {
|
||||
if orientation == .landscapeLeft {
|
||||
angleDegree = -90
|
||||
} else if orientation == .landscapeRight {
|
||||
angleDegree = 90
|
||||
}
|
||||
}
|
||||
|
||||
telecomManager.callStarted = false
|
||||
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
|
||||
telecomManager.callStarted = true
|
||||
}
|
||||
|
||||
callViewModel.orientationUpdate(orientation: orientation)
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable function_body_length
|
||||
func bottomSheetContent(geo: GeometryProxy) -> some View {
|
||||
GeometryReader { _ in
|
||||
VStack(spacing: 0) {
|
||||
|
|
@ -658,22 +831,41 @@ struct CallView: View {
|
|||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
callViewModel.toggleVideo()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(telecomManager.remoteVideo ? "video-camera" : "video-camera-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white)
|
||||
.frame(width: 32, height: 32)
|
||||
if !callViewModel.isConference {
|
||||
Button {
|
||||
callViewModel.toggleVideo()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(telecomManager.remoteVideo ? "video-camera" : "video-camera-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle())
|
||||
.frame(width: 60, height: 60)
|
||||
.background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? .white : Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
|
||||
} else {
|
||||
Button {
|
||||
callViewModel.displayMyVideo()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(callViewModel.videoDisplayed ? "video-camera" : "video-camera-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle())
|
||||
.frame(width: 60, height: 60)
|
||||
.background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? .white : Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle())
|
||||
.frame(width: 60, height: 60)
|
||||
.background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? .white : Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
|
||||
|
||||
Button {
|
||||
callViewModel.toggleMuteMicrophone()
|
||||
|
|
@ -695,8 +887,6 @@ struct CallView: View {
|
|||
if AVAudioSession.sharedInstance().availableInputs != nil
|
||||
&& !AVAudioSession.sharedInstance().availableInputs!.filter({ $0.portType.rawValue.contains("Bluetooth") }).isEmpty {
|
||||
|
||||
hideButtonsSheet = true
|
||||
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
|
||||
audioRouteSheet = true
|
||||
}
|
||||
|
|
@ -732,63 +922,109 @@ struct CallView: View {
|
|||
|
||||
if orientation != .landscapeLeft && orientation != .landscapeRight {
|
||||
HStack(spacing: 0) {
|
||||
VStack {
|
||||
Button {
|
||||
if callViewModel.calls.count < 2 {
|
||||
if !callViewModel.isConference {
|
||||
VStack {
|
||||
Button {
|
||||
if callViewModel.calls.count < 2 {
|
||||
withAnimation {
|
||||
callViewModel.isTransferInsteadCall = true
|
||||
MagicSearchSingleton.shared.searchForSuggestions()
|
||||
isShowStartCallFragment.toggle()
|
||||
}
|
||||
} else {
|
||||
callViewModel.transferClicked()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("phone-transfer")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle())
|
||||
.frame(width: 60, height: 60)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text(callViewModel.calls.count < 2 ? "Transfer" : "Attended transfer")
|
||||
.foregroundStyle(.white)
|
||||
.default_text_style(styleSize: 15)
|
||||
}
|
||||
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
|
||||
|
||||
VStack {
|
||||
Button {
|
||||
withAnimation {
|
||||
callViewModel.isTransferInsteadCall = true
|
||||
MagicSearchSingleton.shared.searchForSuggestions()
|
||||
isShowStartCallFragment.toggle()
|
||||
}
|
||||
} else {
|
||||
callViewModel.transferClicked()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("phone-transfer")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
} label: {
|
||||
HStack {
|
||||
Image("phone-plus")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle())
|
||||
.frame(width: 60, height: 60)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("New call")
|
||||
.foregroundStyle(.white)
|
||||
.default_text_style(styleSize: 15)
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle())
|
||||
.frame(width: 60, height: 60)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text(callViewModel.calls.count < 2 ? "Transfer" : "Attended transfer")
|
||||
.foregroundStyle(.white)
|
||||
.default_text_style(styleSize: 15)
|
||||
}
|
||||
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
|
||||
|
||||
VStack {
|
||||
Button {
|
||||
withAnimation {
|
||||
MagicSearchSingleton.shared.searchForSuggestions()
|
||||
isShowStartCallFragment.toggle()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("phone-plus")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
|
||||
} else {
|
||||
VStack {
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
Image("screencast")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.gray500)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle())
|
||||
.frame(width: 60, height: 60)
|
||||
.background(.white)
|
||||
.cornerRadius(40)
|
||||
.disabled(true)
|
||||
|
||||
Text("Partage d'écran")
|
||||
.foregroundStyle(.white)
|
||||
.default_text_style(styleSize: 15)
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle())
|
||||
.frame(width: 60, height: 60)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
|
||||
|
||||
Text("New call")
|
||||
.foregroundStyle(.white)
|
||||
.default_text_style(styleSize: 15)
|
||||
VStack {
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
Image("users")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle())
|
||||
.frame(width: 60, height: 60)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("Participants")
|
||||
.foregroundStyle(.white)
|
||||
.default_text_style(styleSize: 15)
|
||||
}
|
||||
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
|
||||
}
|
||||
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
|
||||
|
||||
VStack {
|
||||
ZStack {
|
||||
Button {
|
||||
|
|
@ -1162,6 +1398,7 @@ struct CallView: View {
|
|||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
}
|
||||
}
|
||||
// swiftlint:enable function_body_length
|
||||
|
||||
func getAudioRouteImage() {
|
||||
imageAudioRoute = AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue == "Speaker" }).isEmpty
|
||||
|
|
|
|||
58
Linphone/UI/Call/Model/ParticipantModel.swift
Normal file
58
Linphone/UI/Call/Model/ParticipantModel.swift
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of Linphone
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import linphonesw
|
||||
|
||||
class ParticipantModel: ObservableObject {
|
||||
|
||||
static let TAG = "[Participant Model]"
|
||||
|
||||
let address: Address
|
||||
@Published var sipUri: String
|
||||
@Published var name: String
|
||||
@Published var avatarModel: ContactAvatarModel
|
||||
|
||||
init(address: Address) {
|
||||
self.address = address
|
||||
|
||||
self.sipUri = address.asStringUriOnly()
|
||||
|
||||
let addressFriend = ContactsManager.shared.getFriendWithAddress(address: self.address)
|
||||
|
||||
var nameTmp = ""
|
||||
|
||||
if addressFriend != nil {
|
||||
nameTmp = addressFriend!.name!
|
||||
} else {
|
||||
nameTmp = address.displayName != nil
|
||||
? address.displayName!
|
||||
: address.username!
|
||||
}
|
||||
|
||||
self.name = nameTmp
|
||||
|
||||
self.avatarModel = addressFriend != nil
|
||||
? ContactsManager.shared.avatarListModel.first(where: {
|
||||
$0.friend!.name == addressFriend!.name
|
||||
&& $0.friend!.address!.asStringUriOnly() == address.asStringUriOnly()
|
||||
}) ?? ContactAvatarModel(friend: nil, name: nameTmp, withPresence: false)
|
||||
: ContactAvatarModel(friend: nil, name: nameTmp, withPresence: false)
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,14 @@ class CallViewModel: ObservableObject {
|
|||
@Published var isRemoteDeviceTrusted: Bool = false
|
||||
@Published var selectedCall: Call?
|
||||
@Published var isTransferInsteadCall: Bool = false
|
||||
@Published var isConference: Bool = false
|
||||
@Published var videoDisplayed: Bool = false
|
||||
@Published var receiveVideo: Bool = false
|
||||
@Published var participantList: [ParticipantModel] = []
|
||||
@Published var activeSpeakerParticipant: ParticipantModel? = nil
|
||||
|
||||
|
||||
private var mConferenceSuscriptions = Set<AnyCancellable?>()
|
||||
|
||||
var calls: [Call] = []
|
||||
|
||||
|
|
@ -110,6 +118,7 @@ class CallViewModel: ObservableObject {
|
|||
self.isRemoteDeviceTrusted = self.telecomManager.callInProgress ? isDeviceTrusted : false
|
||||
|
||||
self.getCallsList()
|
||||
self.getConference()
|
||||
}
|
||||
|
||||
self.callSuscriptions.insert(self.currentCall!.publisher?.onEncryptionChanged?.postOnMainQueue {(cbVal: (call: Call, on: Bool, authenticationToken: String?)) in
|
||||
|
|
@ -127,6 +136,43 @@ class CallViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func getConference() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
//conf = self.currentCall?.conference != nil ? self.currentCall!.conference! : core.findConferenceInformationFromUri(uri: (self.currentCall?.remoteContactAddress)!)
|
||||
if self.currentCall?.remoteContactAddress != nil {
|
||||
let conf = core.findConferenceInformationFromUri(uri: (self.currentCall?.remoteContactAddress)!)
|
||||
DispatchQueue.main.async {
|
||||
self.isConference = conf != nil
|
||||
if self.isConference {
|
||||
self.displayName = conf?.subject ?? ""
|
||||
self.participantList = []
|
||||
conf?.participantInfos.forEach({ participantInfo in
|
||||
if participantInfo.address != nil {
|
||||
self.participantList.append(ParticipantModel(address: participantInfo.address!))
|
||||
}
|
||||
})
|
||||
self.addConferenceCallBacks()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addConferenceCallBacks() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
self.mConferenceSuscriptions.insert(
|
||||
self.currentCall?.conference?.publisher?.onActiveSpeakerParticipantDevice?.postOnMainQueue {(cbValue: (conference: Conference, participantDevice: ParticipantDevice)) in
|
||||
let direction = cbValue.participantDevice.getStreamCapability(streamType: StreamType.Video)
|
||||
|
||||
self.receiveVideo = direction == MediaDirection.SendRecv || direction == MediaDirection.SendOnly
|
||||
|
||||
if cbValue.participantDevice.address != nil {
|
||||
self.activeSpeakerParticipant = ParticipantModel(address: cbValue.participantDevice.address!)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func terminateCall() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
if self.currentCall != nil {
|
||||
|
|
@ -186,6 +232,33 @@ class CallViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func displayMyVideo() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
if self.currentCall != nil {
|
||||
do {
|
||||
let params = try core.createCallParams(call: self.currentCall)
|
||||
|
||||
if params.videoEnabled {
|
||||
if params.videoDirection == MediaDirection.SendRecv {
|
||||
params.videoDirection = MediaDirection.RecvOnly
|
||||
} else if params.videoDirection == MediaDirection.RecvOnly {
|
||||
params.videoDirection = MediaDirection.SendRecv
|
||||
}
|
||||
}
|
||||
|
||||
try self.currentCall!.update(params: params)
|
||||
|
||||
let video = params.videoDirection == MediaDirection.SendRecv
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
self.videoDisplayed = video
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func switchCamera() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
let currentDevice = core.videoDevice
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ class ConversationsListViewModel: ObservableObject {
|
|||
var conversationsListTmp: [ConversationModel] = []
|
||||
|
||||
chatRooms.forEach { chatRoom in
|
||||
//let disabledBecauseNotSecured = (account?.isInSecureMode() == true && !chatRoom.hasCapability) ? Capabilities.Encrypted.toInt() : 0
|
||||
if chatRoom.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue) {
|
||||
}
|
||||
|
||||
|
|
@ -54,32 +53,6 @@ class ConversationsListViewModel: ObservableObject {
|
|||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
conversationsListTmp.append(model)
|
||||
}
|
||||
/*
|
||||
else {
|
||||
val participants = chatRoom.participants
|
||||
val found = participants.find {
|
||||
// Search in address but also in contact name if exists
|
||||
val model =
|
||||
coreContext.contactsManager.getContactAvatarModelForAddress(it.address)
|
||||
model.contactName?.contains(
|
||||
filter,
|
||||
ignoreCase = true
|
||||
) == true || it.address.asStringUriOnly().contains(
|
||||
filter,
|
||||
ignoreCase = true
|
||||
)
|
||||
}
|
||||
if (
|
||||
found != null ||
|
||||
chatRoom.peerAddress.asStringUriOnly().contains(filter, ignoreCase = true) ||
|
||||
chatRoom.subject.orEmpty().contains(filter, ignoreCase = true)
|
||||
) {
|
||||
val model = ConversationModel(chatRoom, disabledBecauseNotSecured)
|
||||
list.add(model)
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if !self.conversationsList.isEmpty {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ struct Avatar: View {
|
|||
? contactAvatarModel.name.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.frame(width: avatarSize, height: avatarSize)
|
||||
.clipShape(Circle())
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue