diff --git a/Linphone/Contacts/ContactsManager.swift b/Linphone/Contacts/ContactsManager.swift index 0b00df121..9cb3b0768 100644 --- a/Linphone/Contacts/ContactsManager.swift +++ b/Linphone/Contacts/ContactsManager.swift @@ -47,7 +47,6 @@ final class ContactsManager: ObservableObject { if core.globalState == GlobalState.Shutdown || core.globalState == GlobalState.Off { print("\(#function) - Core is being stopped or already destroyed, abort") } else { - do { self.friendList = try core.getFriendListByName(name: self.nativeAddressBookFriendList) ?? core.createFriendList() } catch let error { @@ -81,6 +80,7 @@ final class ContactsManager: ObservableObject { linphoneFriendList.displayName = self.linphoneAddressBookFriendList core.addFriendList(list: linphoneFriendList) } + linphoneFriendList.subscriptionsEnabled = true } } diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift index 10e723c1e..404098fed 100644 --- a/Linphone/Core/CoreContext.swift +++ b/Linphone/Core/CoreContext.swift @@ -86,12 +86,9 @@ final class CoreContext: ObservableObject { } self.mCore.autoIterateEnabled = false - self.mCore.friendsDatabasePath = "\(configDir)/friends.db" self.mCore.callkitEnabled = true self.mCore.pushNotificationEnabled = true - self.mCore.friendListSubscriptionEnabled = true - self.mCore.publisher?.onGlobalStateChanged?.postOnMainQueue { (cbVal: (core: Core, state: GlobalState, message: String)) in if cbVal.state == GlobalState.On { self.defaultAccount = self.mCore.defaultAccount diff --git a/Linphone/TelecomManager/TelecomManager.swift b/Linphone/TelecomManager/TelecomManager.swift index 76c6d0c2b..ce319cde3 100644 --- a/Linphone/TelecomManager/TelecomManager.swift +++ b/Linphone/TelecomManager/TelecomManager.swift @@ -83,13 +83,13 @@ class TelecomManager: ObservableObject { } } - func startCall(core: Core, addr: Address?, isSas: Bool, isVideo: Bool, isConference: Bool = false) throws { + func startCallCallKit(core: Core, addr: Address?, isSas: Bool, isVideo: Bool, isConference: Bool = false) throws { if addr == nil { Log.info("Can not start a call with null address!") return } - if TelecomManager.callKitEnabled(core: core) && !nextCallIsTransfer != true { + if TelecomManager.callKitEnabled(core: core) {//&& !nextCallIsTransfer != true { let uuid = UUID() let name = "outgoingTODO" // FastAddressBook.displayName(for: addr) ?? "unknow" let handle = CXHandle(type: .generic, value: addr?.asStringUriOnly() ?? "") @@ -110,7 +110,7 @@ class TelecomManager: ObservableObject { func startCall(core: Core, addr: String, isSas: Bool = false, isVideo: Bool, isConference: Bool = false) { do { let address = try Factory.Instance.createAddress(addr: addr) - try startCall(core: core, addr: address, isSas: isSas, isVideo: isVideo, isConference: isConference) + try startCallCallKit(core: core, addr: address, isSas: isSas, isVideo: isVideo, isConference: isConference) } catch { Log.error("[TelecomManager] unable to create address for a new outgoing call : \(addr) \(error) ") } @@ -118,11 +118,11 @@ class TelecomManager: ObservableObject { func doCallWithCore(addr: Address) { CoreContext.shared.doOnCoreQueue { core in - do { - try self.startCall(core: core, addr: addr, isSas: false, isVideo: false) - } catch { - Log.error("[TelecomManager] unable to create address for a new outgoing call : \(addr.asStringUriOnly()) \(error) ") - } + do { + try self.startCallCallKit(core: core, addr: addr, isSas: false, isVideo: false, isConference: false) + } catch { + Log.error("[TelecomManager] unable to create address for a new outgoing call : \(addr) \(error) ") + } } } @@ -329,6 +329,14 @@ class TelecomManager: ObservableObject { let addr = call.remoteAddress let displayName = incomingDisplayName(call: call) + #if targetEnvironment(simulator) + DispatchQueue.main.async { + withAnimation { + TelecomManager.shared.callInProgress = true + } + } + #endif + if call.replacedCall != nil { endCallKitReplacedCall = false diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index bc3baf8fa..5f3bc98bb 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -18,6 +18,7 @@ */ import SwiftUI +import CallKit struct CallView: View { @@ -29,10 +30,213 @@ struct CallView: View { @State var startDate = Date.now @State var timeElapsed: Int = 0 + @State var micMutted: Bool = false let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() var body: some View { + if #available(iOS 16.4, *) { + innerView() + .background(.black) + .sheet(isPresented: .constant(true)) { + VStack { + HStack(spacing: 12) { + Button { + terminateCall() + } label: { + Image("phone-disconnect") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 90, height: 60) + .background(Color.redDanger500) + .cornerRadius(40) + + Spacer() + + Button { + } label: { + Image("video-camera") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background(Color.gray500) + .cornerRadius(40) + + Button { + muteCall() + } label: { + Image(micMutted ? "microphone-slash" : "microphone") + .renderingMode(.template) + .resizable() + .foregroundStyle(micMutted ? .black : .white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background(micMutted ? .white : Color.gray500) + .cornerRadius(40) + + Button { + } label: { + Image("speaker-high") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background(Color.gray500) + .cornerRadius(40) + } + .padding(.horizontal, 25) + .padding(.top, 20) + + HStack(spacing: 12) { + Button { + } label: { + Image("video-camera") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background(Color.gray500) + .cornerRadius(40) + + Spacer() + + Button { + muteCall() + } label: { + Image(micMutted ? "microphone-slash" : "microphone") + .renderingMode(.template) + .resizable() + .foregroundStyle(micMutted ? .black : .white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background(micMutted ? .white : Color.gray500) + .cornerRadius(40) + + Spacer() + + Button { + } label: { + Image("speaker-high") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background(Color.gray500) + .cornerRadius(40) + + Spacer() + + Button { + } label: { + Image("speaker-high") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background(Color.gray500) + .cornerRadius(40) + } + .padding(.horizontal, 25) + .padding(.top, 20) + + HStack(spacing: 12) { + Button { + } label: { + Image("video-camera") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background(Color.gray500) + .cornerRadius(40) + + Spacer() + + Button { + muteCall() + } label: { + Image(micMutted ? "microphone-slash" : "microphone") + .renderingMode(.template) + .resizable() + .foregroundStyle(micMutted ? .black : .white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background(micMutted ? .white : Color.gray500) + .cornerRadius(40) + + Spacer() + + Button { + } label: { + Image("speaker-high") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background(Color.gray500) + .cornerRadius(40) + + Spacer() + + Button { + } label: { + Image("speaker-high") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background(Color.gray500) + .cornerRadius(40) + } + .padding(.horizontal, 25) + .padding(.top, 20) + } + .frame(maxHeight: .infinity, alignment: .top) + .background(.black) + .presentationDetents([.fraction(0.1), .medium]) + .interactiveDismissDisabled() + .presentationBackgroundInteraction(.enabled) + } + } + } + + @ViewBuilder + func innerView() -> some View { VStack { Rectangle() .foregroundColor(Color.orangeMain500) @@ -40,222 +244,200 @@ struct CallView: View { .frame(height: 0) HStack { - if callViewModel.direction == .Outgoing { - Image("outgoing-call") - .resizable() - .frame(width: 15, height: 15) - .padding(.horizontal) - - Text("Outgoing call") - .foregroundStyle(.white) - } else { - Image("incoming-call") - .resizable() - .frame(width: 15, height: 15) - .padding(.horizontal) - - Text("Incoming call") - .foregroundStyle(.white) - } - - Spacer() + if callViewModel.direction == .Outgoing { + Image("outgoing-call") + .resizable() + .frame(width: 15, height: 15) + .padding(.horizontal) + + Text("Outgoing call") + .foregroundStyle(.white) + } else { + Image("incoming-call") + .resizable() + .frame(width: 15, height: 15) + .padding(.horizontal) + + Text("Incoming call") + .foregroundStyle(.white) + } + + Spacer() } .frame(height: 40) - - ZStack { - VStack { - Spacer() - - 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, withPresence: false) - - if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty { - if contactAvatarModel != nil { - Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 100, 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: 100, height: 100) - .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: 100, height: 100) - .clipShape(Circle()) - } - - } - } else { - Image("profil-picture-default") - .resizable() - .frame(width: 100, height: 100) - .clipShape(Circle()) - } - - Text(callViewModel.displayName) - .padding(.top) - .foregroundStyle(.white) - - Text(callViewModel.remoteAddressString) - .foregroundStyle(.white) - - Spacer() - } - - if !telecomManager.callStarted { - VStack { - ActivityIndicator() - .frame(width: 20, height: 20) - .padding(.top, 100) - - Text(counterToMinutes()) - .onReceive(timer) { firedDate in - timeElapsed = Int(firedDate.timeIntervalSince(startDate)) - - } - .padding(.top) - .foregroundStyle(.white) - - Spacer() - } - .background(.clear) - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.gray600) - .cornerRadius(20) - .padding(.horizontal, 4) - if telecomManager.callStarted { - HStack(spacing: 12) { - Button { - terminateCall() - } label: { - Image("phone-disconnect") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - - } - .frame(width: 90, height: 60) - .background(Color.redDanger500) - .cornerRadius(40) - - Spacer() - - Button { - } label: { - Image("video-camera") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - - Button { - } label: { - Image("microphone") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - - Button { - } label: { - Image("speaker-high") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - } - .padding(.horizontal, 25) - .padding(.top, 20) - } else { - HStack(spacing: 12) { - - Spacer() - - Button { - terminateCall() - } label: { - Image("phone-disconnect") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - - } - .frame(width: 90, height: 60) - .background(Color.redDanger500) - .cornerRadius(40) + ZStack { + VStack { + Spacer() + + 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, withPresence: false) + + if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty { + if contactAvatarModel != nil { + Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 100, 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: 100, height: 100) + .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: 100, height: 100) + .clipShape(Circle()) + } + + } + } else { + Image("profil-picture-default") + .resizable() + .frame(width: 100, height: 100) + .clipShape(Circle()) + } + + Text(callViewModel.displayName) + .padding(.top) + .foregroundStyle(.white) + + Text(callViewModel.remoteAddressString) + .foregroundStyle(.white) + + Spacer() + } + + if !telecomManager.callStarted { + VStack { + ActivityIndicator() + .frame(width: 20, height: 20) + .padding(.top, 100) + + Text(counterToMinutes()) + .onReceive(timer) { firedDate in + timeElapsed = Int(firedDate.timeIntervalSince(startDate)) - Button { - //telecomManager.callStarted.toggle() - } label: { - Image("phone") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - - } - .frame(width: 90, height: 60) - .background(Color.greenSuccess500) - .cornerRadius(40) - - Spacer() - } - .padding(.horizontal, 25) - .padding(.top, 20) - } + } + .padding(.top) + .foregroundStyle(.white) + + Spacer() + } + .background(.clear) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.gray600) + .cornerRadius(20) + .padding(.horizontal, 4) + + if telecomManager.callStarted { + HStack(spacing: 12) { + HStack { + } + .frame(width: 60, height: 60) + } + .padding(.horizontal, 25) + .padding(.top, 20) + } else { + HStack(spacing: 12) { + + Spacer() + + Button { + terminateCall() + } label: { + Image("phone-disconnect") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 90, height: 60) + .background(Color.redDanger500) + .cornerRadius(40) + + Button { + acceptCall() + } label: { + Image("phone") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 90, height: 60) + .background(Color.greenSuccess500) + .cornerRadius(40) + + Spacer() + } + .padding(.horizontal, 25) + .padding(.top, 20) + } } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.gray900) - } + } func terminateCall() { + withAnimation { + telecomManager.callInProgress = false + telecomManager.callStarted = false + } + coreContext.doOnCoreQueue { core in - do { - // Terminates the call, whether it is ringing or running - try core.currentCall?.terminate() - } catch { NSLog(error.localizedDescription) } + if core.currentCall != nil { + telecomManager.terminateCall(call: core.currentCall!) + } } + timer.upstream.connect().cancel() } + + func acceptCall() { + withAnimation { + telecomManager.callInProgress = true + telecomManager.callStarted = true + } + + coreContext.doOnCoreQueue { core in + if core.currentCall != nil { + telecomManager.acceptCall(core: core, call: core.currentCall!, hasVideo: false) + } + } + + timer.upstream.connect().cancel() + } + + func muteCall() { + coreContext.doOnCoreQueue { core in + if core.currentCall != nil { + micMutted = !micMutted + core.currentCall!.microphoneMuted = micMutted + } + } + } func counterToMinutes() -> String { let currentTime = timeElapsed diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index 905ef9799..cdd0ca2a4 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -446,19 +446,21 @@ struct ContentView: View { }) : ContactAvatarModel(friend: nil, withPresence: false) - HistoryContactFragment( - contactAvatarModel: contactAvatarModel!, - historyViewModel: historyViewModel, - historyListViewModel: historyListViewModel, - contactViewModel: contactViewModel, - editContactViewModel: editContactViewModel, - isShowDeleteAllHistoryPopup: $isShowDeleteAllHistoryPopup, - isShowEditContactFragment: $isShowEditContactFragment, - indexPage: $index - ) - .frame(maxWidth: .infinity) - .background(Color.gray100) - .ignoresSafeArea(.keyboard) + if contactAvatarModel != nil { + HistoryContactFragment( + contactAvatarModel: contactAvatarModel!, + historyViewModel: historyViewModel, + historyListViewModel: historyListViewModel, + contactViewModel: contactViewModel, + editContactViewModel: editContactViewModel, + isShowDeleteAllHistoryPopup: $isShowDeleteAllHistoryPopup, + isShowEditContactFragment: $isShowEditContactFragment, + indexPage: $index + ) + .frame(maxWidth: .infinity) + .background(Color.gray100) + .ignoresSafeArea(.keyboard) + } } } .onAppear { @@ -656,6 +658,9 @@ struct ContentView: View { coreContext.onForeground() if !isShowStartCallFragment { contactsManager.fetchContacts() + DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { + historyListViewModel.computeCallLogsList() + } } print("Active") } else if newPhase == .inactive { diff --git a/Linphone/UI/Main/History/Fragments/DialerBottomSheet.swift b/Linphone/UI/Main/History/Fragments/DialerBottomSheet.swift index 92f932c73..dc3915be6 100644 --- a/Linphone/UI/Main/History/Fragments/DialerBottomSheet.swift +++ b/Linphone/UI/Main/History/Fragments/DialerBottomSheet.swift @@ -231,12 +231,18 @@ struct DialerBottomSheet: View { .clipShape(Circle()) } } - .onTapGesture { - startCallViewModel.searchField += "0" - } - .onLongPressGesture(minimumDuration: 0.2) { - startCallViewModel.searchField += "+" - } + .simultaneousGesture( + LongPressGesture() + .onEnded { _ in + startCallViewModel.searchField += "+" + } + ) + .highPriorityGesture( + TapGesture() + .onEnded { _ in + startCallViewModel.searchField += "0" + } + ) Spacer() diff --git a/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift index ccdfd4820..ceafcb884 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift @@ -51,6 +51,11 @@ struct HistoryListFragment: View { if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty { if contactAvatarModel != nil { Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 45) + } else { + Image("profil-picture-default") + .resizable() + .frame(width: 45, height: 45) + .clipShape(Circle()) } } else { if historyListViewModel.callLogs[index].dir == .Outgoing && historyListViewModel.callLogs[index].toAddress != nil { @@ -95,7 +100,12 @@ struct HistoryListFragment: View { .frame(width: 45, height: 45) .clipShape(Circle()) } - } + } else { + Image("profil-picture-default") + .resizable() + .frame(width: 45, height: 45) + .clipShape(Circle()) + } } VStack(spacing: 0) { diff --git a/Linphone/Utils/Avatar.swift b/Linphone/Utils/Avatar.swift index 780eccc9d..8d44fa061 100644 --- a/Linphone/Utils/Avatar.swift +++ b/Linphone/Utils/Avatar.swift @@ -23,6 +23,7 @@ import linphonesw struct Avatar: View { @ObservedObject var contactAvatarModel: ContactAvatarModel + let avatarSize: CGFloat let hidePresence: Bool @@ -33,41 +34,48 @@ struct Avatar: View { } var body: some View { - AsyncImage(url: ContactsManager.shared.getImagePath(friendPhotoPath: contactAvatarModel.friend!.photo!)) { image in - switch image { - case .empty: - ProgressView() - .frame(width: avatarSize, height: avatarSize) - case .success(let image): - ZStack { - image - .resizable() - .aspectRatio(contentMode: .fill) + if contactAvatarModel.friend != nil { + AsyncImage(url: ContactsManager.shared.getImagePath(friendPhotoPath: contactAvatarModel.friend!.photo!)) { image in + switch image { + case .empty: + ProgressView() .frame(width: avatarSize, height: avatarSize) - .clipShape(Circle()) - HStack { - Spacer() - VStack { + case .success(let image): + ZStack { + image + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: avatarSize, height: avatarSize) + .clipShape(Circle()) + HStack { Spacer() - if !hidePresence && (contactAvatarModel.presenceStatus == .Online || contactAvatarModel.presenceStatus == .Busy) { - Image(contactAvatarModel.presenceStatus == .Online ? "presence-online" : "presence-busy") - .resizable() - .frame(width: avatarSize/4, height: avatarSize/4) - .padding(.trailing, avatarSize == 45 ? 1 : 3) - .padding(.bottom, avatarSize == 45 ? 1 : 3) + VStack { + Spacer() + if !hidePresence && (contactAvatarModel.presenceStatus == .Online || contactAvatarModel.presenceStatus == .Busy) { + Image(contactAvatarModel.presenceStatus == .Online ? "presence-online" : "presence-busy") + .resizable() + .frame(width: avatarSize/4, height: avatarSize/4) + .padding(.trailing, avatarSize == 45 ? 1 : 3) + .padding(.bottom, avatarSize == 45 ? 1 : 3) + } } } + .frame(width: avatarSize, height: avatarSize) } - .frame(width: avatarSize, height: avatarSize) + case .failure: + Image("profil-picture-default") + .resizable() + .frame(width: avatarSize, height: avatarSize) + .clipShape(Circle()) + @unknown default: + EmptyView() } - case .failure: - Image("profil-picture-default") - .resizable() - .frame(width: avatarSize, height: avatarSize) - .clipShape(Circle()) - @unknown default: - EmptyView() } + } else { + Image("profil-picture-default") + .resizable() + .frame(width: avatarSize, height: avatarSize) + .clipShape(Circle()) } } }