mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-17 11:08:06 +00:00
Merge branch 'feature/contact_create_and_edit' into 'master'
Contact create and edit See merge request BC/private/linphone-iphone-6.0!7
This commit is contained in:
commit
ae1ea15558
26 changed files with 2339 additions and 927 deletions
|
|
@ -28,6 +28,7 @@
|
|||
D72343342ACEFFC3009AA24E /* QRScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72343332ACEFFC3009AA24E /* QRScanner.swift */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
|
|
@ -46,6 +47,11 @@
|
|||
D7A2EDD62AC18115005D90FC /* SharedMainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A2EDD52AC18115005D90FC /* SharedMainViewModel.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 */; };
|
||||
D7C3650C2AF0084000FE6142 /* EditContactViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C3650B2AF0084000FE6142 /* EditContactViewModel.swift */; };
|
||||
D7C3650E2AF15BF200FE6142 /* PhotoPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C3650D2AF15BF200FE6142 /* PhotoPicker.swift */; };
|
||||
D7C48DF42AFA66F900D938CB /* EditContactController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C48DF32AFA66F900D938CB /* EditContactController.swift */; };
|
||||
D7C48DF62AFCDF4700D938CB /* ContactInnerActionsFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C48DF52AFCDF4700D938CB /* ContactInnerActionsFragment.swift */; };
|
||||
D7D1698C2AE66FA500109A5C /* MagicSearchSingleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D1698B2AE66FA500109A5C /* MagicSearchSingleton.swift */; };
|
||||
D7D24D132AC1B4E800C6F35B /* NotoSans-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D7D24D0D2AC1B4E800C6F35B /* NotoSans-Medium.ttf */; };
|
||||
D7D24D142AC1B4E800C6F35B /* NotoSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D7D24D0E2AC1B4E800C6F35B /* NotoSans-Regular.ttf */; };
|
||||
|
|
@ -88,6 +94,7 @@
|
|||
D72343332ACEFFC3009AA24E /* QRScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScanner.swift; sourceTree = "<group>"; };
|
||||
D72343352AD037AF009AA24E /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
|
||||
D72992382ADD7F68003AF125 /* HistoryContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryContactFragment.swift; sourceTree = "<group>"; };
|
||||
D732A9082AFD235500DB42BA /* ShareSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheetController.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>"; };
|
||||
|
|
@ -107,6 +114,11 @@
|
|||
D7A2EDDA2AC19EEC005D90FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInnerFragment.swift; sourceTree = "<group>"; };
|
||||
D7C365072AEFAB7F00FE6142 /* ContactListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactListBottomSheet.swift; sourceTree = "<group>"; };
|
||||
D7C365092AF001C300FE6142 /* EditContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditContactFragment.swift; sourceTree = "<group>"; };
|
||||
D7C3650B2AF0084000FE6142 /* EditContactViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditContactViewModel.swift; sourceTree = "<group>"; };
|
||||
D7C3650D2AF15BF200FE6142 /* PhotoPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPicker.swift; sourceTree = "<group>"; };
|
||||
D7C48DF32AFA66F900D938CB /* EditContactController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditContactController.swift; sourceTree = "<group>"; };
|
||||
D7C48DF52AFCDF4700D938CB /* ContactInnerActionsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInnerActionsFragment.swift; sourceTree = "<group>"; };
|
||||
D7D1698B2AE66FA500109A5C /* MagicSearchSingleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicSearchSingleton.swift; sourceTree = "<group>"; };
|
||||
D7D24D0D2AC1B4E800C6F35B /* NotoSans-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSans-Medium.ttf"; sourceTree = "<group>"; };
|
||||
D7D24D0E2AC1B4E800C6F35B /* NotoSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSans-Regular.ttf"; sourceTree = "<group>"; };
|
||||
|
|
@ -150,6 +162,9 @@
|
|||
D717071F2AC5989C0037746F /* TextExtension.swift */,
|
||||
D74C9D002ACB098C0021626A /* PermissionManager.swift */,
|
||||
D7D1698B2AE66FA500109A5C /* MagicSearchSingleton.swift */,
|
||||
D7C3650D2AF15BF200FE6142 /* PhotoPicker.swift */,
|
||||
D7C48DF32AFA66F900D938CB /* EditContactController.swift */,
|
||||
D732A9082AFD235500DB42BA /* ShareSheetController.swift */,
|
||||
);
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -315,6 +330,8 @@
|
|||
D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */,
|
||||
D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */,
|
||||
D7C365072AEFAB7F00FE6142 /* ContactListBottomSheet.swift */,
|
||||
D7C365092AF001C300FE6142 /* EditContactFragment.swift */,
|
||||
D7C48DF52AFCDF4700D938CB /* ContactInnerActionsFragment.swift */,
|
||||
);
|
||||
path = Fragments;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -325,6 +342,7 @@
|
|||
D78290BA2ADD40B2004AA85C /* ContactViewModel.swift */,
|
||||
D71FCA7E2AE1397200D2E43E /* ContactsListViewModel.swift */,
|
||||
D7E6D04A2AE9347D00A57AAF /* FavoriteContactsListViewModel.swift */,
|
||||
D7C3650B2AF0084000FE6142 /* EditContactViewModel.swift */,
|
||||
);
|
||||
path = ViewModel;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -484,21 +502,25 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D7C3650E2AF15BF200FE6142 /* PhotoPicker.swift in Sources */,
|
||||
D71707202AC5989C0037746F /* TextExtension.swift in Sources */,
|
||||
D719ABB92ABC67BF00B41C10 /* ContentView.swift in Sources */,
|
||||
D71FCA832AE14D6E00D2E43E /* ContactFragment.swift in Sources */,
|
||||
D7C3650C2AF0084000FE6142 /* EditContactViewModel.swift in Sources */,
|
||||
D750D3392AD3E6EE00EC99C5 /* PopupLoadingView.swift in Sources */,
|
||||
D7E6D0492AE933AD00A57AAF /* FavoriteContactsListFragment.swift in Sources */,
|
||||
D706BA822ADD72D100278F45 /* DeviceRotationViewModifier.swift in Sources */,
|
||||
D719ABC92ABC6FD700B41C10 /* CoreContext.swift in Sources */,
|
||||
D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */,
|
||||
D777DBB32AE12C5900565A99 /* ContactsManager.swift in Sources */,
|
||||
D7C3650A2AF001C300FE6142 /* EditContactFragment.swift in Sources */,
|
||||
D7A03FBD2ACC2DB60081A588 /* ContactsView.swift in Sources */,
|
||||
D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */,
|
||||
D78290BB2ADD40B2004AA85C /* ContactViewModel.swift in Sources */,
|
||||
D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */,
|
||||
D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */,
|
||||
D7E6D04D2AEBD77600A57AAF /* CustomBottomSheet.swift in Sources */,
|
||||
D7C48DF42AFA66F900D938CB /* EditContactController.swift in Sources */,
|
||||
D74C9D012ACB098C0021626A /* PermissionManager.swift in Sources */,
|
||||
D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */,
|
||||
D71FCA7F2AE1397200D2E43E /* ContactsListViewModel.swift in Sources */,
|
||||
|
|
@ -513,8 +535,10 @@
|
|||
D748BF2C2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift in Sources */,
|
||||
D74C9CF82ACACECE0021626A /* WelcomePage1Fragment.swift in Sources */,
|
||||
D7E6D0552AEBFCCE00A57AAF /* ContactsInnerFragment.swift in Sources */,
|
||||
D732A9092AFD235500DB42BA /* ShareSheetController.swift in Sources */,
|
||||
D72343362AD037AF009AA24E /* ToastView.swift in Sources */,
|
||||
D7FB55112AD447FD00A5AB15 /* RegisterFragment.swift in Sources */,
|
||||
D7C48DF62AFCDF4700D938CB /* ContactInnerActionsFragment.swift in Sources */,
|
||||
D72343322ACEFF58009AA24E /* QRScannerController.swift in Sources */,
|
||||
D72343342ACEFFC3009AA24E /* QRScanner.swift in Sources */,
|
||||
D72343302ACEFEF8009AA24E /* QrCodeScannerFragment.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_3627_17791)">
|
||||
<rect x="0" y="0" width="120" height="120" rx="60" fill="#DFECF2"/>
|
||||
<path d="M59.9666 26.667C41.5666 26.667 26.6666 41.6003 26.6666 60.0003C26.6666 78.4003 41.5666 93.3337 59.9666 93.3337C78.4 93.3337 93.3333 78.4003 93.3333 60.0003C93.3333 41.6003 78.4 26.667 59.9666 26.667ZM60 86.667C45.2666 86.667 33.3333 74.7337 33.3333 60.0003C33.3333 45.267 45.2666 33.3337 60 33.3337C74.7333 33.3337 86.6666 45.267 86.6666 60.0003C86.6666 74.7337 74.7333 86.667 60 86.667ZM71.6666 56.667C74.4333 56.667 76.6666 54.4337 76.6666 51.667C76.6666 48.9003 74.4333 46.667 71.6666 46.667C68.9 46.667 66.6666 48.9003 66.6666 51.667C66.6666 54.4337 68.9 56.667 71.6666 56.667ZM48.3333 56.667C51.1 56.667 53.3333 54.4337 53.3333 51.667C53.3333 48.9003 51.1 46.667 48.3333 46.667C45.5666 46.667 43.3333 48.9003 43.3333 51.667C43.3333 54.4337 45.5666 56.667 48.3333 56.667ZM60 78.3337C67.7666 78.3337 74.3666 73.467 77.0333 66.667H42.9666C45.6333 73.467 52.2333 78.3337 60 78.3337Z" fill="#4E6074"/>
|
||||
<rect x="0" y="0" width="256" height="256" rx="128" fill="#DFECF2"/>
|
||||
<path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24ZM74.08,197.5a64,64,0,0,1,107.84,0,87.83,87.83,0,0,1-107.84,0ZM96,120a32,32,0,1,1,32,32A32,32,0,0,1,96,120Zm97.76,66.41a79.66,79.66,0,0,0-36.06-28.75,48,48,0,1,0-59.4,0,79.66,79.66,0,0,0-36.06,28.75,88,88,0,1,1,131.52,0Z" fill="#4E6074"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_3627_17791" x="0" y="0" width="120" height="120" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<filter id="filter0_d_3627_17791" x="0" y="0" width="256" height="256" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.2 KiB |
|
|
@ -20,62 +20,72 @@
|
|||
import linphonesw
|
||||
import Contacts
|
||||
import SwiftUI
|
||||
import ContactsUI
|
||||
|
||||
final class ContactsManager: ObservableObject {
|
||||
|
||||
static let shared = ContactsManager()
|
||||
final class ContactsManager {
|
||||
|
||||
private var coreContext = CoreContext.shared
|
||||
private var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
private let nativeAddressBookFriendList = "Native address-book"
|
||||
let linphoneAddressBookFirendList = "Linphone address-book"
|
||||
static let shared = ContactsManager()
|
||||
|
||||
@Published var friendList: FriendList?
|
||||
|
||||
private init() {
|
||||
fetchContacts()
|
||||
}
|
||||
|
||||
func fetchContacts() {
|
||||
private var coreContext = CoreContext.shared
|
||||
private var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
private let nativeAddressBookFriendList = "Native address-book"
|
||||
let linphoneAddressBookFriendList = "Linphone address-book"
|
||||
|
||||
var friendList: FriendList?
|
||||
var linphoneFriendList: FriendList?
|
||||
|
||||
private init() {
|
||||
fetchContacts()
|
||||
}
|
||||
|
||||
func fetchContacts() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
if core.globalState == GlobalState.Shutdown || core.globalState == GlobalState.Off {
|
||||
print("$TAG Core is being stopped or already destroyed, abort")
|
||||
print("\(#function) - Core is being stopped or already destroyed, abort")
|
||||
} else {
|
||||
print("$TAG ${friends.size} friends created")
|
||||
|
||||
self.friendList = core.getFriendListByName(name: self.nativeAddressBookFriendList)
|
||||
if self.friendList == nil {
|
||||
do {
|
||||
self.friendList = try core.createFriendList()
|
||||
} catch let error {
|
||||
print("Failed to enumerate contact", error)
|
||||
}
|
||||
}
|
||||
|
||||
if self.friendList!.displayName == nil || self.friendList!.displayName!.isEmpty {
|
||||
print(
|
||||
"$TAG Friend list [$nativeAddressBookFriendList] didn't exist yet, let's create it"
|
||||
)
|
||||
|
||||
self.friendList!.databaseStorageEnabled = false // We don't want to store local address-book in DB
|
||||
|
||||
self.friendList!.displayName = self.nativeAddressBookFriendList
|
||||
core.addFriendList(list: self.friendList!)
|
||||
} else {
|
||||
print(
|
||||
"$TAG Friend list [$LINPHONE_ADDRESS_BOOK_FRIEND_LIST] found, removing existing friends if any"
|
||||
)
|
||||
self.friendList!.friends.forEach { friend in
|
||||
_ = self.friendList!.removeFriend(linphoneFriend: friend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
self.friendList = try core.getFriendListByName(name: self.nativeAddressBookFriendList) ?? core.createFriendList()
|
||||
} catch let error {
|
||||
print("\(#function) - Failed to enumerate contacts: \(error)")
|
||||
}
|
||||
|
||||
if let friendList = self.friendList {
|
||||
if friendList.displayName == nil || friendList.displayName!.isEmpty {
|
||||
print("\(#function) - Friend list '\(self.nativeAddressBookFriendList)' didn't exist yet, let's create it")
|
||||
friendList.databaseStorageEnabled = false // We don't want to store local address-book in DB
|
||||
friendList.displayName = self.nativeAddressBookFriendList
|
||||
core.addFriendList(list: friendList)
|
||||
} else {
|
||||
print("\(#function) - Friend list '\(friendList.displayName!) found, removing existing friends if any")
|
||||
friendList.friends.forEach { friend in
|
||||
_ = friendList.removeFriend(linphoneFriend: friend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
self.linphoneFriendList = try core.getFriendListByName(name: self.linphoneAddressBookFriendList) ?? core.createFriendList()
|
||||
} catch let error {
|
||||
print("\(#function) - Failed to enumerate contacts: \(error)")
|
||||
}
|
||||
|
||||
if let linphoneFriendList = self.linphoneFriendList {
|
||||
if linphoneFriendList.displayName == nil || linphoneFriendList.displayName!.isEmpty {
|
||||
print("\(#function) - Friend list \(self.linphoneAddressBookFriendList) didn't exist yet, let's create it")
|
||||
linphoneFriendList.databaseStorageEnabled = true
|
||||
linphoneFriendList.displayName = self.linphoneAddressBookFriendList
|
||||
core.addFriendList(list: linphoneFriendList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let store = CNContactStore()
|
||||
|
||||
store.requestAccess(for: .contacts) { (granted, error) in
|
||||
if let error = error {
|
||||
print("failed to request access", error)
|
||||
print("\(#function) - failed to request access", error)
|
||||
return
|
||||
}
|
||||
if granted {
|
||||
|
|
@ -83,47 +93,48 @@ final class ContactsManager: ObservableObject {
|
|||
CNContactFamilyNameKey, CNContactGivenNameKey, CNContactNicknameKey,
|
||||
CNContactPostalAddressesKey, CNContactIdentifierKey,
|
||||
CNInstantMessageAddressUsernameKey, CNContactInstantMessageAddressesKey,
|
||||
CNContactImageDataKey, CNContactThumbnailImageDataKey, CNContactOrganizationNameKey]
|
||||
CNContactOrganizationNameKey, CNContactImageDataAvailableKey, CNContactImageDataKey, CNContactThumbnailImageDataKey]
|
||||
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
|
||||
do {
|
||||
try store.enumerateContacts(with: request, usingBlock: { (contact, _) in
|
||||
|
||||
DispatchQueue.main.sync {
|
||||
let newContact = Contact(
|
||||
firstName: contact.givenName,
|
||||
lastName: contact.familyName,
|
||||
organizationName: contact.organizationName,
|
||||
displayName: contact.nickname,
|
||||
sipAddresses: contact.instantMessageAddresses.map { $0.value.service == "SIP" ? $0.value.username : "" },
|
||||
phoneNumbers: contact.phoneNumbers.map { PhoneNumber(numLabel: $0.label ?? "", num: $0.value.stringValue)},
|
||||
imageData: ""
|
||||
)
|
||||
identifier: contact.identifier,
|
||||
firstName: contact.givenName,
|
||||
lastName: contact.familyName,
|
||||
organizationName: contact.organizationName,
|
||||
jobTitle: "",
|
||||
displayName: contact.nickname,
|
||||
sipAddresses: contact.instantMessageAddresses.map { $0.value.service == "SIP" ? $0.value.username : "" },
|
||||
phoneNumbers: contact.phoneNumbers.map { PhoneNumber(numLabel: $0.label ?? "", num: $0.value.stringValue)},
|
||||
imageData: ""
|
||||
)
|
||||
|
||||
let imageThumbnail = UIImage(data: contact.thumbnailImageData ?? Data())
|
||||
self.saveImage(
|
||||
image:
|
||||
UIImage(data: contact.thumbnailImageData ?? Data())
|
||||
image: imageThumbnail
|
||||
?? self.textToImage(
|
||||
firstName: contact.givenName.isEmpty
|
||||
&& contact.familyName.isEmpty
|
||||
&& contact.phoneNumbers.first?.value.stringValue != nil
|
||||
? contact.phoneNumbers.first!.value.stringValue
|
||||
: contact.givenName, lastName: contact.familyName),
|
||||
name: contact.givenName + contact.familyName + String(Int.random(in: 1...1000)),
|
||||
contact: newContact)
|
||||
&& contact.familyName.isEmpty
|
||||
&& contact.phoneNumbers.first?.value.stringValue != nil
|
||||
? contact.phoneNumbers.first!.value.stringValue
|
||||
: contact.givenName, lastName: contact.familyName),
|
||||
name: contact.givenName + contact.familyName + String(Int.random(in: 1...1000)) + ((imageThumbnail == nil) ? "-default" : ""),
|
||||
contact: newContact, linphoneFriend: false, existingFriend: nil)
|
||||
}
|
||||
})
|
||||
|
||||
} catch let error {
|
||||
print("Failed to enumerate contact", error)
|
||||
print("\(#function) - Failed to enumerate contact", error)
|
||||
}
|
||||
|
||||
} else {
|
||||
print("access denied")
|
||||
print("\(#function) - access denied")
|
||||
}
|
||||
}
|
||||
self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
}
|
||||
}
|
||||
self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
func textToImage(firstName: String, lastName: String) -> UIImage {
|
||||
let lblNameInitialize = UILabel()
|
||||
|
|
@ -152,93 +163,138 @@ final class ContactsManager: ObservableObject {
|
|||
|
||||
return IBImgViewUserProfile
|
||||
}
|
||||
|
||||
func saveImage(image: UIImage, name: String, contact: Contact) {
|
||||
guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else {
|
||||
return
|
||||
}
|
||||
|
||||
func saveImage(image: UIImage, name: String, contact: Contact, linphoneFriend: Bool, existingFriend: Friend?) {
|
||||
guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else {
|
||||
return
|
||||
}
|
||||
|
||||
awaitDataWrite(data: data, name: name) { _, result in
|
||||
|
||||
self.coreContext.doOnCoreQueue() { core in
|
||||
do {
|
||||
var friend = try core.createFriend()
|
||||
|
||||
friend.edit()
|
||||
try friend.setName(newValue: contact.firstName + " " + contact.lastName)
|
||||
friend.organization = contact.organizationName
|
||||
|
||||
var friendAddresses: [Address] = []
|
||||
contact.sipAddresses.forEach { sipAddress in
|
||||
let address = core.interpretUrl(url: sipAddress, applyInternationalPrefix: true)
|
||||
|
||||
if address != nil && ((friendAddresses.firstIndex(where: {$0.asString() == address?.asString()})) == nil) {
|
||||
friend.addAddress(address: address!)
|
||||
friendAddresses.append(address!)
|
||||
}
|
||||
self.saveFriend(result: result, contact: contact, existingFriend: existingFriend) { resultFriend in
|
||||
if resultFriend != nil {
|
||||
if linphoneFriend && existingFriend == nil {
|
||||
_ = self.linphoneFriendList?.addLocalFriend(linphoneFriend: resultFriend!)
|
||||
self.linphoneFriendList?.updateSubscriptions()
|
||||
} else if existingFriend == nil {
|
||||
_ = self.friendList?.addLocalFriend(linphoneFriend: resultFriend!)
|
||||
self.friendList?.updateSubscriptions()
|
||||
}
|
||||
|
||||
var friendPhoneNumbers: [PhoneNumber] = []
|
||||
contact.phoneNumbers.forEach { phone in
|
||||
do {
|
||||
if (friendPhoneNumbers.firstIndex(where: {$0.numLabel == phone.numLabel})) == nil {
|
||||
let labelDrop = String(phone.numLabel.dropFirst(4).dropLast(4))
|
||||
let phoneNumber = try Factory.Instance.createFriendPhoneNumber(phoneNumber: phone.num, label: labelDrop)
|
||||
friend.addPhoneNumberWithLabel(phoneNumber: phoneNumber)
|
||||
friendPhoneNumbers.append(phone)
|
||||
}
|
||||
} catch let error {
|
||||
print("Failed to enumerate contact", error)
|
||||
}
|
||||
}
|
||||
|
||||
let contactImage = result.dropFirst(8)
|
||||
friend.photo = "file:/" + contactImage
|
||||
|
||||
friend.organization = contact.organizationName
|
||||
|
||||
friend.done()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
_ = self.friendList!.addLocalFriend(linphoneFriend: friend)
|
||||
|
||||
self.friendList!.updateSubscriptions()
|
||||
}
|
||||
} catch let error {
|
||||
print("Failed to enumerate contact", error)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func awaitDataWrite(data: Data, name: String, completion: @escaping ((), String) -> Void) {
|
||||
let directory = FileManager.default.temporaryDirectory
|
||||
|
||||
DispatchQueue.main.async {
|
||||
do {
|
||||
let decodedData: () = try data.write(to: directory.appendingPathComponent(name + ".png"))
|
||||
completion(decodedData, directory.appendingPathComponent(name + ".png").absoluteString) // <--- here, return the results
|
||||
} catch {
|
||||
print("Error: ", error) // need to deal with errors
|
||||
completion((), "") // <--- here, should return the error
|
||||
func saveFriend(result: String, contact: Contact, existingFriend: Friend?, completion: @escaping (Friend?) -> Void) {
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
do {
|
||||
let friend = try existingFriend ?? core.createFriend()
|
||||
|
||||
friend.edit()
|
||||
friend.nativeUri = contact.identifier
|
||||
try friend.setName(newValue: contact.firstName + " " + contact.lastName)
|
||||
|
||||
let friendvCard = friend.vcard
|
||||
|
||||
if friendvCard != nil {
|
||||
friendvCard!.givenName = contact.firstName
|
||||
friendvCard!.familyName = contact.lastName
|
||||
}
|
||||
|
||||
friend.organization = contact.organizationName
|
||||
|
||||
var friendAddresses: [Address] = []
|
||||
friend.addresses.forEach({ address in
|
||||
friend.removeAddress(address: address)
|
||||
})
|
||||
contact.sipAddresses.forEach { sipAddress in
|
||||
let address = core.interpretUrl(url: sipAddress, applyInternationalPrefix: true)
|
||||
|
||||
if address != nil && ((friendAddresses.firstIndex(where: {$0.asString() == address?.asString()})) == nil) {
|
||||
friend.addAddress(address: address!)
|
||||
friendAddresses.append(address!)
|
||||
}
|
||||
}
|
||||
|
||||
var friendPhoneNumbers: [PhoneNumber] = []
|
||||
friend.phoneNumbersWithLabel.forEach({ phoneNumber in
|
||||
friend.removePhoneNumberWithLabel(phoneNumber: phoneNumber)
|
||||
})
|
||||
contact.phoneNumbers.forEach { phone in
|
||||
do {
|
||||
if (friendPhoneNumbers.firstIndex(where: {$0.num == phone.num})) == nil {
|
||||
let labelDrop = String(phone.numLabel.dropFirst(4).dropLast(4))
|
||||
let phoneNumber = try Factory.Instance.createFriendPhoneNumber(phoneNumber: phone.num, label: labelDrop)
|
||||
friend.addPhoneNumberWithLabel(phoneNumber: phoneNumber)
|
||||
friendPhoneNumbers.append(phone)
|
||||
}
|
||||
} catch let error {
|
||||
print("\(#function) - Failed to create friend phone number for \(phone.numLabel):", error)
|
||||
}
|
||||
}
|
||||
|
||||
friend.photo = "file:/" + result
|
||||
friend.organization = contact.organizationName
|
||||
friend.jobTitle = contact.jobTitle
|
||||
|
||||
friend.done()
|
||||
completion(friend)
|
||||
} catch let error {
|
||||
print("Failed to enumerate contact", error)
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getImagePath(friendPhotoPath: String) -> URL {
|
||||
let friendPath = String(friendPhotoPath.dropFirst(6))
|
||||
|
||||
let imagePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent(friendPath)
|
||||
|
||||
return imagePath
|
||||
}
|
||||
|
||||
func awaitDataWrite(data: Data, name: String, completion: @escaping ((), String) -> Void) {
|
||||
let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
|
||||
|
||||
if directory != nil {
|
||||
DispatchQueue.main.async {
|
||||
do {
|
||||
let urlName = URL(string: name)
|
||||
let imagePath = urlName != nil ? urlName!.absoluteString.replacingOccurrences(of: "%", with: "") : String(Int.random(in: 1...1000))
|
||||
let decodedData: () = try data.write(to: directory!.appendingPathComponent(imagePath + ".png"))
|
||||
completion(decodedData, imagePath + ".png")
|
||||
} catch {
|
||||
print("Error: ", error)
|
||||
completion((), "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getFriend(contact: Contact) -> Friend? {
|
||||
if friendList != nil {
|
||||
let friend = friendList!.friends.first(where: {$0.nativeUri == contact.identifier})
|
||||
return friend
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PhoneNumber {
|
||||
var numLabel: String
|
||||
var num: String
|
||||
var numLabel: String
|
||||
var num: String
|
||||
}
|
||||
|
||||
struct Contact: Identifiable {
|
||||
var id = UUID()
|
||||
var firstName: String
|
||||
var lastName: String
|
||||
var organizationName: String
|
||||
var displayName: String
|
||||
var sipAddresses: [String] = []
|
||||
var phoneNumbers: [PhoneNumber] = []
|
||||
var imageData: String
|
||||
var id = UUID()
|
||||
var identifier: String
|
||||
var firstName: String
|
||||
var lastName: String
|
||||
var organizationName: String
|
||||
var jobTitle: String
|
||||
var displayName: String
|
||||
var sipAddresses: [String] = []
|
||||
var phoneNumbers: [PhoneNumber] = []
|
||||
var imageData: String
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ final class CoreContext: ObservableObject {
|
|||
|
||||
private init() {}
|
||||
|
||||
func doOnCoreQueue(synchronous : Bool = false, lambda: @escaping (Core) -> Void) {
|
||||
func doOnCoreQueue(synchronous: Bool = false, lambda: @escaping (Core) -> Void) {
|
||||
if synchronous {
|
||||
coreQueue.sync {
|
||||
lambda(self.mCore)
|
||||
|
|
@ -77,7 +77,9 @@ final class CoreContext: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
self.mCore.publisher?.onAccountRegistrationStateChanged?.postOnMainQueue { (cbVal: (core: Core, account: Account, state: RegistrationState, message: String)) in
|
||||
self.mCore.publisher?.onAccountRegistrationStateChanged?.postOnMainQueue {(cbVal:
|
||||
(core: Core, account: Account, state: RegistrationState, message: String)
|
||||
) in
|
||||
// If account has been configured correctly, we will go through Progress and Ok states
|
||||
// Otherwise, we will be Failed.
|
||||
NSLog("New registration state is \(cbVal.state) for user id " +
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ struct LinphoneApp: App {
|
|||
AssistantView(sharedMainViewModel: sharedMainViewModel)
|
||||
.toast(isShowing: $coreContext.toastMessage)
|
||||
} else if coreContext.defaultAccount != nil {
|
||||
ContentView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel())
|
||||
ContentView(contactViewModel: ContactViewModel(), editContactViewModel: EditContactViewModel(), historyViewModel: HistoryViewModel())
|
||||
.toast(isShowing: $coreContext.toastMessage)
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
"strings" : {
|
||||
"" : {
|
||||
|
||||
},
|
||||
" " : {
|
||||
|
||||
},
|
||||
" et " : {
|
||||
|
||||
|
|
@ -33,6 +36,9 @@
|
|||
},
|
||||
"**Contacts** : Pour vous afficher vos contacts et retrouver qui utilise Linphone." : {
|
||||
|
||||
},
|
||||
"**Job :** %@" : {
|
||||
|
||||
},
|
||||
"**Micro** : Pour permettre à vos correspondants de vous entendre." : {
|
||||
|
||||
|
|
@ -95,12 +101,18 @@
|
|||
},
|
||||
"Accept all" : {
|
||||
|
||||
},
|
||||
"Add a picture" : {
|
||||
|
||||
},
|
||||
"Add to favourites" : {
|
||||
|
||||
},
|
||||
"All contacts" : {
|
||||
|
||||
},
|
||||
"All modifications will be canceled." : {
|
||||
|
||||
},
|
||||
"Appel" : {
|
||||
|
||||
|
|
@ -121,9 +133,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Block" : {
|
||||
|
||||
},
|
||||
"Block the address" : {
|
||||
|
||||
|
|
@ -145,6 +154,9 @@
|
|||
},
|
||||
"Close" : {
|
||||
|
||||
},
|
||||
"Company" : {
|
||||
|
||||
},
|
||||
"Conditions de service" : {
|
||||
|
||||
|
|
@ -190,9 +202,21 @@
|
|||
},
|
||||
"Domain" : {
|
||||
|
||||
},
|
||||
"Don’t save modifications?" : {
|
||||
|
||||
},
|
||||
"Edit" : {
|
||||
|
||||
},
|
||||
"Edit contact" : {
|
||||
|
||||
},
|
||||
"Edit Contact" : {
|
||||
|
||||
},
|
||||
"Edit picture" : {
|
||||
|
||||
},
|
||||
"En continuant, vous acceptez ces conditions, " : {
|
||||
|
||||
|
|
@ -208,6 +232,12 @@
|
|||
},
|
||||
"Favourites" : {
|
||||
|
||||
},
|
||||
"First Name" : {
|
||||
|
||||
},
|
||||
"First name*" : {
|
||||
|
||||
},
|
||||
"History Contact fragment" : {
|
||||
|
||||
|
|
@ -235,6 +265,12 @@
|
|||
},
|
||||
"Invitation" : {
|
||||
|
||||
},
|
||||
"Job title" : {
|
||||
|
||||
},
|
||||
"Last name" : {
|
||||
|
||||
},
|
||||
"Linphone" : {
|
||||
|
||||
|
|
@ -248,10 +284,10 @@
|
|||
"Message" : {
|
||||
|
||||
},
|
||||
"Mute" : {
|
||||
"My Profile" : {
|
||||
|
||||
},
|
||||
"My Profile" : {
|
||||
"New contact" : {
|
||||
|
||||
},
|
||||
"Next" : {
|
||||
|
|
@ -297,9 +333,15 @@
|
|||
},
|
||||
"Personnalize your profil mode" : {
|
||||
|
||||
},
|
||||
"Phone :" : {
|
||||
|
||||
},
|
||||
"Phone (%@) :" : {
|
||||
|
||||
},
|
||||
"Phone number" : {
|
||||
|
||||
},
|
||||
"Plus tard" : {
|
||||
|
||||
|
|
@ -316,7 +358,10 @@
|
|||
"Register" : {
|
||||
|
||||
},
|
||||
"Remove to favourites" : {
|
||||
"Remove from favourites" : {
|
||||
|
||||
},
|
||||
"Remove picture" : {
|
||||
|
||||
},
|
||||
"Scan QR code" : {
|
||||
|
|
@ -333,6 +378,9 @@
|
|||
},
|
||||
"Share" : {
|
||||
|
||||
},
|
||||
"SIP address" : {
|
||||
|
||||
},
|
||||
"SIP address :" : {
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,14 @@ class AccountLoginViewModel: ObservableObject {
|
|||
// userID is set to null as it's the same as the username in our case
|
||||
// ha1 is set to null as we are using the clear text password. Upon first register, the hash will be computed automatically.
|
||||
// The realm will be determined automatically from the first register, as well as the algorithm
|
||||
let authInfo = try Factory.Instance.createAuthInfo(username: self.username, userid: "", passwd: self.passwd, ha1: "", realm: "", domain: self.domain)
|
||||
let authInfo = try Factory.Instance.createAuthInfo(
|
||||
username: self.username,
|
||||
userid: "",
|
||||
passwd: self.passwd,
|
||||
ha1: "",
|
||||
realm: "",
|
||||
domain: self.domain
|
||||
)
|
||||
|
||||
// Account object replaces deprecated ProxyConfig object
|
||||
// Account object is configured through an AccountParams object that we can obtain from the Core
|
||||
|
|
|
|||
|
|
@ -23,17 +23,22 @@ struct ContactsView: View {
|
|||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var historyViewModel: HistoryViewModel
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
|
||||
@Binding var isShowEditContactFragment: Bool
|
||||
@Binding var isShowDeletePopup: Bool
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
|
||||
ContactsFragment(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup)
|
||||
|
||||
Button {
|
||||
// Action
|
||||
withAnimation {
|
||||
editContactViewModel.selectedEditFriend = nil
|
||||
editContactViewModel.resetValues()
|
||||
isShowEditContactFragment.toggle()
|
||||
}
|
||||
} label: {
|
||||
Image("user-plus")
|
||||
.padding()
|
||||
|
|
@ -50,5 +55,11 @@ struct ContactsView: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
ContactsView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel(), isShowDeletePopup: .constant(false))
|
||||
ContactsView(
|
||||
contactViewModel: ContactViewModel(),
|
||||
historyViewModel: HistoryViewModel(),
|
||||
editContactViewModel: EditContactViewModel(),
|
||||
isShowEditContactFragment: .constant(false),
|
||||
isShowDeletePopup: .constant(false)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,32 +18,87 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import Contacts
|
||||
|
||||
struct ContactFragment: View {
|
||||
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
|
||||
@Binding var isShowDeletePopup: Bool
|
||||
@Binding var isShowDismissPopup: Bool
|
||||
|
||||
@State private var showingSheet = false
|
||||
@State private var showShareSheet = false
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
ContactInnerFragment(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet)
|
||||
if idiom != .pad {
|
||||
ContactInnerFragment(
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
cnContact: CNContact(),
|
||||
isShowDeletePopup: $isShowDeletePopup,
|
||||
showingSheet: $showingSheet,
|
||||
showShareSheet: $showShareSheet,
|
||||
isShowDismissPopup: $isShowDismissPopup
|
||||
)
|
||||
.sheet(isPresented: $showingSheet) {
|
||||
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
.presentationDetents([.fraction(0.2)])
|
||||
}
|
||||
} else {
|
||||
ContactInnerFragment(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet)
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(friendToShare: MagicSearchSingleton.shared.lastSearch[contactViewModel.indexDisplayedFriend!].friend!)
|
||||
.presentationDetents([.medium])
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
} else {
|
||||
ContactInnerFragment(
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
cnContact: CNContact(),
|
||||
isShowDeletePopup: $isShowDeletePopup,
|
||||
showingSheet: $showingSheet,
|
||||
showShareSheet: $showShareSheet,
|
||||
isShowDismissPopup: $isShowDismissPopup
|
||||
)
|
||||
.halfSheet(showSheet: $showingSheet) {
|
||||
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
} onDismiss: {}
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(friendToShare: MagicSearchSingleton.shared.lastSearch[contactViewModel.indexDisplayedFriend!].friend!)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ContactInnerFragment(
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
cnContact: CNContact(),
|
||||
isShowDeletePopup: $isShowDeletePopup,
|
||||
showingSheet: $showingSheet,
|
||||
showShareSheet: $showShareSheet,
|
||||
isShowDismissPopup: $isShowDismissPopup
|
||||
)
|
||||
.halfSheet(showSheet: $showingSheet) {
|
||||
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
} onDismiss: {}
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(friendToShare: MagicSearchSingleton.shared.lastSearch[contactViewModel.indexDisplayedFriend!].friend!)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContactFragment(contactViewModel: ContactViewModel(), isShowDeletePopup: .constant(false))
|
||||
ContactFragment(
|
||||
contactViewModel: ContactViewModel(),
|
||||
editContactViewModel: EditContactViewModel(),
|
||||
isShowDeletePopup: .constant(false),
|
||||
isShowDismissPopup: .constant(false)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,442 @@
|
|||
//
|
||||
// ContactInnerActionsFragment.swift
|
||||
// Linphone
|
||||
//
|
||||
// Created by Benoît Martins on 09/11/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContactInnerActionsFragment: View {
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
|
||||
@State private var informationIsOpen = true
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
@Binding var showShareSheet: Bool
|
||||
@Binding var isShowDeletePopup: Bool
|
||||
@Binding var isShowDismissPopup: Bool
|
||||
|
||||
var actionEditButton: () -> Void
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center) {
|
||||
Text("Information")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(informationIsOpen ? "caret-up" : "caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
.padding(.top, 30)
|
||||
.padding(.bottom, 10)
|
||||
.padding(.horizontal, 16)
|
||||
.background(Color.gray100)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
informationIsOpen.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if informationIsOpen {
|
||||
VStack(spacing: 0) {
|
||||
if contactViewModel.indexDisplayedFriend != nil && magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
|
||||
ForEach(0..<magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses.count, id: \.self) { index in
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
VStack {
|
||||
Text("SIP address :")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Text(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses[index].asStringUriOnly().dropFirst(4))
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
Image("phone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.stringToCopy = magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses[index].asStringUriOnly()
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbers.isEmpty
|
||||
|| index < magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses.count - 1 {
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
ForEach(0..<magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbers.count, id: \.self) { index in
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
VStack {
|
||||
if magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].label != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].label!.isEmpty {
|
||||
Text("Phone (\(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].label!)) :")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {
|
||||
Text("Phone :")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
Text(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].phoneNumber)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
Image("phone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.stringToCopy =
|
||||
magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].phoneNumber
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if index < magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbers.count - 1 {
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(.white)
|
||||
.cornerRadius(15)
|
||||
.padding(.horizontal)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
|
||||
if contactViewModel.indexDisplayedFriend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& ((magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!.isEmpty)
|
||||
|| (magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle!.isEmpty)) {
|
||||
VStack {
|
||||
if magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!.isEmpty {
|
||||
Text("**Company :** \(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!)")
|
||||
.default_text_style(styleSize: 14)
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
if magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle!.isEmpty {
|
||||
Text("**Job :** \(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle!)")
|
||||
.default_text_style(styleSize: 14)
|
||||
.padding(.top,
|
||||
magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!.isEmpty
|
||||
? 0 : 15
|
||||
)
|
||||
.padding(.bottom, 15)
|
||||
.padding(.horizontal, 20)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
.background(.white)
|
||||
.cornerRadius(15)
|
||||
.padding(.top)
|
||||
.padding(.horizontal)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
|
||||
// TODO Trust Fragment
|
||||
|
||||
// TODO Medias Fragment
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("Other actions")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < magicSearch.lastSearch.count
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri!.isEmpty {
|
||||
Button {
|
||||
actionEditButton()
|
||||
} label: {
|
||||
HStack {
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("Edit")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
} else {
|
||||
NavigationLink(destination: EditContactFragment(
|
||||
editContactViewModel: editContactViewModel,
|
||||
isShowEditContactFragment: .constant(false),
|
||||
isShowDismissPopup: $isShowDismissPopup)) {
|
||||
HStack {
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("Edit")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
editContactViewModel.selectedEditFriend = magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend
|
||||
editContactViewModel.resetValues()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
if magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
|
||||
contactViewModel.objectWillChange.send()
|
||||
magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred.toggle()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(contactViewModel.indexDisplayedFriend != nil && magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred == true ? "heart-fill" : "heart")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(contactViewModel.indexDisplayedFriend != nil && magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred == true ? Color.redDanger500 : Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
Text(contactViewModel.indexDisplayedFriend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred == true
|
||||
? "Remove from favourites"
|
||||
: "Add to favourites")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
showShareSheet.toggle()
|
||||
} label: {
|
||||
HStack {
|
||||
Image("share-network")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("Share")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
/*
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
Image("bell-simple-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("Mute")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
Image("x-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("Block")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
*/
|
||||
|
||||
Button {
|
||||
if magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
|
||||
isShowDeletePopup.toggle()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("Delete this contact")
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
}
|
||||
.background(.white)
|
||||
.cornerRadius(15)
|
||||
.padding(.horizontal)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContactInnerActionsFragment(
|
||||
contactViewModel: ContactViewModel(),
|
||||
editContactViewModel: EditContactViewModel(),
|
||||
showingSheet: .constant(false),
|
||||
showShareSheet: .constant(false),
|
||||
isShowDeletePopup: .constant(false),
|
||||
isShowDismissPopup: .constant(false),
|
||||
actionEditButton: {}
|
||||
)
|
||||
}
|
||||
|
|
@ -18,244 +18,159 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import Contacts
|
||||
import ContactsUI
|
||||
|
||||
struct ContactInnerFragment: View {
|
||||
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel()
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
|
||||
@State private var informationIsOpen = true
|
||||
@State private var presentingEditContact = false
|
||||
@State var cnContact: CNContact?
|
||||
|
||||
@Binding var isShowDeletePopup: Bool
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
@Binding var showShareSheet: Bool
|
||||
@Binding var isShowDismissPopup: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 1) {
|
||||
Rectangle()
|
||||
.foregroundColor(Color.orangeMain500)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.frame(height: 0)
|
||||
|
||||
HStack {
|
||||
if !(orientation == .landscapeLeft
|
||||
|| orientation == .landscapeRight
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.top, 2)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
contactViewModel.displayedFriend = nil
|
||||
NavigationView {
|
||||
VStack(spacing: 1) {
|
||||
Rectangle()
|
||||
.foregroundColor(Color.orangeMain500)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.frame(height: 0)
|
||||
|
||||
HStack {
|
||||
if !(orientation == .landscapeLeft || orientation == .landscapeRight
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.top, 2)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
contactViewModel.indexDisplayedFriend = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.top, 2)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 50)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 4)
|
||||
.background(.white)
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
|
||||
Spacer()
|
||||
if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < magicSearch.lastSearch.count
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri!.isEmpty {
|
||||
Button(action: {
|
||||
editNativeContact()
|
||||
}, label: {
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.top, 2)
|
||||
})
|
||||
} else {
|
||||
NavigationLink(destination: EditContactFragment(
|
||||
editContactViewModel: editContactViewModel,
|
||||
isShowEditContactFragment: .constant(false),
|
||||
isShowDismissPopup: $isShowDismissPopup)) {
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.top, 2)
|
||||
}
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
editContactViewModel.selectedEditFriend = magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend
|
||||
editContactViewModel.resetValues()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 50)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 4)
|
||||
.background(.white)
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
if contactViewModel.displayedFriend != nil
|
||||
&& contactViewModel.displayedFriend!.photo != nil
|
||||
&& !contactViewModel.displayedFriend!.photo!.isEmpty {
|
||||
AsyncImage(url: URL(string: contactViewModel.displayedFriend!.photo!)) { image in
|
||||
switch image {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
.frame(width: 100, height: 100)
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
case .failure:
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < magicSearch.lastSearch.count
|
||||
&& 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()
|
||||
}
|
||||
}
|
||||
} else if contactViewModel.indexDisplayedFriend != nil && magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
} else if contactViewModel.displayedFriend != nil {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
if contactViewModel.displayedFriend != nil && contactViewModel.displayedFriend?.name != nil {
|
||||
Text((contactViewModel.displayedFriend?.name)!)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
|
||||
Text("En ligne")
|
||||
.foregroundStyle(Color.greenSuccess500)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
}
|
||||
.frame(minHeight: 150)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
.background(Color.gray100)
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
|
||||
}, label: {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("phone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
|
||||
}
|
||||
}
|
||||
if contactViewModel.indexDisplayedFriend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend?.name != nil {
|
||||
Text((magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend?.name)!)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
|
||||
Text("En ligne")
|
||||
.foregroundStyle(Color.greenSuccess500)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("Appel")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
})
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
.frame(minHeight: 150)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
.background(Color.gray100)
|
||||
|
||||
}, label: {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("chat-teardrop-text")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Text("Message")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
})
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
|
||||
}, label: {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("video-camera")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("Video Call")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
})
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 20)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.gray100)
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("Information")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(informationIsOpen ? "caret-up" : "caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
.padding(.top, 30)
|
||||
.padding(.bottom, 10)
|
||||
.padding(.horizontal, 16)
|
||||
.background(Color.gray100)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
informationIsOpen.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if informationIsOpen {
|
||||
VStack(spacing: 0) {
|
||||
if contactViewModel.displayedFriend != nil {
|
||||
ForEach(0..<contactViewModel.displayedFriend!.addresses.count, id: \.self) { index in
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
VStack {
|
||||
Text("SIP address :")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Text(contactViewModel.displayedFriend!.addresses[index].asStringUriOnly().dropFirst(4))
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
}, label: {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("phone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
|
|
@ -267,50 +182,23 @@ struct ContactInnerFragment: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("Appel")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.stringToCopy = contactViewModel.displayedFriend!.addresses[index].asStringUriOnly()
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if !contactViewModel.displayedFriend!.phoneNumbers.isEmpty || index < contactViewModel.displayedFriend!.addresses.count - 1 {
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
ForEach(0..<contactViewModel.displayedFriend!.phoneNumbers.count, id: \.self) { index in
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
VStack {
|
||||
Text("Phone (\(contactViewModel.displayedFriend!.phoneNumbersWithLabel[index].label!)) :")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Text(contactViewModel.displayedFriend!.phoneNumbersWithLabel[index].phoneNumber)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
Image("phone")
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
|
||||
}, label: {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("chat-teardrop-text")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
|
|
@ -321,245 +209,106 @@ struct ContactInnerFragment: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("Message")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.stringToCopy = contactViewModel.displayedFriend!.phoneNumbersWithLabel[index].phoneNumber
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
|
||||
if index < contactViewModel.displayedFriend!.phoneNumbers.count - 1 {
|
||||
VStack {
|
||||
Divider()
|
||||
}, label: {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("video-camera")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("Video Call")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 20)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.gray100)
|
||||
|
||||
ContactInnerActionsFragment(
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
showingSheet: $showingSheet,
|
||||
showShareSheet: $showShareSheet,
|
||||
isShowDeletePopup: $isShowDeletePopup,
|
||||
isShowDismissPopup: $isShowDismissPopup,
|
||||
actionEditButton: editNativeContact
|
||||
)
|
||||
}
|
||||
.background(.white)
|
||||
.cornerRadius(15)
|
||||
.padding(.horizontal)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
}
|
||||
|
||||
if contactViewModel.displayedFriend != nil
|
||||
&& contactViewModel.displayedFriend!.organization != nil
|
||||
&& !contactViewModel.displayedFriend!.organization!.isEmpty {
|
||||
VStack {
|
||||
Text("**Company :** \(contactViewModel.displayedFriend!.organization!)")
|
||||
.default_text_style(styleSize: 14)
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.background(.white)
|
||||
.cornerRadius(15)
|
||||
.padding(.top)
|
||||
.padding(.horizontal)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
|
||||
// TODO Trust Fragment
|
||||
|
||||
// TODO Medias Fragment
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("Other actions")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("Edit")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
if contactViewModel.displayedFriend != nil {
|
||||
contactViewModel.objectWillChange.send()
|
||||
contactViewModel.displayedFriend!.starred.toggle()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(contactViewModel.displayedFriend != nil && contactViewModel.displayedFriend!.starred == true ? "heart-fill" : "heart")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
Text(contactViewModel.displayedFriend != nil && contactViewModel.displayedFriend!.starred == true
|
||||
? "Remove to favourites"
|
||||
: "Add to favourites")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
Image("share-network")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("Share")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
Image("bell-simple-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("Mute")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
Image("x-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("Block")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
if contactViewModel.displayedFriend != nil {
|
||||
isShowDeletePopup.toggle()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("Delete this contact")
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
}
|
||||
.background(.white)
|
||||
.cornerRadius(15)
|
||||
.padding(.horizontal)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.background(Color.gray100)
|
||||
}
|
||||
.background(.white)
|
||||
.navigationBarHidden(true)
|
||||
.onRotate { newOrientation in
|
||||
orientation = newOrientation
|
||||
}
|
||||
.fullScreenCover(isPresented: $presentingEditContact) {
|
||||
NavigationView {
|
||||
EditContactView(contact: $cnContact)
|
||||
.navigationBarTitle("Edit Contact")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.edgesIgnoringSafeArea(.vertical)
|
||||
}
|
||||
}
|
||||
.background(Color.gray100)
|
||||
}
|
||||
.background(.white)
|
||||
.navigationBarHidden(true)
|
||||
.onRotate { newOrientation in
|
||||
orientation = newOrientation
|
||||
.navigationViewStyle(.stack)
|
||||
}
|
||||
|
||||
func editNativeContact() {
|
||||
do {
|
||||
let store = CNContactStore()
|
||||
let descriptor = CNContactViewController.descriptorForRequiredKeys()
|
||||
cnContact = try store.unifiedContact(
|
||||
withIdentifier: magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri!,
|
||||
keysToFetch: [descriptor]
|
||||
)
|
||||
|
||||
if cnContact != nil {
|
||||
presentingEditContact.toggle()
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContactInnerFragment(contactViewModel: ContactViewModel(), isShowDeletePopup: .constant(false), showingSheet: .constant(false))
|
||||
ContactInnerFragment(
|
||||
contactViewModel: ContactViewModel(),
|
||||
editContactViewModel: EditContactViewModel(),
|
||||
isShowDeletePopup: .constant(false),
|
||||
showingSheet: .constant(false),
|
||||
showShareSheet: .constant(false),
|
||||
isShowDismissPopup: .constant(false)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import UniformTypeIdentifiers
|
|||
|
||||
struct ContactListBottomSheet: View {
|
||||
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
|
@ -34,9 +36,9 @@ struct ContactListBottomSheet: View {
|
|||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if orientation == .landscapeLeft
|
||||
if idiom != .pad && (orientation == .landscapeLeft
|
||||
|| orientation == .landscapeRight
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height {
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
|
|
@ -91,7 +93,12 @@ struct ContactListBottomSheet: View {
|
|||
if contactViewModel.stringToCopy.prefix(4) != "sip:" {
|
||||
Button {
|
||||
if #available(iOS 16.0, *) {
|
||||
showingSheet.toggle()
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
|
|
|
|||
|
|
@ -20,29 +20,67 @@
|
|||
import SwiftUI
|
||||
|
||||
struct ContactsFragment: View {
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
||||
@Binding var isShowDeletePopup: Bool
|
||||
|
||||
@State private var showingSheet = false
|
||||
|
||||
var body: some View {
|
||||
|
||||
@State private var showingSheet = false
|
||||
@State private var showShareSheet = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if #available(iOS 16.0, *) {
|
||||
ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
.sheet(isPresented: $showingSheet) {
|
||||
ContactsListBottomSheet(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet)
|
||||
.presentationDetents([.fraction(0.2)])
|
||||
}
|
||||
if idiom != .pad {
|
||||
ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
.sheet(isPresented: $showingSheet) {
|
||||
ContactsListBottomSheet(
|
||||
contactViewModel: contactViewModel,
|
||||
isShowDeletePopup: $isShowDeletePopup,
|
||||
showingSheet: $showingSheet,
|
||||
showShareSheet: $showShareSheet
|
||||
)
|
||||
.presentationDetents([.fraction(0.2)])
|
||||
}
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(friendToShare: contactViewModel.selectedFriendToShare!)
|
||||
.presentationDetents([.medium])
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
} else {
|
||||
ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
.halfSheet(showSheet: $showingSheet) {
|
||||
ContactsListBottomSheet(
|
||||
contactViewModel: contactViewModel,
|
||||
isShowDeletePopup: $isShowDeletePopup,
|
||||
showingSheet: $showingSheet,
|
||||
showShareSheet: $showShareSheet
|
||||
)
|
||||
} onDismiss: {}
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(friendToShare: contactViewModel.selectedFriendToShare!)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
.halfSheet(showSheet: $showingSheet) {
|
||||
ContactsListBottomSheet(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet)
|
||||
ContactsListBottomSheet(
|
||||
contactViewModel: contactViewModel,
|
||||
isShowDeletePopup: $isShowDeletePopup,
|
||||
showingSheet: $showingSheet,
|
||||
showShareSheet: $showShareSheet
|
||||
)
|
||||
} onDismiss: {}
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(friendToShare: contactViewModel.selectedFriendToShare!)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
|
|
|||
|
|
@ -18,64 +18,77 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import linphonesw
|
||||
|
||||
struct ContactsInnerFragment: View {
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
||||
@State private var isFavoriteOpen = true
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if !magicSearch.lastSearch.filter({ $0.friend?.starred == true }).isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("Favourites")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(isFavoriteOpen ? "caret-up" : "caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
.padding(.top, 30)
|
||||
.padding(.horizontal, 16)
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
isFavoriteOpen.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if isFavoriteOpen {
|
||||
FavoriteContactsListFragment(
|
||||
contactViewModel: contactViewModel,
|
||||
favoriteContactsListViewModel: FavoriteContactsListViewModel(),
|
||||
showingSheet: $showingSheet)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("All contacts")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
ContactsListFragment(contactViewModel: contactViewModel, contactsListViewModel: ContactsListViewModel(), showingSheet: $showingSheet)
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
||||
@State private var isFavoriteOpen = true
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if !magicSearch.lastSearch.filter({ $0.friend?.starred == true }).isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("Favourites")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(isFavoriteOpen ? "caret-up" : "caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
.padding(.top, 30)
|
||||
.padding(.horizontal, 16)
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
isFavoriteOpen.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if isFavoriteOpen {
|
||||
FavoriteContactsListFragment(
|
||||
contactViewModel: contactViewModel,
|
||||
favoriteContactsListViewModel: FavoriteContactsListViewModel(),
|
||||
showingSheet: $showingSheet)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("All contacts")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
ContactsListFragment(contactViewModel: contactViewModel, contactsListViewModel: ContactsListViewModel(), showingSheet: $showingSheet)
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
.onChange(of: scenePhase) { newPhase in
|
||||
if newPhase == .active {
|
||||
ContactsManager.shared.fetchContacts()
|
||||
print("Active")
|
||||
} else if newPhase == .inactive {
|
||||
print("Inactive")
|
||||
} else if newPhase == .background {
|
||||
print("Background")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContactsInnerFragment(contactViewModel: ContactViewModel(), showingSheet: .constant(false))
|
||||
ContactsInnerFragment(contactViewModel: ContactViewModel(), showingSheet: .constant(false))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,138 +19,169 @@
|
|||
|
||||
import SwiftUI
|
||||
import linphonesw
|
||||
import Contacts
|
||||
|
||||
struct ContactsListBottomSheet: View {
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
|
||||
@Binding var isShowDeletePopup: Bool
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if orientation == .landscapeLeft
|
||||
|| orientation == .landscapeRight
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Close") {
|
||||
if #available(iOS 16.0, *) {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.trailing)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
Button {
|
||||
if contactViewModel.selectedFriend != nil {
|
||||
contactViewModel.selectedFriend!.starred.toggle()
|
||||
}
|
||||
self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
@Binding var showingSheet: Bool
|
||||
@Binding var showShareSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if idiom != .pad && (orientation == .landscapeLeft
|
||||
|| orientation == .landscapeRight
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Close") {
|
||||
if #available(iOS 16.0, *) {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.trailing)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
Button {
|
||||
if contactViewModel.selectedFriend != nil {
|
||||
contactViewModel.selectedFriend!.starred.toggle()
|
||||
}
|
||||
self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(contactViewModel.selectedFriend != nil && contactViewModel.selectedFriend!.starred == true ? "heart-fill" : "heart")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text(contactViewModel.selectedFriend != nil && contactViewModel.selectedFriend!.starred == true
|
||||
? "Remove to favourites"
|
||||
: "Add to favourites")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Button {
|
||||
if #available(iOS 16.0, *) {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("share-network")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text("Share")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Button {
|
||||
if contactViewModel.selectedFriend != nil {
|
||||
isShowDeletePopup.toggle()
|
||||
}
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(
|
||||
contactViewModel.selectedFriend != nil && contactViewModel.selectedFriend!.starred == true
|
||||
? Color.redDanger500
|
||||
: Color.grayMain2c500
|
||||
)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text(contactViewModel.selectedFriend != nil && contactViewModel.selectedFriend!.starred == true
|
||||
? "Remove from favourites"
|
||||
: "Add to favourites")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Button {
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text("Delete")
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
}
|
||||
.onRotate { newOrientation in
|
||||
orientation = newOrientation
|
||||
}
|
||||
.background(Color.gray100)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
contactViewModel.selectedFriendToShare = contactViewModel.selectedFriend
|
||||
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
|
||||
showShareSheet.toggle()
|
||||
}
|
||||
|
||||
} label: {
|
||||
HStack {
|
||||
Image("share-network")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text("Share")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Button {
|
||||
if contactViewModel.selectedFriend != nil {
|
||||
isShowDeletePopup.toggle()
|
||||
}
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text("Delete")
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
}
|
||||
.background(Color.gray100)
|
||||
.frame(maxWidth: .infinity)
|
||||
.onRotate { newOrientation in
|
||||
orientation = newOrientation
|
||||
}
|
||||
.onDisappear {
|
||||
contactViewModel.selectedFriend = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ struct ContactsListFragment: View {
|
|||
}
|
||||
|
||||
if magicSearch.lastSearch[index].friend!.photo != nil && !magicSearch.lastSearch[index].friend!.photo!.isEmpty {
|
||||
AsyncImage(url: URL(string: magicSearch.lastSearch[index].friend!.photo!)) { image in
|
||||
AsyncImage(url: ContactsManager.shared.getImagePath(friendPhotoPath: magicSearch.lastSearch[index].friend!.photo!)) { image in
|
||||
switch image {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
|
|
@ -72,6 +72,7 @@ struct ContactsListFragment: View {
|
|||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
case .failure:
|
||||
|
|
@ -106,7 +107,7 @@ struct ContactsListFragment: View {
|
|||
TapGesture()
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
contactViewModel.displayedFriend = magicSearch.lastSearch[index].friend
|
||||
contactViewModel.indexDisplayedFriend = index
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
531
Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift
Normal file
531
Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift
Normal file
|
|
@ -0,0 +1,531 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import linphonesw
|
||||
|
||||
struct EditContactFragment: View {
|
||||
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel()
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
@Binding var isShowEditContactFragment: Bool
|
||||
@Binding var isShowDismissPopup: Bool
|
||||
|
||||
@State private var hasTimeElapsed = false
|
||||
@State private var delayedColor = Color.white
|
||||
|
||||
@FocusState var isFirstNameFocused: Bool
|
||||
@FocusState var isLastNameFocused: Bool
|
||||
@FocusState var isSIPAddressFocused: Int?
|
||||
@FocusState var isPhoneNumberFocused: Int?
|
||||
@FocusState var isCompanyFocused: Bool
|
||||
@FocusState var isJobTitleFocused: Bool
|
||||
|
||||
@State private var showPhotoPicker = false
|
||||
@State private var selectedImage: UIImage?
|
||||
@State private var removedImage = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
VStack(spacing: 1) {
|
||||
if editContactViewModel.selectedEditFriend == nil {
|
||||
Rectangle()
|
||||
.foregroundColor(delayedColor)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.frame(height: 0)
|
||||
.task(delayColor)
|
||||
} else {
|
||||
Rectangle()
|
||||
.foregroundColor(Color.orangeMain500)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.frame(height: 0)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.top, 2)
|
||||
.onTapGesture {
|
||||
if editContactViewModel.selectedEditFriend == nil
|
||||
&& editContactViewModel.firstName.isEmpty
|
||||
&& editContactViewModel.lastName.isEmpty
|
||||
&& editContactViewModel.sipAddresses.first!.isEmpty
|
||||
&& editContactViewModel.phoneNumbers.first!.isEmpty
|
||||
&& editContactViewModel.company.isEmpty
|
||||
&& editContactViewModel.jobTitle.isEmpty {
|
||||
delayColorDismiss()
|
||||
withAnimation {
|
||||
isShowEditContactFragment.toggle()
|
||||
}
|
||||
} else if editContactViewModel.selectedEditFriend == nil {
|
||||
isShowDismissPopup.toggle()
|
||||
} else {
|
||||
if editContactViewModel.firstName.isEmpty
|
||||
&& editContactViewModel.lastName.isEmpty
|
||||
&& editContactViewModel.sipAddresses.first!.isEmpty
|
||||
&& editContactViewModel.phoneNumbers.first!.isEmpty
|
||||
&& editContactViewModel.company.isEmpty
|
||||
&& editContactViewModel.jobTitle.isEmpty {
|
||||
withAnimation {
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
isShowDismissPopup.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(editContactViewModel.selectedEditFriend == nil ? "New contact" : "Edit contact")
|
||||
.multilineTextAlignment(.leading)
|
||||
.default_text_style_orange_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("check")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(editContactViewModel.firstName.isEmpty ? Color.orangeMain100 : Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.top, 2)
|
||||
.disabled(editContactViewModel.firstName.isEmpty)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
addOrEditFriend()
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 50)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 4)
|
||||
.background(.white)
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
} else if selectedImage == nil {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
} else {
|
||||
Image(uiImage: selectedImage!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
if editContactViewModel.selectedEditFriend != nil
|
||||
&& editContactViewModel.selectedEditFriend!.photo != nil
|
||||
&& !editContactViewModel.selectedEditFriend!.photo!.isEmpty
|
||||
&& (editContactViewModel.selectedEditFriend!.photo!.suffix(11) != "default.png" || selectedImage != nil) && !removedImage {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
showPhotoPicker = true
|
||||
}, label: {
|
||||
HStack {
|
||||
Image("pencil-simple")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("Edit picture")
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
})
|
||||
.padding(.top, 10)
|
||||
.padding(.trailing, 10)
|
||||
.sheet(isPresented: $showPhotoPicker) {
|
||||
PhotoPicker(filter: .images, limit: 1) { results in
|
||||
PhotoPicker.convertToUIImageArray(fromResults: results) { imagesOrNil, errorOrNil in
|
||||
if let error = errorOrNil {
|
||||
print(error)
|
||||
}
|
||||
if let images = imagesOrNil {
|
||||
if let first = images.first {
|
||||
selectedImage = first
|
||||
removedImage = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
removedImage = true
|
||||
selectedImage = nil
|
||||
}, label: {
|
||||
HStack {
|
||||
Image("trash-simple")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("Remove picture")
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
})
|
||||
.padding(.top, 10)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
} else {
|
||||
Button(action: {
|
||||
showPhotoPicker = true
|
||||
}, label: {
|
||||
HStack {
|
||||
Image("camera")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("Add a picture")
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
})
|
||||
.padding(.top, 10)
|
||||
.sheet(isPresented: $showPhotoPicker) {
|
||||
PhotoPicker(filter: .images, limit: 1) { results in
|
||||
PhotoPicker.convertToUIImageArray(fromResults: results) { imagesOrNil, errorOrNil in
|
||||
if let error = errorOrNil {
|
||||
print(error)
|
||||
}
|
||||
if let images = imagesOrNil {
|
||||
if let first = images.first {
|
||||
selectedImage = first
|
||||
removedImage = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 150)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("First name*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("First Name", text: $editContactViewModel.firstName)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.background(.white)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isFirstNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.focused($isFirstNameFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("Last name")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("Last name", text: $editContactViewModel.lastName)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.background(.white)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isLastNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.focused($isLastNameFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("SIP address")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
ForEach(0..<editContactViewModel.sipAddresses.count, id: \.self) { index in
|
||||
|
||||
HStack(alignment: .center) {
|
||||
TextField("SIP address", text: $editContactViewModel.sipAddresses[index])
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.background(.white)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isSIPAddressFocused == index ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isSIPAddressFocused, equals: index)
|
||||
.onChange(of: editContactViewModel.sipAddresses[index]) { newValue in
|
||||
if !newValue.isEmpty && index + 1 == editContactViewModel.sipAddresses.count {
|
||||
editContactViewModel.sipAddresses.append("")
|
||||
}
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
editContactViewModel.sipAddresses.remove(at: index)
|
||||
}, label: {
|
||||
Image("x")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(
|
||||
editContactViewModel.sipAddresses[index].isEmpty && editContactViewModel.sipAddresses.count == index + 1
|
||||
? Color.gray100
|
||||
: Color.grayMain2c600
|
||||
)
|
||||
.frame(width: 25, height: 25)
|
||||
})
|
||||
.disabled(editContactViewModel.sipAddresses[index].isEmpty && editContactViewModel.sipAddresses.count == index + 1)
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("Phone number")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
ForEach(0..<editContactViewModel.phoneNumbers.count, id: \.self) { index in
|
||||
HStack(alignment: .center) {
|
||||
TextField("Phone number", text: $editContactViewModel.phoneNumbers[index])
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.background(.white)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isPhoneNumberFocused == index ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isPhoneNumberFocused, equals: index)
|
||||
.onChange(of: editContactViewModel.phoneNumbers[index]) { newValue in
|
||||
if !newValue.isEmpty && index + 1 == editContactViewModel.phoneNumbers.count {
|
||||
withAnimation {
|
||||
editContactViewModel.phoneNumbers.append("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
editContactViewModel.phoneNumbers.remove(at: index)
|
||||
}, label: {
|
||||
Image("x")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(
|
||||
editContactViewModel.phoneNumbers[index].isEmpty && editContactViewModel.phoneNumbers.count == index + 1
|
||||
? Color.gray100
|
||||
: Color.grayMain2c600
|
||||
)
|
||||
.frame(width: 25, height: 25)
|
||||
})
|
||||
.disabled(editContactViewModel.phoneNumbers[index].isEmpty && editContactViewModel.phoneNumbers.count == index + 1)
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.zIndex(isPhoneNumberFocused == index ? 1 : 0)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("Company")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("Company", text: $editContactViewModel.company)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.background(.white)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isCompanyFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.focused($isCompanyFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("Job title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("Job title", text: $editContactViewModel.jobTitle)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.background(.white)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isJobTitleFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.focused($isJobTitleFocused)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.background(Color.gray100)
|
||||
}
|
||||
.background(.white)
|
||||
|
||||
if editContactViewModel.removePopup {
|
||||
ZStack {
|
||||
|
||||
}.onAppear {
|
||||
if editContactViewModel.selectedEditFriend == nil {
|
||||
delayColorDismiss()
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
editContactViewModel.removePopup = false
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
|
||||
@Sendable private func delayColor() async {
|
||||
try? await Task.sleep(nanoseconds: 250_000_000)
|
||||
delayedColor = Color.orangeMain500
|
||||
}
|
||||
|
||||
func delayColorDismiss() {
|
||||
if editContactViewModel.selectedEditFriend == nil {
|
||||
Task {
|
||||
try? await Task.sleep(nanoseconds: 80_000_000)
|
||||
delayedColor = .white
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addOrEditFriend() {
|
||||
let newContact = Contact(
|
||||
identifier: editContactViewModel.identifier,
|
||||
firstName: editContactViewModel.firstName,
|
||||
lastName: editContactViewModel.lastName,
|
||||
organizationName: editContactViewModel.company,
|
||||
jobTitle: editContactViewModel.jobTitle,
|
||||
displayName: "",
|
||||
sipAddresses: editContactViewModel.sipAddresses.map { $0 },
|
||||
phoneNumbers: editContactViewModel.phoneNumbers.map { PhoneNumber(numLabel: "", num: $0)},
|
||||
imageData: ""
|
||||
)
|
||||
|
||||
if editContactViewModel.selectedEditFriend != nil && selectedImage == nil &&
|
||||
!removedImage {
|
||||
ContactsManager.shared.saveFriend(
|
||||
result: String(editContactViewModel.selectedEditFriend!.photo!.dropFirst(6)),
|
||||
contact: newContact,
|
||||
existingFriend: editContactViewModel.selectedEditFriend, completion: {_ in }
|
||||
)
|
||||
} else {
|
||||
ContactsManager.shared.saveImage(
|
||||
image: selectedImage
|
||||
?? ContactsManager.shared.textToImage(
|
||||
firstName: editContactViewModel.firstName, lastName: editContactViewModel.lastName),
|
||||
name: editContactViewModel.firstName
|
||||
+ editContactViewModel.lastName
|
||||
+ String(Int.random(in: 1...1000))
|
||||
+ ((selectedImage == nil) ? "-default" : ""),
|
||||
contact: newContact, linphoneFriend: true, existingFriend: editContactViewModel.selectedEditFriend)
|
||||
}
|
||||
|
||||
MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
||||
delayColorDismiss()
|
||||
if editContactViewModel.selectedEditFriend == nil {
|
||||
withAnimation {
|
||||
isShowEditContactFragment.toggle()
|
||||
}
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
editContactViewModel.resetValues()
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
EditContactFragment(editContactViewModel: EditContactViewModel(), isShowEditContactFragment: .constant(false), isShowDismissPopup: .constant(false))
|
||||
}
|
||||
|
|
@ -32,62 +32,62 @@ struct FavoriteContactsListFragment: View {
|
|||
var body: some View {
|
||||
ScrollView(.horizontal) {
|
||||
HStack {
|
||||
ForEach(0..<magicSearch.lastSearch.filter({ $0.friend?.starred == true }).count, id: \.self) { index in
|
||||
Button {
|
||||
} label: {
|
||||
VStack {
|
||||
if magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend!.photo != nil
|
||||
&& !magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend!.photo!.isEmpty {
|
||||
AsyncImage(
|
||||
url: URL(string: magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend!.photo!)
|
||||
) { image in
|
||||
switch image {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
.frame(width: 45, height: 45)
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
case .failure:
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
Text((magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend?.name)!)
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame( maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
}
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.selectedFriend = magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
contactViewModel.displayedFriend = (
|
||||
magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend
|
||||
)!
|
||||
}
|
||||
}
|
||||
)
|
||||
.frame(minWidth: 70, maxWidth: 70)
|
||||
ForEach(0..<magicSearch.lastSearch.count, id: \.self) { index in
|
||||
if magicSearch.lastSearch[index].friend != nil && magicSearch.lastSearch[index].friend!.starred == true {
|
||||
Button {
|
||||
} label: {
|
||||
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()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
Text((magicSearch.lastSearch[index].friend?.name)!)
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame( maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
}
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.selectedFriend = magicSearch.lastSearch[index].friend
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
contactViewModel.indexDisplayedFriend = index
|
||||
}
|
||||
}
|
||||
)
|
||||
.frame(minWidth: 70, maxWidth: 70)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.all, 10)
|
||||
|
|
|
|||
|
|
@ -20,11 +20,14 @@
|
|||
import linphonesw
|
||||
|
||||
class ContactViewModel: ObservableObject {
|
||||
|
||||
@Published var displayedFriend: Friend?
|
||||
|
||||
@Published var indexDisplayedFriend: Int?
|
||||
|
||||
var stringToCopy: String = ""
|
||||
|
||||
var selectedFriend: Friend?
|
||||
var selectedFriendToShare: Friend?
|
||||
var selectedFriendToDelete: Friend?
|
||||
|
||||
private var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import linphonesw
|
||||
|
||||
class EditContactViewModel: ObservableObject {
|
||||
|
||||
@Published var selectedEditFriend: Friend?
|
||||
|
||||
@Published var identifier: String = ""
|
||||
@Published var firstName: String = ""
|
||||
@Published var lastName: String = ""
|
||||
@Published var sipAddresses: [String] = []
|
||||
@Published var phoneNumbers: [String] = []
|
||||
@Published var company: String = ""
|
||||
@Published var jobTitle: String = ""
|
||||
@Published var removePopup: Bool = false
|
||||
|
||||
init() {
|
||||
resetValues()
|
||||
}
|
||||
|
||||
func resetValues() {
|
||||
identifier = (selectedEditFriend == nil ? "" : selectedEditFriend!.nativeUri) ?? ""
|
||||
firstName = (selectedEditFriend == nil ? "" : selectedEditFriend!.vcard?.givenName) ?? ""
|
||||
lastName = (selectedEditFriend == nil ? "" : selectedEditFriend!.vcard?.familyName) ?? ""
|
||||
sipAddresses = []
|
||||
phoneNumbers = []
|
||||
company = (selectedEditFriend == nil ? "" : selectedEditFriend!.organization) ?? ""
|
||||
jobTitle = (selectedEditFriend == nil ? "" : selectedEditFriend!.jobTitle) ?? ""
|
||||
|
||||
if selectedEditFriend != nil {
|
||||
selectedEditFriend?.addresses.forEach({ address in
|
||||
sipAddresses.append(String(address.asStringUriOnly().dropFirst(4)))
|
||||
})
|
||||
|
||||
selectedEditFriend?.phoneNumbers.forEach({ phoneNumber in
|
||||
phoneNumbers.append(phoneNumber)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
sipAddresses.append("")
|
||||
phoneNumbers.append("")
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ struct ContentView: View {
|
|||
var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
@ObservedObject var historyViewModel: HistoryViewModel
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
|
||||
|
|
@ -36,8 +37,10 @@ struct ContentView: View {
|
|||
@State private var searchIsActive = false
|
||||
@State private var text = ""
|
||||
@FocusState private var focusedField: Bool
|
||||
@State var isMenuOpen: Bool = false
|
||||
@State var isMenuOpen = false
|
||||
@State var isShowDeletePopup = false
|
||||
@State var isShowEditContactFragment = false
|
||||
@State var isShowDismissPopup = false
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
|
|
@ -73,7 +76,7 @@ struct ContentView: View {
|
|||
|
||||
Button(action: {
|
||||
self.index = 1
|
||||
contactViewModel.displayedFriend = nil
|
||||
contactViewModel.indexDisplayedFriend = nil
|
||||
}, label: {
|
||||
VStack {
|
||||
Image("phone")
|
||||
|
|
@ -273,7 +276,13 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
if self.index == 0 {
|
||||
ContactsView(contactViewModel: contactViewModel, historyViewModel: historyViewModel, isShowDeletePopup: $isShowDeletePopup)
|
||||
ContactsView(
|
||||
contactViewModel: contactViewModel,
|
||||
historyViewModel: historyViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
isShowEditContactFragment: $isShowEditContactFragment,
|
||||
isShowDeletePopup: $isShowDeletePopup
|
||||
)
|
||||
} else if self.index == 1 {
|
||||
HistoryView()
|
||||
}
|
||||
|
|
@ -328,7 +337,7 @@ struct ContentView: View {
|
|||
|
||||
Button(action: {
|
||||
self.index = 1
|
||||
contactViewModel.displayedFriend = nil
|
||||
contactViewModel.indexDisplayedFriend = nil
|
||||
}, label: {
|
||||
VStack {
|
||||
Image("phone")
|
||||
|
|
@ -358,7 +367,7 @@ struct ContentView: View {
|
|||
}
|
||||
}
|
||||
|
||||
if contactViewModel.displayedFriend != nil || !historyViewModel.historyTitle.isEmpty {
|
||||
if contactViewModel.indexDisplayedFriend != nil || !historyViewModel.historyTitle.isEmpty {
|
||||
HStack(spacing: 0) {
|
||||
Spacer()
|
||||
.frame(maxWidth:
|
||||
|
|
@ -369,7 +378,12 @@ struct ContentView: View {
|
|||
: 0
|
||||
)
|
||||
if self.index == 0 {
|
||||
ContactFragment(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup)
|
||||
ContactFragment(
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
isShowDeletePopup: $isShowDeletePopup,
|
||||
isShowDismissPopup: $isShowDismissPopup
|
||||
)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.gray100)
|
||||
.ignoresSafeArea(.keyboard)
|
||||
|
|
@ -413,31 +427,46 @@ struct ContentView: View {
|
|||
.ignoresSafeArea(.all)
|
||||
.zIndex(2)
|
||||
|
||||
if isShowEditContactFragment {
|
||||
EditContactFragment(
|
||||
editContactViewModel: editContactViewModel,
|
||||
isShowEditContactFragment: $isShowEditContactFragment,
|
||||
isShowDismissPopup: $isShowDismissPopup
|
||||
)
|
||||
.zIndex(3)
|
||||
.transition(.move(edge: .bottom))
|
||||
.onAppear {
|
||||
contactViewModel.indexDisplayedFriend = nil
|
||||
}
|
||||
}
|
||||
|
||||
if isShowDeletePopup {
|
||||
PopupView(sharedMainViewModel: SharedMainViewModel(), isShowPopup: $isShowDeletePopup,
|
||||
title: Text(
|
||||
contactViewModel.selectedFriend != nil
|
||||
? "Delete \(contactViewModel.selectedFriend!.name!)?"
|
||||
: (contactViewModel.displayedFriend != nil
|
||||
? "Delete \(contactViewModel.displayedFriend!.name!)?"
|
||||
: "Error Name")),
|
||||
contactViewModel.selectedFriend != nil
|
||||
? "Delete \(contactViewModel.selectedFriend!.name!)?"
|
||||
: (contactViewModel.indexDisplayedFriend != nil
|
||||
? "Delete \(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.name!)?"
|
||||
: "Error Name")),
|
||||
content: Text("This contact will be deleted definitively."),
|
||||
titleFirstButton: Text("Cancel"),
|
||||
actionFirstButton: {self.isShowDeletePopup.toggle()},
|
||||
actionFirstButton: {
|
||||
self.isShowDeletePopup.toggle()},
|
||||
titleSecondButton: Text("Ok"),
|
||||
actionSecondButton: {
|
||||
if contactViewModel.selectedFriend != nil {
|
||||
contactViewModel.selectedFriend!.remove()
|
||||
if contactViewModel.displayedFriend != nil && contactViewModel.selectedFriend!.name == contactViewModel.displayedFriend!.name {
|
||||
if contactViewModel.selectedFriendToDelete != nil {
|
||||
if contactViewModel.indexDisplayedFriend != nil {
|
||||
withAnimation {
|
||||
contactViewModel.displayedFriend = nil
|
||||
contactViewModel.indexDisplayedFriend = nil
|
||||
}
|
||||
}
|
||||
} else if contactViewModel.displayedFriend != nil {
|
||||
contactViewModel.displayedFriend!.remove()
|
||||
contactViewModel.selectedFriendToDelete!.remove()
|
||||
} else if contactViewModel.indexDisplayedFriend != nil {
|
||||
let tmpIndex = contactViewModel.indexDisplayedFriend
|
||||
withAnimation {
|
||||
contactViewModel.displayedFriend = nil
|
||||
contactViewModel.indexDisplayedFriend = nil
|
||||
}
|
||||
magicSearch.lastSearch[tmpIndex!].friend!.remove()
|
||||
}
|
||||
magicSearch.searchForContacts(
|
||||
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
|
@ -448,6 +477,39 @@ struct ContentView: View {
|
|||
.onTapGesture {
|
||||
self.isShowDeletePopup.toggle()
|
||||
}
|
||||
.onAppear {
|
||||
contactViewModel.selectedFriendToDelete = contactViewModel.selectedFriend
|
||||
}
|
||||
}
|
||||
|
||||
if isShowDismissPopup {
|
||||
PopupView(sharedMainViewModel: SharedMainViewModel(), isShowPopup: $isShowDismissPopup,
|
||||
title: Text("Don’t save modifications?"),
|
||||
content: Text("All modifications will be canceled."),
|
||||
titleFirstButton: Text("Cancel"),
|
||||
actionFirstButton: {self.isShowDismissPopup.toggle()},
|
||||
titleSecondButton: Text("Ok"),
|
||||
actionSecondButton: {
|
||||
if editContactViewModel.selectedEditFriend == nil {
|
||||
self.isShowDismissPopup.toggle()
|
||||
editContactViewModel.removePopup = true
|
||||
editContactViewModel.resetValues()
|
||||
withAnimation {
|
||||
isShowEditContactFragment.toggle()
|
||||
}
|
||||
} else {
|
||||
self.isShowDismissPopup.toggle()
|
||||
editContactViewModel.resetValues()
|
||||
withAnimation {
|
||||
editContactViewModel.removePopup = true
|
||||
}
|
||||
}
|
||||
})
|
||||
.background(.black.opacity(0.65))
|
||||
.zIndex(3)
|
||||
.onTapGesture {
|
||||
self.isShowDismissPopup.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -462,7 +524,7 @@ struct ContentView: View {
|
|||
}
|
||||
}
|
||||
.onRotate { newOrientation in
|
||||
if (contactViewModel.displayedFriend != nil || !historyViewModel.historyTitle.isEmpty) && searchIsActive {
|
||||
if (contactViewModel.indexDisplayedFriend != nil || !historyViewModel.historyTitle.isEmpty) && searchIsActive {
|
||||
self.focusedField = false
|
||||
} else if searchIsActive {
|
||||
self.focusedField = true
|
||||
|
|
@ -479,5 +541,5 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
ContentView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel())
|
||||
ContentView(contactViewModel: ContactViewModel(), editContactViewModel: EditContactViewModel(), historyViewModel: HistoryViewModel())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,11 +47,9 @@ class SharedMainViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
if preferences.object(forKey: displayProfileModeKey) == nil {
|
||||
print("displayProfileModeKeydisplayProfileModeKey nil")
|
||||
preferences.set(displayProfileMode, forKey: displayProfileModeKey)
|
||||
} else {
|
||||
displayProfileMode = preferences.bool(forKey: displayProfileModeKey)
|
||||
print("displayProfileModeKeydisplayProfileModeKey \(displayProfileMode)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
96
Linphone/Utils/EditContactController.swift
Normal file
96
Linphone/Utils/EditContactController.swift
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import ContactsUI
|
||||
import linphonesw
|
||||
|
||||
struct EditContactView: UIViewControllerRepresentable {
|
||||
|
||||
class Coordinator: NSObject, CNContactViewControllerDelegate, UINavigationControllerDelegate {
|
||||
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
|
||||
if let cnc = contact {
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
|
||||
self.parent.contact = cnc
|
||||
|
||||
let newContact = Contact(
|
||||
identifier: cnc.identifier,
|
||||
firstName: cnc.givenName,
|
||||
lastName: cnc.familyName,
|
||||
organizationName: cnc.organizationName,
|
||||
jobTitle: "",
|
||||
displayName: cnc.nickname,
|
||||
sipAddresses: cnc.instantMessageAddresses.map { $0.value.service == "SIP" ? $0.value.username : "" },
|
||||
phoneNumbers: cnc.phoneNumbers.map { PhoneNumber(numLabel: $0.label ?? "", num: $0.value.stringValue)},
|
||||
imageData: ""
|
||||
)
|
||||
|
||||
let imageThumbnail = UIImage(data: contact!.thumbnailImageData ?? Data())
|
||||
ContactsManager.shared.saveImage(
|
||||
image: imageThumbnail
|
||||
?? ContactsManager.shared.textToImage(
|
||||
firstName: cnc.givenName.isEmpty
|
||||
&& cnc.familyName.isEmpty
|
||||
&& cnc.phoneNumbers.first?.value.stringValue != nil
|
||||
? cnc.phoneNumbers.first!.value.stringValue
|
||||
: cnc.givenName, lastName: cnc.familyName),
|
||||
name: cnc.givenName + cnc.familyName + String(Int.random(in: 1...1000)) + ((imageThumbnail == nil) ? "-default" : ""),
|
||||
contact: newContact,
|
||||
linphoneFriend: false,
|
||||
existingFriend: ContactsManager.shared.getFriend(contact: newContact))
|
||||
|
||||
MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
}
|
||||
}
|
||||
viewController.dismiss(animated: true, completion: {})
|
||||
}
|
||||
|
||||
func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var parent: EditContactView
|
||||
|
||||
init(_ parent: EditContactView) {
|
||||
self.parent = parent
|
||||
}
|
||||
}
|
||||
|
||||
@Binding var contact: CNContact?
|
||||
|
||||
init(contact: Binding<CNContact?>) {
|
||||
self._contact = contact
|
||||
}
|
||||
|
||||
typealias UIViewControllerType = CNContactViewController
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
return Coordinator(self)
|
||||
}
|
||||
|
||||
func makeUIViewController(context: UIViewControllerRepresentableContext<EditContactView>) -> EditContactView.UIViewControllerType {
|
||||
let vcontact = contact != nil ? CNContactViewController(for: contact!) : CNContactViewController(forNewContact: CNContact())
|
||||
vcontact.isEditing = true
|
||||
vcontact.delegate = context.coordinator
|
||||
return vcontact
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: EditContactView.UIViewControllerType, context: UIViewControllerRepresentableContext<EditContactView>) {
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
private var domainDefaultAccount = ""
|
||||
|
||||
private init() {
|
||||
coreContext.doOnCoreQueue{ core in
|
||||
coreContext.doOnCoreQueue { core in
|
||||
self.domainDefaultAccount = core.defaultAccount?.params?.domain ?? ""
|
||||
|
||||
self.magicSearch = try? core.createMagicSearch()
|
||||
|
|
@ -53,7 +53,7 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
}
|
||||
|
||||
func searchForContacts(sourceFlags: Int) {
|
||||
coreContext.doOnCoreQueue{ core in
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
var needResetCache = false
|
||||
|
||||
DispatchQueue.main.sync {
|
||||
|
|
|
|||
85
Linphone/Utils/PhotoPicker.swift
Normal file
85
Linphone/Utils/PhotoPicker.swift
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import PhotosUI
|
||||
|
||||
struct PhotoPicker: UIViewControllerRepresentable {
|
||||
typealias UIViewControllerType = PHPickerViewController
|
||||
|
||||
let filter: PHPickerFilter
|
||||
var limit: Int = 0
|
||||
let onComplete: ([PHPickerResult]) -> Void
|
||||
|
||||
func makeUIViewController(context: Context) -> PHPickerViewController {
|
||||
|
||||
var configuration = PHPickerConfiguration()
|
||||
configuration.filter = filter
|
||||
configuration.selectionLimit = limit
|
||||
|
||||
let controller = PHPickerViewController(configuration: configuration)
|
||||
|
||||
controller.delegate = context.coordinator
|
||||
return controller
|
||||
}
|
||||
|
||||
static func convertToUIImageArray(fromResults results: [PHPickerResult], onComplete: @escaping ([UIImage]?, Error?) -> Void) {
|
||||
var images = [UIImage]()
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
for result in results {
|
||||
dispatchGroup.enter()
|
||||
let itemProvider = result.itemProvider
|
||||
if itemProvider.canLoadObject(ofClass: UIImage.self) {
|
||||
itemProvider.loadObject(ofClass: UIImage.self) { (imageOrNil, errorOrNil) in
|
||||
if let error = errorOrNil {
|
||||
onComplete(nil, error)
|
||||
}
|
||||
if let image = imageOrNil as? UIImage {
|
||||
images.append(image)
|
||||
}
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatchGroup.notify(queue: .main) {
|
||||
onComplete(images, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
return Coordinator(self)
|
||||
}
|
||||
|
||||
class Coordinator: PHPickerViewControllerDelegate {
|
||||
|
||||
private let parent: PhotoPicker
|
||||
|
||||
init(_ parent: PhotoPicker) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||
picker.dismiss(animated: true)
|
||||
parent.onComplete(results)
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Linphone/Utils/ShareSheetController.swift
Normal file
91
Linphone/Utils/ShareSheetController.swift
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import linphonesw
|
||||
|
||||
struct ShareSheet: UIViewControllerRepresentable {
|
||||
typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
|
||||
|
||||
let friendToShare: Friend
|
||||
var activityItems: [Any] = []
|
||||
let applicationActivities: [UIActivity]? = nil
|
||||
let excludedActivityTypes: [UIActivity.ActivityType]? = nil
|
||||
let callback: Callback? = nil
|
||||
|
||||
func makeUIViewController(context: Context) -> UIActivityViewController {
|
||||
let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first
|
||||
|
||||
if directoryURL != nil {
|
||||
if friendToShare.name != nil {
|
||||
let filename = friendToShare.name!.replacingOccurrences(of: " ", with: "")
|
||||
|
||||
let fileURL = directoryURL!
|
||||
.appendingPathComponent(filename)
|
||||
.appendingPathExtension("vcf")
|
||||
|
||||
if friendToShare.vcard != nil {
|
||||
try? friendToShare.vcard!.asVcard4String().write(to: fileURL, atomically: false, encoding: String.Encoding.utf8)
|
||||
|
||||
let controller = UIActivityViewController(
|
||||
activityItems: [fileURL],
|
||||
applicationActivities: applicationActivities
|
||||
)
|
||||
controller.excludedActivityTypes = excludedActivityTypes
|
||||
controller.completionWithItemsHandler = callback
|
||||
return controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let controller = UIActivityViewController(
|
||||
activityItems: activityItems,
|
||||
applicationActivities: applicationActivities)
|
||||
controller.excludedActivityTypes = excludedActivityTypes
|
||||
controller.completionWithItemsHandler = callback
|
||||
return controller
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
func shareContacts(friend: String) {
|
||||
|
||||
let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first
|
||||
|
||||
if directoryURL != nil {
|
||||
let filename = NSUUID().uuidString
|
||||
|
||||
let fileURL = directoryURL!
|
||||
.appendingPathComponent(filename)
|
||||
.appendingPathExtension("vcf")
|
||||
|
||||
try? friend.write(to: fileURL, atomically: false, encoding: String.Encoding.utf8)
|
||||
}
|
||||
|
||||
/*
|
||||
let activityViewController = UIActivityViewController(
|
||||
activityItems: [fileURL],
|
||||
applicationActivities: nil
|
||||
)
|
||||
*/
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue