forked from mirrors/linphone-iphone
Add Meeting Waiting Room
This commit is contained in:
parent
0299640c2c
commit
601be3ebed
13 changed files with 908 additions and 51 deletions
|
|
@ -63,6 +63,8 @@
|
|||
D732A9132B04C7A300DB42BA /* HistoryListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A9122B04C7A300DB42BA /* HistoryListFragment.swift */; };
|
||||
D732A9152B04C7FE00DB42BA /* HistoryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A9142B04C7FE00DB42BA /* HistoryListViewModel.swift */; };
|
||||
D732A91B2B061BD900DB42BA /* HistoryListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A91A2B061BD900DB42BA /* HistoryListBottomSheet.swift */; };
|
||||
D73449992BC6932A00778C56 /* MeetingWaitingRoomFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73449982BC6932A00778C56 /* MeetingWaitingRoomFragment.swift */; };
|
||||
D734499B2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D734499A2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift */; };
|
||||
D748BF2C2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */; };
|
||||
D748BF2E2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */; };
|
||||
D74C9CF82ACACECE0021626A /* WelcomePage1Fragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74C9CF72ACACECE0021626A /* WelcomePage1Fragment.swift */; };
|
||||
|
|
@ -200,6 +202,8 @@
|
|||
D732A9122B04C7A300DB42BA /* HistoryListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryListFragment.swift; sourceTree = "<group>"; };
|
||||
D732A9142B04C7FE00DB42BA /* HistoryListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryListViewModel.swift; sourceTree = "<group>"; };
|
||||
D732A91A2B061BD900DB42BA /* HistoryListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryListBottomSheet.swift; sourceTree = "<group>"; };
|
||||
D73449982BC6932A00778C56 /* MeetingWaitingRoomFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingWaitingRoomFragment.swift; sourceTree = "<group>"; };
|
||||
D734499A2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingWaitingRoomViewModel.swift; sourceTree = "<group>"; };
|
||||
D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountLoginFragment.swift; sourceTree = "<group>"; };
|
||||
D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountWarningFragment.swift; sourceTree = "<group>"; };
|
||||
D74C9CF72ACACECE0021626A /* WelcomePage1Fragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomePage1Fragment.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -610,6 +614,7 @@
|
|||
D720E6AB2BAD81C800DDFD87 /* Model */,
|
||||
D7B99E972B29B37F00BE7BF2 /* ViewModel */,
|
||||
D7B5678D2B28888F00DE63EB /* CallView.swift */,
|
||||
D73449982BC6932A00778C56 /* MeetingWaitingRoomFragment.swift */,
|
||||
);
|
||||
path = Call;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -618,6 +623,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D7B99E982B29B39000BE7BF2 /* CallViewModel.swift */,
|
||||
D734499A2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift */,
|
||||
);
|
||||
path = ViewModel;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -890,6 +896,7 @@
|
|||
D70A26F02B7D02E6006CC8FC /* ConversationViewModel.swift in Sources */,
|
||||
D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */,
|
||||
D7E6D04D2AEBD77600A57AAF /* CustomBottomSheet.swift in Sources */,
|
||||
D73449992BC6932A00778C56 /* MeetingWaitingRoomFragment.swift in Sources */,
|
||||
D7C48DF42AFA66F900D938CB /* EditContactController.swift in Sources */,
|
||||
66C491FF2B24D4AC00CEA16D /* FileUtils.swift in Sources */,
|
||||
D74C9D012ACB098C0021626A /* PermissionManager.swift in Sources */,
|
||||
|
|
@ -898,6 +905,7 @@
|
|||
D732A9152B04C7FE00DB42BA /* HistoryListViewModel.swift in Sources */,
|
||||
D71FCA7F2AE1397200D2E43E /* ContactsListViewModel.swift in Sources */,
|
||||
D71FCA812AE14CFC00D2E43E /* ContactsListFragment.swift in Sources */,
|
||||
D734499B2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift in Sources */,
|
||||
D719ABB72ABC67BF00B41C10 /* LinphoneApp.swift in Sources */,
|
||||
D732A91B2B061BD900DB42BA /* HistoryListBottomSheet.swift in Sources */,
|
||||
D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ final class CoreContext: ObservableObject {
|
|||
|
||||
self.mCore.videoCaptureEnabled = true
|
||||
self.mCore.videoDisplayEnabled = true
|
||||
self.mCore.videoPreviewEnabled = false
|
||||
|
||||
self.mCoreSuscriptions.insert(self.mCore.publisher?.onGlobalStateChanged?.postOnMainQueue { (cbVal: (core: Core, state: GlobalState, message: String)) in
|
||||
if cbVal.state == GlobalState.On {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ struct LinphoneApp: App {
|
|||
@State private var historyListViewModel: HistoryListViewModel?
|
||||
@State private var startCallViewModel: StartCallViewModel?
|
||||
@State private var callViewModel: CallViewModel?
|
||||
@State private var meetingWaitingRoomViewModel: MeetingWaitingRoomViewModel?
|
||||
@State private var conversationsListViewModel: ConversationsListViewModel?
|
||||
@State private var conversationViewModel: ConversationViewModel?
|
||||
|
||||
|
|
@ -99,6 +100,7 @@ struct LinphoneApp: App {
|
|||
&& historyListViewModel != nil
|
||||
&& startCallViewModel != nil
|
||||
&& callViewModel != nil
|
||||
&& meetingWaitingRoomViewModel != nil
|
||||
&& conversationsListViewModel != nil
|
||||
&& conversationViewModel != nil {
|
||||
ContentView(
|
||||
|
|
@ -108,6 +110,7 @@ struct LinphoneApp: App {
|
|||
historyListViewModel: historyListViewModel!,
|
||||
startCallViewModel: startCallViewModel!,
|
||||
callViewModel: callViewModel!,
|
||||
meetingWaitingRoomViewModel: meetingWaitingRoomViewModel!,
|
||||
conversationsListViewModel: conversationsListViewModel!,
|
||||
conversationViewModel: conversationViewModel!
|
||||
)
|
||||
|
|
@ -123,6 +126,7 @@ struct LinphoneApp: App {
|
|||
historyListViewModel = HistoryListViewModel()
|
||||
startCallViewModel = StartCallViewModel()
|
||||
callViewModel = CallViewModel()
|
||||
meetingWaitingRoomViewModel = MeetingWaitingRoomViewModel()
|
||||
conversationsListViewModel = ConversationsListViewModel()
|
||||
conversationViewModel = ConversationViewModel()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -241,6 +241,9 @@
|
|||
},
|
||||
"Conditions de service" : {
|
||||
|
||||
},
|
||||
"Connexion à la réunion" : {
|
||||
|
||||
},
|
||||
"Contacts" : {
|
||||
|
||||
|
|
@ -443,6 +446,9 @@
|
|||
},
|
||||
"Marquer comme non lu" : {
|
||||
|
||||
},
|
||||
"Mercredi 10 Avril 2024 | 2:41 PM - 2:42 PM" : {
|
||||
|
||||
},
|
||||
"Message" : {
|
||||
|
||||
|
|
@ -553,6 +559,9 @@
|
|||
},
|
||||
"Register" : {
|
||||
|
||||
},
|
||||
"Rejoindre" : {
|
||||
|
||||
},
|
||||
"Remove from favourites" : {
|
||||
|
||||
|
|
@ -696,6 +705,9 @@
|
|||
},
|
||||
"Vos communications sont en sécurité grâce aux **Chiffrement de bout en bout**." : {
|
||||
|
||||
},
|
||||
"Vous allez rejoindre la réunion dans quelques instants..." : {
|
||||
|
||||
},
|
||||
"Welcome" : {
|
||||
|
||||
|
|
|
|||
|
|
@ -329,7 +329,7 @@ extension ProviderDelegate: CXProviderDelegate {
|
|||
CoreContext.shared.doOnCoreQueue { core in
|
||||
do {
|
||||
core.configureAudioSession()
|
||||
try TelecomManager.shared.doCall(core: core, addr: addr!, isSas: callInfo?.sasEnabled ?? false, isVideo: callInfo?.videoEnabled ?? false, isConference: callInfo?.isConference ?? false)
|
||||
try TelecomManager.shared.doCall(core: core, addr: addr!, isSas: callInfo?.sasEnabled ?? false, isVideo: ((callInfo?.videoEnabled ?? false) && core.videoPreviewEnabled), isConference: callInfo?.isConference ?? false)
|
||||
action.fulfill()
|
||||
} catch {
|
||||
Log.info("CallKit: Call started failed because \(error)")
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ class TelecomManager: ObservableObject {
|
|||
@Published var refreshCallViewModel: Bool = false
|
||||
@Published var remainingCall: Bool = false
|
||||
@Published var callConnected: Bool = false
|
||||
@Published var meetingWaitingRoomDisplayed: Bool = false
|
||||
@Published var meetingWaitingRoomSelected: Address?
|
||||
@Published var meetingWaitingRoomName: String = ""
|
||||
|
||||
var actionToFulFill: CXCallAction?
|
||||
var callkitAudioSessionActivated: Bool?
|
||||
|
|
@ -215,6 +218,7 @@ class TelecomManager: ObservableObject {
|
|||
|
||||
if isConference {
|
||||
lcallParams.videoEnabled = true
|
||||
lcallParams.videoDirection = isVideo ? MediaDirection.SendRecv : MediaDirection.RecvOnly
|
||||
/* if (ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! != .AudioOnly) {
|
||||
lcallParams.videoEnabled = true
|
||||
lcallParams.videoDirection = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true ? .SendRecv : .RecvOnly
|
||||
|
|
@ -393,6 +397,7 @@ class TelecomManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if self.remoteConfVideo && self.remoteConfVideo != oldRemoteConfVideo {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
|
||||
|
|
@ -400,6 +405,7 @@ class TelecomManager: ObservableObject {
|
|||
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if self.remoteConfVideo {
|
||||
Log.info("[Call] Remote video is activated")
|
||||
|
|
@ -444,8 +450,11 @@ class TelecomManager: ObservableObject {
|
|||
self.isPausedByRemote = false
|
||||
}
|
||||
|
||||
if (cstate == Call.State.Connected) {
|
||||
if cstate == Call.State.Connected {
|
||||
self.callConnected = true
|
||||
|
||||
self.meetingWaitingRoomSelected = nil
|
||||
self.meetingWaitingRoomDisplayed = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -538,7 +538,7 @@ struct CallView: View {
|
|||
if contactAvatarModel != nil {
|
||||
Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 200, hidePresence: true)
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
displayVideo = true
|
||||
}
|
||||
}
|
||||
|
|
@ -554,7 +554,7 @@ struct CallView: View {
|
|||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
displayVideo = true
|
||||
}
|
||||
}
|
||||
|
|
@ -569,7 +569,7 @@ struct CallView: View {
|
|||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
displayVideo = true
|
||||
}
|
||||
}
|
||||
|
|
@ -750,7 +750,7 @@ struct CallView: View {
|
|||
|
||||
if telecomManager.outgoingCallStarted {
|
||||
VStack {
|
||||
ActivityIndicator()
|
||||
ActivityIndicator(color: .white)
|
||||
.frame(width: 20, height: 20)
|
||||
.padding(.top, 60)
|
||||
|
||||
|
|
@ -772,7 +772,8 @@ struct CallView: View {
|
|||
}
|
||||
.onDisappear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
callViewModel.getConference()
|
||||
// callViewModel.getConference()
|
||||
callViewModel.waitingForCreatedStateConference()
|
||||
}
|
||||
}
|
||||
.background(.clear)
|
||||
|
|
@ -799,6 +800,8 @@ struct CallView: View {
|
|||
angleDegree = -90
|
||||
} else if orientation == .landscapeRight {
|
||||
angleDegree = 90
|
||||
} else if UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height {
|
||||
angleDegree = 90
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -820,6 +823,8 @@ struct CallView: View {
|
|||
angleDegree = -90
|
||||
} else if orientation == .landscapeRight {
|
||||
angleDegree = 90
|
||||
} else if UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height {
|
||||
angleDegree = 90
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
542
Linphone/UI/Call/MeetingWaitingRoomFragment.swift
Normal file
542
Linphone/UI/Call/MeetingWaitingRoomFragment.swift
Normal file
|
|
@ -0,0 +1,542 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-iphone
|
||||
*
|
||||
* 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 SwiftUI
|
||||
import linphonesw
|
||||
import AVFAudio
|
||||
|
||||
struct MeetingWaitingRoomFragment: View {
|
||||
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
@ObservedObject private var telecomManager = TelecomManager.shared
|
||||
|
||||
@ObservedObject var meetingWaitingRoomViewModel: MeetingWaitingRoomViewModel
|
||||
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
|
||||
let pub = NotificationCenter.default.publisher(for: AVAudioSession.routeChangeNotification)
|
||||
|
||||
@State var audioRouteSheet: Bool = false
|
||||
@State var options: Int = 1
|
||||
@State var angleDegree = 0.0
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
|
||||
if #available(iOS 16.0, *), idiom != .pad {
|
||||
innerView(geometry: geometry)
|
||||
.sheet(isPresented: $audioRouteSheet, onDismiss: {
|
||||
audioRouteSheet = false
|
||||
}) {
|
||||
innerBottomSheet()
|
||||
.presentationDetents([.fraction(0.3)])
|
||||
}
|
||||
.onAppear {
|
||||
meetingWaitingRoomViewModel.enableAVAudioSession()
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
meetingWaitingRoomViewModel.disableAVAudioSession()
|
||||
}
|
||||
} else {
|
||||
innerView(geometry: geometry)
|
||||
.halfSheet(showSheet: $audioRouteSheet) {
|
||||
innerBottomSheet()
|
||||
} onDismiss: {
|
||||
audioRouteSheet = false
|
||||
}
|
||||
.onAppear {
|
||||
meetingWaitingRoomViewModel.enableAVAudioSession()
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
meetingWaitingRoomViewModel.disableAVAudioSession()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func innerView(geometry: GeometryProxy) -> some View {
|
||||
VStack {
|
||||
if #available(iOS 16.0, *) {
|
||||
Rectangle()
|
||||
.foregroundColor(Color.orangeMain500)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.frame(height: 0)
|
||||
} else if idiom != .pad && !(orientation == .landscapeLeft || orientation == .landscapeRight
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
|
||||
Rectangle()
|
||||
.foregroundColor(Color.orangeMain500)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.frame(height: 1)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Button {
|
||||
withAnimation {
|
||||
meetingWaitingRoomViewModel.disableVideoPreview()
|
||||
telecomManager.meetingWaitingRoomSelected = nil
|
||||
telecomManager.meetingWaitingRoomDisplayed = false
|
||||
}
|
||||
} label: {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
|
||||
Text(telecomManager.meetingWaitingRoomName)
|
||||
.default_text_style_white_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(height: 40)
|
||||
.zIndex(1)
|
||||
|
||||
HStack {
|
||||
Button {
|
||||
} label: {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
.hidden()
|
||||
|
||||
Text("Mercredi 10 Avril 2024 | 2:41 PM - 2:42 PM")
|
||||
.foregroundStyle(.white)
|
||||
.default_text_style_white(styleSize: 12)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(height: 40)
|
||||
.padding(.top, -25)
|
||||
.zIndex(1)
|
||||
|
||||
if !telecomManager.callStarted {
|
||||
ZStack {
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
if meetingWaitingRoomViewModel.avatarDisplayed {
|
||||
ZStack {
|
||||
|
||||
if meetingWaitingRoomViewModel.isRemoteDeviceTrusted {
|
||||
Circle()
|
||||
.fill(Color.blueInfo500)
|
||||
.frame(width: 206, height: 206)
|
||||
}
|
||||
|
||||
if meetingWaitingRoomViewModel.avatarModel != nil {
|
||||
Avatar(contactAvatarModel: meetingWaitingRoomViewModel.avatarModel!, avatarSize: 200, hidePresence: true)
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
if meetingWaitingRoomViewModel.isRemoteDeviceTrusted {
|
||||
VStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
Image("trusted")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 15)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(width: 200, height: 200)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(
|
||||
width:
|
||||
angleDegree == 0
|
||||
? 120 * ceil((geometry.size.width - 20) / 120)
|
||||
: 160 * ceil((geometry.size.height - 160) / 160),
|
||||
height:
|
||||
angleDegree == 0
|
||||
? 160 * ceil((geometry.size.width - 20) / 120)
|
||||
: 120 * ceil((geometry.size.height - 160) / 160)
|
||||
)
|
||||
|
||||
LinphoneVideoViewHolder { view in
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativePreviewWindow = view
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
width:
|
||||
angleDegree == 0
|
||||
? 120 * ceil((geometry.size.width - 20) / 120)
|
||||
: 160 * ceil((geometry.size.height - 160) / 160),
|
||||
height:
|
||||
angleDegree == 0
|
||||
? 160 * ceil((geometry.size.width - 20) / 120)
|
||||
: 120 * ceil((geometry.size.height - 160) / 160)
|
||||
)
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
if meetingWaitingRoomViewModel.videoDisplayed {
|
||||
Button {
|
||||
meetingWaitingRoomViewModel.switchCamera()
|
||||
} label: {
|
||||
Image("camera-rotate")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 30, height: 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
Text(meetingWaitingRoomViewModel.userName)
|
||||
.foregroundStyle(.white)
|
||||
.default_text_style_white(styleSize: 20)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.all, 10)
|
||||
.frame(maxWidth: geometry.size.width - 20, maxHeight: geometry.size.height - (angleDegree == 0 ? 250 : 160))
|
||||
}
|
||||
.background(Color.gray600)
|
||||
.frame(maxWidth: geometry.size.width - 20, maxHeight: geometry.size.height - (angleDegree == 0 ? 250 : 160))
|
||||
.cornerRadius(20)
|
||||
.padding(.horizontal, 10)
|
||||
.onDisappear {
|
||||
meetingWaitingRoomViewModel.disableVideoPreview()
|
||||
}
|
||||
|
||||
if angleDegree != 0 {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
!meetingWaitingRoomViewModel.videoDisplayed ? meetingWaitingRoomViewModel.enableVideoPreview() : meetingWaitingRoomViewModel.disableVideoPreview()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(meetingWaitingRoomViewModel.videoDisplayed ? "video-camera" : "video-camera-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle())
|
||||
.frame(width: 60, height: 60)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.padding(.horizontal, 5)
|
||||
|
||||
Button {
|
||||
meetingWaitingRoomViewModel.toggleMuteMicrophone()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(meetingWaitingRoomViewModel.micMutted ? "microphone-slash" : "microphone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle())
|
||||
.frame(width: 60, height: 60)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.padding(.horizontal, 5)
|
||||
|
||||
Button {
|
||||
if AVAudioSession.sharedInstance().availableInputs != nil
|
||||
&& !AVAudioSession.sharedInstance().availableInputs!.filter({ $0.portType.rawValue.contains("Bluetooth") }).isEmpty {
|
||||
|
||||
audioRouteSheet = true
|
||||
} else {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(
|
||||
AVAudioSession.sharedInstance().currentRoute.outputs.filter(
|
||||
{ $0.portType.rawValue == "Speaker" }
|
||||
).isEmpty ? .speaker : .none
|
||||
)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(meetingWaitingRoomViewModel.imageAudioRoute)
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
.onAppear(perform: meetingWaitingRoomViewModel.getAudioRouteImage)
|
||||
.onReceive(pub) { _ in
|
||||
self.meetingWaitingRoomViewModel.getAudioRouteImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle())
|
||||
.frame(width: 60, height: 60)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.padding(.horizontal, 5)
|
||||
|
||||
Spacer()
|
||||
|
||||
if angleDegree != 0 {
|
||||
Button(action: {
|
||||
meetingWaitingRoomViewModel.joinMeeting()
|
||||
}, label: {
|
||||
Text("Rejoindre")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.padding(.horizontal, 10)
|
||||
.frame(width: (geometry.size.width - 20) / 2)
|
||||
}
|
||||
}
|
||||
.padding(.all, 10)
|
||||
|
||||
if angleDegree == 0 {
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
meetingWaitingRoomViewModel.joinMeeting()
|
||||
}, label: {
|
||||
Text("Rejoindre")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.padding(.bottom)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
}
|
||||
} else {
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
Text("Connexion à la réunion")
|
||||
.default_text_style_white_600(styleSize: 24)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
|
||||
Text("Vous allez rejoindre la réunion dans quelques instants...")
|
||||
.default_text_style_white(styleSize: 16)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
|
||||
ActivityIndicator(color: Color.orangeMain500)
|
||||
.frame(width: 35, height: 35)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.background(Color.gray900)
|
||||
.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
|
||||
} else if UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height {
|
||||
angleDegree = 90
|
||||
}
|
||||
}
|
||||
|
||||
meetingWaitingRoomViewModel.orientationUpdate(orientation: orientation)
|
||||
}
|
||||
.onAppear {
|
||||
if orientation == .portrait || orientation == .portraitUpsideDown {
|
||||
angleDegree = 0
|
||||
} else {
|
||||
if orientation == .landscapeLeft {
|
||||
angleDegree = -90
|
||||
} else if orientation == .landscapeRight {
|
||||
angleDegree = 90
|
||||
} else if UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height {
|
||||
angleDegree = 90
|
||||
}
|
||||
}
|
||||
|
||||
meetingWaitingRoomViewModel.orientationUpdate(orientation: orientation)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func innerBottomSheet() -> some View {
|
||||
VStack(spacing: 0) {
|
||||
Button(action: {
|
||||
options = 1
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
|
||||
if meetingWaitingRoomViewModel.isHeadPhoneAvailable() {
|
||||
try AVAudioSession.sharedInstance().setPreferredInput(AVAudioSession.sharedInstance().availableInputs?.filter({ $0.portType.rawValue.contains("Receiver") }).first)
|
||||
} else {
|
||||
try AVAudioSession.sharedInstance().setPreferredInput(AVAudioSession.sharedInstance().availableInputs?.first)
|
||||
}
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}, label: {
|
||||
HStack {
|
||||
Image(options == 1 ? "radio-button-fill" : "radio-button")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
|
||||
Text(!meetingWaitingRoomViewModel.isHeadPhoneAvailable() ? "Earpiece" : "Headphones")
|
||||
.default_text_style_white(styleSize: 15)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(!meetingWaitingRoomViewModel.isHeadPhoneAvailable() ? "ear" : "headset")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
})
|
||||
.frame(maxHeight: .infinity)
|
||||
|
||||
Button(action: {
|
||||
options = 2
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}, label: {
|
||||
HStack {
|
||||
Image(options == 2 ? "radio-button-fill" : "radio-button")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
|
||||
Text("Speaker")
|
||||
.default_text_style_white(styleSize: 15)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("speaker-high")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
})
|
||||
.frame(maxHeight: .infinity)
|
||||
|
||||
Button(action: {
|
||||
options = 3
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
|
||||
try AVAudioSession.sharedInstance().setPreferredInput(AVAudioSession.sharedInstance().availableInputs?.filter({ $0.portType.rawValue.contains("Bluetooth") }).first)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}, label: {
|
||||
HStack {
|
||||
Image(options == 3 ? "radio-button-fill" : "radio-button")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
|
||||
Text("Bluetooth")
|
||||
.default_text_style_white(styleSize: 15)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("bluetooth")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
})
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.background(Color.gray600)
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
MeetingWaitingRoomFragment(meetingWaitingRoomViewModel: MeetingWaitingRoomViewModel())
|
||||
}
|
||||
|
|
@ -53,7 +53,6 @@ class CallViewModel: ObservableObject {
|
|||
@Published var activeSpeakerName: String = ""
|
||||
@Published var myParticipantModel: ParticipantModel? = nil
|
||||
|
||||
|
||||
private var mConferenceSuscriptions = Set<AnyCancellable?>()
|
||||
|
||||
var calls: [Call] = []
|
||||
|
|
@ -98,19 +97,10 @@ class CallViewModel: ObservableObject {
|
|||
self.remoteAddressString = String(self.currentCall!.remoteAddress!.asStringUriOnly().dropFirst(4))
|
||||
self.remoteAddress = self.currentCall!.remoteAddress!
|
||||
|
||||
let friend = ContactsManager.shared.getFriendWithAddress(address: self.currentCall!.remoteAddress!)
|
||||
if friend != nil && friend!.address != nil && friend!.address!.displayName != nil {
|
||||
self.displayName = friend!.address!.displayName!
|
||||
} else {
|
||||
if self.currentCall!.remoteAddress!.displayName != nil {
|
||||
self.displayName = self.currentCall!.remoteAddress!.displayName!
|
||||
} else if self.currentCall!.remoteAddress!.username != nil {
|
||||
self.displayName = self.currentCall!.remoteAddress!.username!
|
||||
}
|
||||
}
|
||||
self.displayName = self.currentCall?.conference?.subject ?? ""
|
||||
|
||||
//self.avatarModel = ???
|
||||
self.micMutted = self.currentCall!.microphoneMuted
|
||||
self.micMutted = self.currentCall!.microphoneMuted || !core.micEnabled
|
||||
self.isRecording = self.currentCall!.params!.isRecording
|
||||
self.isPaused = self.isCallPaused()
|
||||
self.timeElapsed = self.currentCall?.duration ?? 0
|
||||
|
|
@ -128,7 +118,7 @@ class CallViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
self.getCallsList()
|
||||
self.getConference()
|
||||
self.waitingForCreatedStateConference()
|
||||
}
|
||||
|
||||
self.callSuscriptions.insert(self.currentCall!.publisher?.onEncryptionChanged?.postOnMainQueue {(cbVal: (call: Call, on: Bool, authenticationToken: String?)) in
|
||||
|
|
@ -157,19 +147,15 @@ class CallViewModel: ObservableObject {
|
|||
|
||||
if self.currentCall?.callLog?.localAddress != nil {
|
||||
self.myParticipantModel = ParticipantModel(address: self.currentCall!.callLog!.localAddress!)
|
||||
print("ParticipantModelParticipantModel myParticipantModel \(self.currentCall!.callLog!.localAddress!.asStringUriOnly())")
|
||||
print("ParticipantModelParticipantModel 1 \(conf.me?.address!.asStringUriOnly())")
|
||||
}
|
||||
|
||||
if conf.activeSpeakerParticipantDevice?.address != nil {
|
||||
self.activeSpeakerParticipant = ParticipantModel(address: conf.activeSpeakerParticipantDevice!.address!)
|
||||
print("ParticipantModelParticipantModel activeSpeakerParticipantDevice 1 \(conf.activeSpeakerParticipantDevice!.address!.asStringUriOnly())")
|
||||
print("ParticipantModelParticipantModel 2 \(conf.activeSpeakerParticipantDevice!.address!.asStringUriOnly())")
|
||||
} else if conf.participantList.first?.address != nil {
|
||||
self.activeSpeakerParticipant = ParticipantModel(address: conf.participantList.first!.address!)
|
||||
print("ParticipantModelParticipantModel activeSpeakerParticipantDevice 2 \(conf.participantList.first!.address!.asStringUriOnly())")
|
||||
} else {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
self.getConference()
|
||||
}
|
||||
print("ParticipantModelParticipantModel 3 \(conf.participantList.first!.address!.asStringUriOnly())")
|
||||
}
|
||||
|
||||
if self.activeSpeakerParticipant != nil {
|
||||
|
|
@ -187,7 +173,7 @@ class CallViewModel: ObservableObject {
|
|||
|
||||
conf.participantDeviceList.forEach({ participantDevice in
|
||||
self.participantList.append(ParticipantModel(address: participantDevice.address!))
|
||||
print("ParticipantModelParticipantModel participantDevice \(participantDevice.address!.asStringUriOnly())")
|
||||
print("ParticipantModelParticipantModel 4 \(participantDevice.address!.asStringUriOnly()) \(conf.isIn) \(conf.state) \(self.currentCall?.state)")
|
||||
})
|
||||
|
||||
//self.addConferenceCallBacks()
|
||||
|
|
@ -198,6 +184,17 @@ class CallViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func waitingForCreatedStateConference() {
|
||||
self.mConferenceSuscriptions.insert(
|
||||
self.currentCall?.conference?.publisher?.onStateChanged?.postOnMainQueue {(cbValue: (conference: Conference, state: Conference.State)) in
|
||||
if cbValue.state == .Created {
|
||||
print("ParticipantModelParticipantModel 0 \(cbValue.conference.isIn) \(cbValue.conference.state) \(self.currentCall?.state) \(cbValue.state)")
|
||||
self.getConference()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
func addConferenceCallBacks() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
self.mConferenceSuscriptions.insert(
|
||||
|
|
@ -298,10 +295,16 @@ class CallViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func toggleMuteMicrophone() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
coreContext.doOnCoreQueue { core in
|
||||
if self.currentCall != nil {
|
||||
self.currentCall!.microphoneMuted = !self.currentCall!.microphoneMuted
|
||||
self.micMutted = self.currentCall!.microphoneMuted
|
||||
if !core.micEnabled && !self.currentCall!.microphoneMuted {
|
||||
core.micEnabled = true
|
||||
} else {
|
||||
self.currentCall!.microphoneMuted = !self.currentCall!.microphoneMuted
|
||||
}
|
||||
|
||||
self.micMutted = self.currentCall!.microphoneMuted || !core.micEnabled
|
||||
|
||||
Log.info(
|
||||
"[CallViewModel] Microphone mute switch \(self.micMutted)"
|
||||
)
|
||||
|
|
@ -436,18 +439,6 @@ class CallViewModel: ObservableObject {
|
|||
return false
|
||||
}
|
||||
|
||||
func getAudioRoute() -> Int {
|
||||
if AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue == "Speaker" }).isEmpty {
|
||||
if AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue.contains("Bluetooth") }).isEmpty {
|
||||
return 1
|
||||
} else {
|
||||
return 3
|
||||
}
|
||||
} else {
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
func orientationUpdate(orientation: UIDeviceOrientation) {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
let oldLinphoneOrientation = core.deviceRotation
|
||||
|
|
|
|||
261
Linphone/UI/Call/ViewModel/MeetingWaitingRoomViewModel.swift
Normal file
261
Linphone/UI/Call/ViewModel/MeetingWaitingRoomViewModel.swift
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-iphone
|
||||
*
|
||||
* 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
|
||||
import SwiftUI
|
||||
import AVFAudio
|
||||
|
||||
class MeetingWaitingRoomViewModel: ObservableObject {
|
||||
var coreContext = CoreContext.shared
|
||||
var telecomManager = TelecomManager.shared
|
||||
|
||||
@Published var userName: String = ""
|
||||
@Published var avatarModel: ContactAvatarModel?
|
||||
@Published var micMutted: Bool = false
|
||||
@Published var isRemoteDeviceTrusted: Bool = false
|
||||
@Published var selectedCall: Call?
|
||||
@Published var isConference: Bool = false
|
||||
@Published var videoDisplayed: Bool = false
|
||||
@Published var avatarDisplayed: Bool = true
|
||||
@Published var imageAudioRoute: String = ""
|
||||
|
||||
init() {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .voiceChat, options: .allowBluetooth)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
if !telecomManager.callStarted {
|
||||
self.resetMeetingRoomView()
|
||||
}
|
||||
}
|
||||
|
||||
func resetMeetingRoomView() {
|
||||
if self.telecomManager.meetingWaitingRoomSelected != nil {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
|
||||
let conf = core.findConferenceInformationFromUri(uri: self.telecomManager.meetingWaitingRoomSelected!)
|
||||
|
||||
if conf != nil && conf!.uri != nil {
|
||||
let confNameTmp = conf?.subject ?? "Conference"
|
||||
var userNameTmp = ""
|
||||
|
||||
let friend = core.defaultAccount != nil && core.defaultAccount!.contactAddress != nil
|
||||
? ContactsManager.shared.getFriendWithAddress(address: core.defaultAccount!.contactAddress!)
|
||||
: nil
|
||||
|
||||
if friend != nil && friend!.address != nil && friend!.address!.displayName != nil {
|
||||
userNameTmp = friend!.address!.displayName!
|
||||
} else {
|
||||
if core.defaultAccount!.contactAddress!.displayName != nil {
|
||||
userNameTmp = core.defaultAccount!.contactAddress!.displayName!
|
||||
} else if core.defaultAccount!.contactAddress!.username != nil {
|
||||
userNameTmp = core.defaultAccount!.contactAddress!.username!
|
||||
}
|
||||
}
|
||||
|
||||
let avatarModelTmp = friend != nil
|
||||
? ContactsManager.shared.avatarListModel.first(where: {
|
||||
$0.friend!.name == friend!.name
|
||||
&& $0.friend!.address!.asStringUriOnly() == core.defaultAccount!.contactAddress!.asStringUriOnly()
|
||||
}) ?? ContactAvatarModel(friend: nil, name: userNameTmp, withPresence: false)
|
||||
: ContactAvatarModel(friend: nil, name: userNameTmp, withPresence: false)
|
||||
|
||||
if core.videoEnabled && !core.videoPreviewEnabled {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
core.videoPreviewEnabled = true
|
||||
self.videoDisplayed = true
|
||||
}
|
||||
}
|
||||
|
||||
core.micEnabled = true
|
||||
|
||||
let micMuttedTmp = !core.micEnabled
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if self.telecomManager.meetingWaitingRoomName.isEmpty || self.telecomManager.meetingWaitingRoomName != confNameTmp {
|
||||
self.telecomManager.meetingWaitingRoomName = confNameTmp
|
||||
}
|
||||
|
||||
self.userName = userNameTmp
|
||||
self.avatarModel = avatarModelTmp
|
||||
self.micMutted = micMuttedTmp
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func enableVideoPreview() {
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
if core.videoEnabled {
|
||||
self.videoDisplayed = true
|
||||
core.videoPreviewEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func disableVideoPreview() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
if core.videoEnabled {
|
||||
self.videoDisplayed = false
|
||||
core.videoPreviewEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func switchCamera() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
let currentDevice = core.videoDevice
|
||||
Log.info("[CallViewModel] Current camera device is \(currentDevice)")
|
||||
|
||||
core.videoDevicesList.forEach { camera in
|
||||
if camera != currentDevice && camera != "StaticImage: Static picture" {
|
||||
Log.info("[CallViewModel] New camera device will be \(camera)")
|
||||
do {
|
||||
try core.setVideodevice(newValue: camera)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func orientationUpdate(orientation: UIDeviceOrientation) {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
let oldLinphoneOrientation = core.deviceRotation
|
||||
var newRotation = 0
|
||||
switch orientation {
|
||||
case .portrait:
|
||||
newRotation = 0
|
||||
case .portraitUpsideDown:
|
||||
newRotation = 180
|
||||
case .landscapeRight:
|
||||
newRotation = 90
|
||||
case .landscapeLeft:
|
||||
newRotation = 270
|
||||
default:
|
||||
newRotation = oldLinphoneOrientation
|
||||
}
|
||||
|
||||
if oldLinphoneOrientation != newRotation {
|
||||
core.deviceRotation = newRotation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func enableMicrophone() {
|
||||
self.micMutted = false
|
||||
}
|
||||
|
||||
func toggleMuteMicrophone() {
|
||||
self.micMutted = !self.micMutted
|
||||
}
|
||||
|
||||
func enableAVAudioSession() {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setActive(true)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func disableAVAudioSession() {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setActive(false)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func getAudioRouteImage() {
|
||||
print("AVAudioSessionAVAudioSession getAudioRouteImage \(imageAudioRoute)")
|
||||
imageAudioRoute = AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue == "Speaker" }).isEmpty
|
||||
? (
|
||||
AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue.contains("Bluetooth") }).isEmpty
|
||||
? (
|
||||
isHeadPhoneAvailable()
|
||||
? "headset"
|
||||
: "speaker-slash"
|
||||
)
|
||||
: "bluetooth"
|
||||
)
|
||||
: "speaker-high"
|
||||
|
||||
print("AVAudioSessionAVAudioSession getAudioRouteImage \(imageAudioRoute)")
|
||||
}
|
||||
|
||||
func isHeadPhoneAvailable() -> Bool {
|
||||
guard let availableInputs = AVAudioSession.sharedInstance().availableInputs else {return false}
|
||||
for inputDevice in availableInputs {
|
||||
if inputDevice.portType == .headsetMic || inputDevice.portType == .headphones {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func joinMeeting() {
|
||||
if self.telecomManager.meetingWaitingRoomSelected != nil {
|
||||
if self.micMutted {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.micEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
let audioSession = imageAudioRoute
|
||||
|
||||
telecomManager.doCallWithCore(
|
||||
addr: self.telecomManager.meetingWaitingRoomSelected!, isVideo: self.videoDisplayed, isConference: true
|
||||
)
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
switch audioSession {
|
||||
case "bluetooth":
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
|
||||
try AVAudioSession.sharedInstance().setPreferredInput(AVAudioSession.sharedInstance().availableInputs?.filter({ $0.portType.rawValue.contains("Bluetooth") }).first)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
case "speaker-high":
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
default:
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
|
||||
if self.isHeadPhoneAvailable() {
|
||||
try AVAudioSession.sharedInstance().setPreferredInput(AVAudioSession.sharedInstance().availableInputs?.filter({ $0.portType.rawValue.contains("Receiver") }).first)
|
||||
} else {
|
||||
try AVAudioSession.sharedInstance().setPreferredInput(AVAudioSession.sharedInstance().availableInputs?.first)
|
||||
}
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -40,6 +40,7 @@ struct ContentView: View {
|
|||
@ObservedObject var historyListViewModel: HistoryListViewModel
|
||||
@ObservedObject var startCallViewModel: StartCallViewModel
|
||||
@ObservedObject var callViewModel: CallViewModel
|
||||
@ObservedObject var meetingWaitingRoomViewModel: MeetingWaitingRoomViewModel
|
||||
@ObservedObject var conversationsListViewModel: ConversationsListViewModel
|
||||
@ObservedObject var conversationViewModel: ConversationViewModel
|
||||
|
||||
|
|
@ -713,7 +714,7 @@ struct ContentView: View {
|
|||
isShowDismissPopup: $isShowDismissPopup
|
||||
)
|
||||
.zIndex(3)
|
||||
.transition(.move(edge: .bottom))
|
||||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||||
.onAppear {
|
||||
contactViewModel.indexDisplayedFriend = nil
|
||||
}
|
||||
|
|
@ -729,7 +730,7 @@ struct ContentView: View {
|
|||
resetCallView: {callViewModel.resetCallView()}
|
||||
)
|
||||
.zIndex(4)
|
||||
.transition(.move(edge: .bottom))
|
||||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||||
.sheet(isPresented: $showingDialer) {
|
||||
DialerBottomSheet(
|
||||
startCallViewModel: startCallViewModel,
|
||||
|
|
@ -748,7 +749,7 @@ struct ContentView: View {
|
|||
resetCallView: {callViewModel.resetCallView()}
|
||||
)
|
||||
.zIndex(4)
|
||||
.transition(.move(edge: .bottom))
|
||||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||||
.halfSheet(showSheet: $showingDialer) {
|
||||
DialerBottomSheet(
|
||||
startCallViewModel: startCallViewModel,
|
||||
|
|
@ -856,7 +857,16 @@ struct ContentView: View {
|
|||
}
|
||||
}
|
||||
|
||||
if telecomManager.callDisplayed && ((telecomManager.callInProgress && telecomManager.outgoingCallStarted) || telecomManager.callConnected) {
|
||||
if telecomManager.meetingWaitingRoomDisplayed {
|
||||
MeetingWaitingRoomFragment(meetingWaitingRoomViewModel: meetingWaitingRoomViewModel)
|
||||
.zIndex(3)
|
||||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||||
.onAppear {
|
||||
meetingWaitingRoomViewModel.resetMeetingRoomView()
|
||||
}
|
||||
}
|
||||
|
||||
if telecomManager.callDisplayed && ((telecomManager.callInProgress && telecomManager.outgoingCallStarted) || telecomManager.callConnected) && !telecomManager.meetingWaitingRoomDisplayed {
|
||||
CallView(callViewModel: callViewModel, fullscreenVideo: $fullscreenVideo, isShowCallsListFragment: $isShowCallsListFragment, isShowStartCallFragment: $isShowStartCallFragment)
|
||||
.zIndex(3)
|
||||
.transition(.scale.combined(with: .move(edge: .top)))
|
||||
|
|
@ -911,6 +921,7 @@ struct ContentView: View {
|
|||
historyListViewModel: HistoryListViewModel(),
|
||||
startCallViewModel: StartCallViewModel(),
|
||||
callViewModel: CallViewModel(),
|
||||
meetingWaitingRoomViewModel: MeetingWaitingRoomViewModel(),
|
||||
conversationsListViewModel: ConversationsListViewModel(),
|
||||
conversationViewModel: ConversationViewModel()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -215,11 +215,18 @@ struct HistoryListFragment: View {
|
|||
if historyListViewModel.callLogs[index].toAddress!.asStringUriOnly().hasPrefix("sip:conference-focus@sip.linphone.org") {
|
||||
do {
|
||||
//let reudumatin = try Factory.Instance.createAddress(addr: "sip:conference-focus@sip.linphone.org;conf-id=8~YNkpFOv;gr=0ee3f37f-6df2-0071-bb9a-a4e24be30135")
|
||||
/*
|
||||
let reutest = try Factory.Instance.createAddress(addr: "sip:conference-focus@sip.linphone.org;conf-id=iVs8XshC~;gr=0ee3f37f-6df2-0071-bb9a-a4e24be30135")
|
||||
|
||||
telecomManager.doCallWithCore(
|
||||
addr: reutest, isVideo: false, isConference: true
|
||||
)
|
||||
*/
|
||||
|
||||
let reutest = try Factory.Instance.createAddress(addr: "sip:conference-focus@sip.linphone.org;conf-id=iVs8XshC~;gr=0ee3f37f-6df2-0071-bb9a-a4e24be30135")
|
||||
|
||||
telecomManager.meetingWaitingRoomDisplayed = true
|
||||
telecomManager.meetingWaitingRoomSelected = reutest
|
||||
} catch {}
|
||||
} else {
|
||||
telecomManager.doCallWithCore(
|
||||
|
|
@ -230,10 +237,17 @@ struct HistoryListFragment: View {
|
|||
if historyListViewModel.callLogs[index].fromAddress!.asStringUriOnly().hasPrefix("sip:conference-focus@sip.linphone.org") {
|
||||
do {
|
||||
//let reudumatin = try Factory.Instance.createAddress(addr: "sip:conference-focus@sip.linphone.org;conf-id=8~YNkpFOv;gr=0ee3f37f-6df2-0071-bb9a-a4e24be30135")
|
||||
/*
|
||||
let reutest = try Factory.Instance.createAddress(addr: "sip:conference-focus@sip.linphone.org;conf-id=iVs8XshC~;gr=0ee3f37f-6df2-0071-bb9a-a4e24be30135")
|
||||
telecomManager.doCallWithCore(
|
||||
addr: reutest, isVideo: false, isConference: true
|
||||
)
|
||||
*/
|
||||
|
||||
let reutest = try Factory.Instance.createAddress(addr: "sip:conference-focus@sip.linphone.org;conf-id=iVs8XshC~;gr=0ee3f37f-6df2-0071-bb9a-a4e24be30135")
|
||||
|
||||
telecomManager.meetingWaitingRoomDisplayed = true
|
||||
telecomManager.meetingWaitingRoomSelected = reutest
|
||||
} catch {}
|
||||
} else {
|
||||
telecomManager.doCallWithCore(
|
||||
|
|
|
|||
|
|
@ -23,15 +23,14 @@ struct ActivityIndicator: View {
|
|||
|
||||
let style = StrokeStyle(lineWidth: 3, lineCap: .round)
|
||||
@State var animate = false
|
||||
let color1 = Color.white
|
||||
let color2 = Color.white.opacity(0.5)
|
||||
let color: Color
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Circle()
|
||||
.trim(from: 0, to: 0.7)
|
||||
.stroke(
|
||||
AngularGradient(gradient: .init(colors: [color1, color2]), center: .center), style: style)
|
||||
AngularGradient(gradient: .init(colors: [color, color.opacity(0.5)]), center: .center), style: style)
|
||||
.rotationEffect(Angle(degrees: animate ? 360: 0))
|
||||
.animation(Animation.linear(duration: 0.7).repeatForever(autoreverses: false), value: UUID())
|
||||
}.onAppear {
|
||||
|
|
@ -41,5 +40,5 @@ struct ActivityIndicator: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
ActivityIndicator()
|
||||
ActivityIndicator(color: .white)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue