diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index 64f20df16..ee1e35284 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -29,6 +29,8 @@ D72343362AD037AF009AA24E /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72343352AD037AF009AA24E /* ToastView.swift */; }; D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72992382ADD7F68003AF125 /* HistoryContactFragment.swift */; }; D732A9092AFD235500DB42BA /* ShareSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A9082AFD235500DB42BA /* ShareSheetController.swift */; }; + D732A90C2B0376F500DB42BA /* linphonerc-default in Resources */ = {isa = PBXBuildFile; fileRef = D732A90A2B0376F500DB42BA /* linphonerc-default */; }; + D732A90D2B0376F500DB42BA /* linphonerc-factory in Resources */ = {isa = PBXBuildFile; fileRef = D732A90B2B0376F500DB42BA /* linphonerc-factory */; }; 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 */; }; @@ -45,6 +47,7 @@ D7A03FC02ACC2E390081A588 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A03FBF2ACC2E390081A588 /* HistoryView.swift */; }; D7A03FC62ACC458A0081A588 /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A03FC52ACC458A0081A588 /* SplashScreen.swift */; }; D7A2EDD62AC18115005D90FC /* SharedMainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A2EDD52AC18115005D90FC /* SharedMainViewModel.swift */; }; + D7ADF6002AFE356400212231 /* Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADF5FF2AFE356400212231 /* Avatar.swift */; }; D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */; }; D7C365082AEFAB7F00FE6142 /* ContactListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C365072AEFAB7F00FE6142 /* ContactListBottomSheet.swift */; }; D7C3650A2AF001C300FE6142 /* EditContactFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C365092AF001C300FE6142 /* EditContactFragment.swift */; }; @@ -95,6 +98,8 @@ D72343352AD037AF009AA24E /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; D72992382ADD7F68003AF125 /* HistoryContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryContactFragment.swift; sourceTree = ""; }; D732A9082AFD235500DB42BA /* ShareSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheetController.swift; sourceTree = ""; }; + D732A90A2B0376F500DB42BA /* linphonerc-default */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "linphonerc-default"; sourceTree = ""; }; + D732A90B2B0376F500DB42BA /* linphonerc-factory */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "linphonerc-factory"; sourceTree = ""; }; D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountLoginFragment.swift; sourceTree = ""; }; D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountWarningFragment.swift; sourceTree = ""; }; D74C9CF72ACACECE0021626A /* WelcomePage1Fragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomePage1Fragment.swift; sourceTree = ""; }; @@ -112,6 +117,7 @@ D7A03FC52ACC458A0081A588 /* SplashScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashScreen.swift; sourceTree = ""; }; D7A2EDD52AC18115005D90FC /* SharedMainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedMainViewModel.swift; sourceTree = ""; }; D7A2EDDA2AC19EEC005D90FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + D7ADF5FF2AFE356400212231 /* Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Avatar.swift; sourceTree = ""; }; D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInnerFragment.swift; sourceTree = ""; }; D7C365072AEFAB7F00FE6142 /* ContactListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactListBottomSheet.swift; sourceTree = ""; }; D7C365092AF001C300FE6142 /* EditContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditContactFragment.swift; sourceTree = ""; }; @@ -165,6 +171,7 @@ D7C3650D2AF15BF200FE6142 /* PhotoPicker.swift */, D7C48DF32AFA66F900D938CB /* EditContactController.swift */, D732A9082AFD235500DB42BA /* ShareSheetController.swift */, + D7ADF5FF2AFE356400212231 /* Avatar.swift */, ); path = Utils; sourceTree = ""; @@ -201,6 +208,7 @@ D70C93DD2AC2D0F60063CA3B /* Localizable.xcstrings */, D719ABBD2ABC67BF00B41C10 /* Preview Content */, D7D24D0C2AC1B4C700C6F35B /* Fonts */, + D7ADF6012AFE5C7C00212231 /* Ressources */, ); path = Linphone; sourceTree = ""; @@ -375,6 +383,15 @@ path = Viewmodel; sourceTree = ""; }; + D7ADF6012AFE5C7C00212231 /* Ressources */ = { + isa = PBXGroup; + children = ( + D732A90A2B0376F500DB42BA /* linphonerc-default */, + D732A90B2B0376F500DB42BA /* linphonerc-factory */, + ); + path = Ressources; + sourceTree = ""; + }; D7D24D0C2AC1B4C700C6F35B /* Fonts */ = { isa = PBXGroup; children = ( @@ -469,6 +486,8 @@ D719ABBF2ABC67BF00B41C10 /* Preview Assets.xcassets in Resources */, D719ABBB2ABC67BF00B41C10 /* Assets.xcassets in Resources */, D7D24D132AC1B4E800C6F35B /* NotoSans-Medium.ttf in Resources */, + D732A90C2B0376F500DB42BA /* linphonerc-default in Resources */, + D732A90D2B0376F500DB42BA /* linphonerc-factory in Resources */, D70C93DE2AC2D0F60063CA3B /* Localizable.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -503,6 +522,7 @@ buildActionMask = 2147483647; files = ( D7C3650E2AF15BF200FE6142 /* PhotoPicker.swift in Sources */, + D7ADF6002AFE356400212231 /* Avatar.swift in Sources */, D71707202AC5989C0037746F /* TextExtension.swift in Sources */, D719ABB92ABC67BF00B41C10 /* ContentView.swift in Sources */, D71FCA832AE14D6E00D2E43E /* ContactFragment.swift in Sources */, diff --git a/Linphone/Assets.xcassets/presence-busy.imageset/Contents.json b/Linphone/Assets.xcassets/presence-busy.imageset/Contents.json new file mode 100644 index 000000000..227036d8d --- /dev/null +++ b/Linphone/Assets.xcassets/presence-busy.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "presence-busy.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/presence-busy.imageset/presence-busy.svg b/Linphone/Assets.xcassets/presence-busy.imageset/presence-busy.svg new file mode 100644 index 000000000..0f24966ca --- /dev/null +++ b/Linphone/Assets.xcassets/presence-busy.imageset/presence-busy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Linphone/Assets.xcassets/presence-online.imageset/Contents.json b/Linphone/Assets.xcassets/presence-online.imageset/Contents.json new file mode 100644 index 000000000..606200f2a --- /dev/null +++ b/Linphone/Assets.xcassets/presence-online.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "presence-online.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/presence-online.imageset/presence-online.svg b/Linphone/Assets.xcassets/presence-online.imageset/presence-online.svg new file mode 100644 index 000000000..ae3b6ed5e --- /dev/null +++ b/Linphone/Assets.xcassets/presence-online.imageset/presence-online.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift index 350fb6a0d..a18811e17 100644 --- a/Linphone/Core/CoreContext.swift +++ b/Linphone/Core/CoreContext.swift @@ -30,6 +30,7 @@ final class CoreContext: ObservableObject { @Published var loggingInProgress: Bool = false @Published var toastMessage: String = "" @Published var defaultAccount: Account? + @Published var coreIsStarted: Bool = false private var mCore: Core! private var mIteratePublisher: AnyCancellable? @@ -53,17 +54,39 @@ final class CoreContext: ObservableObject { coreQueue.async { let configDir = Factory.Instance.getConfigDir(context: nil) - try? self.mCore = Factory.Instance.createCore(configPath: "\(configDir)/MyConfig", factoryConfigPath: "", systemContext: nil) + + let url = NSURL(fileURLWithPath: configDir) + if let pathComponent = url.appendingPathComponent("linphonerc") { + let filePath = pathComponent.path + let fileManager = FileManager.default + if !fileManager.fileExists(atPath: filePath) { + let path = Bundle.main.path(forResource: "linphonerc-default", ofType: nil) + if path != nil { + try? FileManager.default.copyItem(at: NSURL(fileURLWithPath: path!) as URL, to: pathComponent) + } + } + } + + let config: Config! = Config.newForSharedCore( + appGroupId: "group.org.linphone.phone.msgNotification", + configFilename: "linphonerc", + factoryConfigFilename: Bundle.main.path(forResource: "linphonerc-factory", ofType: nil) + ) + + self.mCore = try? Factory.Instance.createCoreWithConfig(config: config, systemContext: nil) + self.mCore.autoIterateEnabled = false self.mCore.friendsDatabasePath = "\(configDir)/friends.db" self.mCore.publisher?.onGlobalStateChanged?.postOnMainQueue { (cbVal: (core: Core, state: GlobalState, message: String)) in if cbVal.state == GlobalState.On { self.defaultAccount = self.mCore.defaultAccount + self.coreIsStarted = true } else if cbVal.state == GlobalState.Off { self.defaultAccount = nil } } + try? self.mCore.start() // Create a Core listener to listen for the callback we need diff --git a/Linphone/Linphone.entitlements b/Linphone/Linphone.entitlements index f2ef3ae02..500e2cecd 100644 --- a/Linphone/Linphone.entitlements +++ b/Linphone/Linphone.entitlements @@ -2,9 +2,15 @@ - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - + com.apple.security.app-sandbox + + com.apple.security.application-groups + + group.belledonne-communications.linphone + group.org.linphone.phone.msgNotification + group.org.linphone.phone.linphoneExtension + + com.apple.security.files.user-selected.read-only + diff --git a/Linphone/LinphoneApp.swift b/Linphone/LinphoneApp.swift index 85f14b81c..9308e26fe 100644 --- a/Linphone/LinphoneApp.swift +++ b/Linphone/LinphoneApp.swift @@ -29,7 +29,7 @@ struct LinphoneApp: App { var body: some Scene { WindowGroup { - if isActive { + if isActive && coreContext.coreIsStarted { if !sharedMainViewModel.welcomeViewDisplayed { WelcomeView(sharedMainViewModel: sharedMainViewModel) } else if coreContext.defaultAccount == nil || sharedMainViewModel.displayProfileMode { diff --git a/Linphone/Ressources/linphonerc-default b/Linphone/Ressources/linphonerc-default new file mode 100644 index 000000000..ea5356429 --- /dev/null +++ b/Linphone/Ressources/linphonerc-default @@ -0,0 +1,39 @@ + +## Start of default rc + +[sip] +contact="Linphone iPhone" +use_info=0 +use_ipv6=1 +keepalive_period=30000 +sip_port=-1 +sip_tcp_port=-1 +sip_tls_port=-1 +media_encryption=none +update_presence_model_timestamp_before_publish_expires_refresh=1 + +[net] +#Because dynamic bitrate adaption can increase bitrate, we must allow "no limit" +download_bw=0 +upload_bw=0 + +[video] +size=vga + +[app] +tunnel=disabled +auto_start=1 +record_aware=1 + +[tunnel] +host= +port=443 + +[misc] +log_collection_upload_server_url=https://www.linphone.org:444/lft.php +file_transfer_server_url=https://www.linphone.org:444/lft.php +version_check_url_root=https://www.linphone.org/releases +max_calls=10 +conference_layout=1 + +## End of default rc diff --git a/Linphone/Ressources/linphonerc-factory b/Linphone/Ressources/linphonerc-factory new file mode 100644 index 000000000..85b543074 --- /dev/null +++ b/Linphone/Ressources/linphonerc-factory @@ -0,0 +1,65 @@ + +## Start of factory rc + +# This file shall not contain path referencing package name, in order to be portable when app is renamed. +# Paths to resources must be set from LinphoneManager, after creating LinphoneCore. + +[net] +mtu=1300 +force_ice_disablement=0 + +[rtp] +accept_any_encryption=1 + +[sip] +guess_hostname=1 +register_only_when_network_is_up=1 +auto_net_state_mon=1 +auto_answer_replacing_calls=1 +ping_with_options=0 +use_cpim=1 +zrtp_key_agreements_suites=MS_ZRTP_KEY_AGREEMENT_K255_KYB512 +chat_messages_aggregation_delay=1000 +chat_messages_aggregation=1 +update_presence_model_timestamp_before_publish_expires_refresh=1 +rls_uri=sips:rls@sip.linphone.org + +[sound] +#remove this property for any application that is not Linphone public version itself +ec_calibrator_cool_tones=1 + +[video] +displaytype=MSAndroidTextureDisplay +auto_resize_preview_to_keep_ratio=1 +max_conference_size=vga + +[misc] +enable_basic_to_client_group_chat_room_migration=0 +enable_simple_group_chat_message_state=0 +aggregate_imdn=1 +notify_each_friend_individually_when_presence_received=0 +store_friends=0 + +[app] +activation_code_length=4 +prefer_basic_chat_room=1 +record_aware=1 + +[account_creator] +backend=1 +# 1 means FlexiAPI, 0 is XMLRPC +url=https://subscribe.linphone.org/api/ +# replace above URL by https://staging-subscribe.linphone.org/api/ for testing + +[lime] +lime_update_threshold=86400 + +[alerts] +alerts_enabled=1 + +[assistant] +algorithm=SHA-256 +password_min_length=6 +username_regex=^[a-z0-9+_.\-]*$ + +## End of factory rc diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift index ac6a8e5cd..dfefb33b6 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift @@ -113,28 +113,9 @@ struct ContactInnerFragment: View { && magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil && magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.photo != nil && !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.photo!.isEmpty { - AsyncImage( - url: ContactsManager.shared.getImagePath( - friendPhotoPath: magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.photo!)) { image in - switch image { - case .empty: - ProgressView() - .frame(width: 100, height: 100) - case .success(let image): - image - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 100, height: 100) - .clipShape(Circle()) - case .failure: - Image("profil-picture-default") - .resizable() - .frame(width: 100, height: 100) - .clipShape(Circle()) - @unknown default: - EmptyView() - } - } + + Avatar(friend: magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!, avatarSize: 100) + } else if contactViewModel.indexDisplayedFriend != nil && magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil { Image("profil-picture-default") .resizable() diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift index 7b274e646..67562f06f 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift @@ -64,26 +64,9 @@ struct ContactsListFragment: View { } if magicSearch.lastSearch[index].friend!.photo != nil && !magicSearch.lastSearch[index].friend!.photo!.isEmpty { - AsyncImage(url: ContactsManager.shared.getImagePath(friendPhotoPath: magicSearch.lastSearch[index].friend!.photo!)) { image in - switch image { - case .empty: - ProgressView() - .frame(width: 45, height: 45) - case .success(let image): - image - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 45, height: 45) - .clipShape(Circle()) - case .failure: - Image("profil-picture-default") - .resizable() - .frame(width: 45, height: 45) - .clipShape(Circle()) - @unknown default: - EmptyView() - } - } + + Avatar(friend: magicSearch.lastSearch[index].friend!, avatarSize: 45) + } else { Image("profil-picture-default") .resizable() diff --git a/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift b/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift index e7de05556..9e1b2b09c 100644 --- a/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift @@ -129,26 +129,9 @@ struct EditContactFragment: View { if editContactViewModel.selectedEditFriend != nil && editContactViewModel.selectedEditFriend!.photo != nil && !editContactViewModel.selectedEditFriend!.photo!.isEmpty && selectedImage == nil && !removedImage { - AsyncImage(url: ContactsManager.shared.getImagePath(friendPhotoPath: editContactViewModel.selectedEditFriend!.photo!)) { image in - switch image { - case .empty: - ProgressView() - .frame(width: 100, height: 100) - case .success(let image): - image - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 100, height: 100) - .clipShape(Circle()) - case .failure: - Image("profil-picture-default") - .resizable() - .frame(width: 100, height: 100) - .clipShape(Circle()) - @unknown default: - EmptyView() - } - } + + Avatar(friend: editContactViewModel.selectedEditFriend!, avatarSize: 100) + } else if selectedImage == nil { Image("profil-picture-default") .resizable() diff --git a/Linphone/UI/Main/Contacts/Fragments/FavoriteContactsListFragment.swift b/Linphone/UI/Main/Contacts/Fragments/FavoriteContactsListFragment.swift index 0eb2fa566..1942ece86 100644 --- a/Linphone/UI/Main/Contacts/Fragments/FavoriteContactsListFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/FavoriteContactsListFragment.swift @@ -39,27 +39,9 @@ struct FavoriteContactsListFragment: View { VStack { if magicSearch.lastSearch[index].friend!.photo != nil && !magicSearch.lastSearch[index].friend!.photo!.isEmpty { - AsyncImage(url: ContactsManager.shared.getImagePath(friendPhotoPath: magicSearch.lastSearch[index].friend!.photo!) - ) { image in - switch image { - case .empty: - ProgressView() - .frame(width: 45, height: 45) - case .success(let image): - image - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 45, height: 45) - .clipShape(Circle()) - case .failure: - Image("profil-picture-default") - .resizable() - .frame(width: 45, height: 45) - .clipShape(Circle()) - @unknown default: - EmptyView() - } - } + + Avatar(friend: magicSearch.lastSearch[index].friend!, avatarSize: 45) + } else { Image("profil-picture-default") .resizable() diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index d9a8307c1..f3dfce4c8 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -136,6 +136,7 @@ struct ContentView: View { Menu { Button { isMenuOpen = false + contactViewModel.indexDisplayedFriend = nil magicSearch.allContact = true magicSearch.searchForContacts( sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) @@ -153,6 +154,7 @@ struct ContentView: View { Button { isMenuOpen = false + contactViewModel.indexDisplayedFriend = nil magicSearch.allContact = false magicSearch.searchForContacts( sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) diff --git a/Linphone/Utils/Avatar.swift b/Linphone/Utils/Avatar.swift new file mode 100644 index 000000000..72847b760 --- /dev/null +++ b/Linphone/Utils/Avatar.swift @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2010-2023 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 . + */ + +import SwiftUI +import linphonesw + +struct Avatar: View { + var friend: Friend + let avatarSize: CGFloat + + @State private var friendDelegate: FriendDelegate? + @State private var presenceImage = "" + + var body: some View { + AsyncImage(url: ContactsManager.shared.getImagePath(friendPhotoPath: friend.photo!)) { image in + switch image { + case .empty: + ProgressView() + .frame(width: avatarSize, height: avatarSize) + case .success(let image): + ZStack { + image + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: avatarSize, height: avatarSize) + .clipShape(Circle()) + HStack { + Spacer() + VStack { + Spacer() + if !friend.addresses.isEmpty { + if presenceImage.isEmpty && (friend.consolidatedPresence == ConsolidatedPresence.Online || friend.consolidatedPresence == ConsolidatedPresence.Busy) { + Image(friend.consolidatedPresence == ConsolidatedPresence.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) + } else if !presenceImage.isEmpty && (friend.consolidatedPresence != ConsolidatedPresence.DoNotDisturb || friend.consolidatedPresence != ConsolidatedPresence.Offline) { + Image(presenceImage) + .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) + } + case .failure: + Image("profil-picture-default") + .resizable() + .frame(width: avatarSize, height: avatarSize) + .clipShape(Circle()) + @unknown default: + EmptyView() + } + } + .onAppear { + addDelegate() + } + .onDisappear { + removeAllDelegate() + } + } + + func addDelegate() { + if friend.address != nil { + print("Presence received for first \(friend.name!) \(friend.consolidatedPresence)") + //self.presenceImage = friend.consolidatedPresence == ConsolidatedPresence.Online ? "presence-online" : "presence-busy" + } + + let newFriendDelegate = FriendDelegateStub( + onPresenceReceived: { (linphoneFriend: Friend) -> Void in + print("Presence received for second \(linphoneFriend.name) \(linphoneFriend.consolidatedPresence)") + + /* + if linphoneFriend.address != nil && friend.address != nil + && linphoneFriend.address!.asStringUriOnly() == friend.address!.asStringUriOnly() { + let presenceModel = linphoneFriend.getPresenceModelForUriOrTel(uriOrTel: (linphoneFriend.address!.asStringUriOnly())) + if presenceModel != nil { + presenceImage = presenceModel!.consolidatedPresence == ConsolidatedPresence.Online ? "presence-online" : "presence-busy" + } + } + */ + } + ) + + friendDelegate = newFriendDelegate + if friendDelegate != nil { + friend.addDelegate(delegate: friendDelegate!) + } + } + + func removeAllDelegate(){ + if friendDelegate != nil { + presenceImage = "" + friend.removeDelegate(delegate: friendDelegate!) + friendDelegate = nil + } + } +}