Changes ContactsManager

This commit is contained in:
benoit.martins 2023-10-26 17:00:54 +02:00
parent 9ef96bbd78
commit 219ee2d438
10 changed files with 643 additions and 617 deletions

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "profil-picture-default.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,18 @@
<svg width="120" height="120" viewBox="0 0 120 120" 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"/>
</g>
<defs>
<filter id="filter0_d_3627_17791" x="0" y="0" width="120" height="120" 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/>
<feGaussianBlur stdDeviation="5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3627_17791"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3627_17791" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -24,213 +24,198 @@ import SwiftUI
final class ContactsManager: ObservableObject {
static let shared = ContactsManager()
private var coreContext = CoreContext.shared
@Published var contacts: [Contact] = []
private var magicSearch = MagicSearchSingleton.shared
private let nativeAddressBookFriendList = "Native address-book"
let linphoneAddressBookFirendList = "Linphone address-book"
@Published var friendList: FriendList?
private init() {
fetchContacts()
}
func fetchContacts() {
contacts.removeAll()
DispatchQueue.global().async {
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, error) in
if let error = error {
print("failed to request access", error)
return
}
if granted {
let keys = [CNContactEmailAddressesKey, CNContactPhoneNumbersKey,
CNContactFamilyNameKey, CNContactGivenNameKey, CNContactNicknameKey,
CNContactPostalAddressesKey, CNContactIdentifierKey,
CNInstantMessageAddressUsernameKey, CNContactInstantMessageAddressesKey,
CNContactImageDataKey, CNContactThumbnailImageDataKey, CNContactOrganizationNameKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
do {
try store.enumerateContacts(with: request, usingBlock: { (contact, _) in
DispatchQueue.main.sync {
self.contacts.append(
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: self.saveImage(
image:
UIImage(data: contact.thumbnailImageData ?? Data())
?? 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.identifier)
)
)
}
self.contacts.sort(by: {
$0.firstName.folding(
options: .diacriticInsensitive, locale: .current
) < $1.firstName.folding(
options: .diacriticInsensitive, locale: .current
)
})
})
} catch let error {
print("Failed to enumerate contact", error)
}
} else {
print("access denied")
}
}
var friends: [Friend] = []
self.contacts.forEach { contact in
do {
let friend = try self.coreContext.mCore.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 = self.coreContext.mCore.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] = []
contact.phoneNumbers.forEach { phone in
do {
if (friendPhoneNumbers.firstIndex(where: {$0.numLabel == phone.numLabel})) == nil {
let phoneNumber = try Factory.Instance.createFriendPhoneNumber(phoneNumber: phone.num, label: phone.numLabel)
friend.addPhoneNumberWithLabel(phoneNumber: phoneNumber)
friendPhoneNumbers.append(phone)
}
} catch let error {
print("Failed to enumerate contact", error)
}
}
let contactImage = contact.imageData.dropFirst(8)
friend.photo = "file:/" + contactImage
friend.done()
friends.append(friend)
} catch let error {
print("Failed to enumerate contact", error)
}
}
if self.coreContext.mCore.globalState == GlobalState.Shutdown || self.coreContext.mCore.globalState == GlobalState.Off {
print("$TAG Core is being stopped or already destroyed, abort")
} else if friends.isEmpty {
print("$TAG No friend created!")
} else {
} else {
print("$TAG ${friends.size} friends created")
let fetchedFriends = friends
let nativeFriendList = self.coreContext.mCore.getFriendListByName(name: self.nativeAddressBookFriendList)
var friendList = nativeFriendList
if friendList == nil {
self.friendList = self.coreContext.mCore.getFriendListByName(name: self.nativeAddressBookFriendList)
if self.friendList == nil {
do {
friendList = try self.coreContext.mCore.createFriendList()
self.friendList = try self.coreContext.mCore.createFriendList()
} catch let error {
print("Failed to enumerate contact", error)
}
}
if friendList!.displayName == nil || friendList!.displayName!.isEmpty {
if self.friendList!.displayName == nil || self.friendList!.displayName!.isEmpty {
print(
"$TAG Friend list [$nativeAddressBookFriendList] didn't exist yet, let's create it"
)
friendList?.databaseStorageEnabled = false // We don't want to store local address-book in DB
self.friendList!.databaseStorageEnabled = false // We don't want to store local address-book in DB
friendList!.displayName = self.nativeAddressBookFriendList
self.coreContext.mCore.addFriendList(list: friendList!)
self.friendList!.displayName = self.nativeAddressBookFriendList
self.coreContext.mCore.addFriendList(list: self.friendList!)
} else {
print(
"$TAG Friend list [$LINPHONE_ADDRESS_BOOK_FRIEND_LIST] found, removing existing friends if any"
)
friendList!.friends.forEach { friend in
_ = friendList!.removeFriend(linphoneFriend: friend)
self.friendList!.friends.forEach { friend in
_ = self.friendList!.removeFriend(linphoneFriend: friend)
}
}
fetchedFriends.forEach { friend in
_ = friendList!.addLocalFriend(linphoneFriend: friend)
}
friends.removeAll()
print("$TAG Friends added")
friendList!.updateSubscriptions()
print("$TAG Subscription(s) updated")
}
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, error) in
if let error = error {
print("failed to request access", error)
return
}
if granted {
let keys = [CNContactEmailAddressesKey, CNContactPhoneNumbersKey,
CNContactFamilyNameKey, CNContactGivenNameKey, CNContactNicknameKey,
CNContactPostalAddressesKey, CNContactIdentifierKey,
CNInstantMessageAddressUsernameKey, CNContactInstantMessageAddressesKey,
CNContactImageDataKey, CNContactThumbnailImageDataKey, CNContactOrganizationNameKey]
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: ""
)
self.saveImage(
image:
UIImage(data: contact.thumbnailImageData ?? Data())
?? 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)
}
})
} catch let error {
print("Failed to enumerate contact", error)
}
} else {
print("access denied")
}
}
self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
}
}
func textToImage(firstName: String, lastName: String) -> UIImage {
let lblNameInitialize = UILabel()
lblNameInitialize.frame.size = CGSize(width: 100.0, height: 100.0)
lblNameInitialize.font = UIFont(name: "NotoSans-ExtraBold", size: 40)
lblNameInitialize.textColor = UIColor(Color.grayMain2c600)
var textToDisplay = ""
if firstName.first != nil {
textToDisplay += String(firstName.first!)
}
if lastName.first != nil {
textToDisplay += String(lastName.first!)
}
lblNameInitialize.text = textToDisplay.uppercased()
lblNameInitialize.textAlignment = .center
lblNameInitialize.backgroundColor = UIColor(Color.grayMain2c200)
lblNameInitialize.layer.cornerRadius = 10.0
var IBImgViewUserProfile = UIImage()
UIGraphicsBeginImageContext(lblNameInitialize.frame.size)
lblNameInitialize.layer.render(in: UIGraphicsGetCurrentContext()!)
IBImgViewUserProfile = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return IBImgViewUserProfile
}
func saveImage(image: UIImage, name: String) -> String {
func saveImage(image: UIImage, name: String, contact: Contact) {
guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else {
return ""
}
let directory = FileManager.default.temporaryDirectory
print("FileManagerFileManager \(directory.absoluteString)")
do {
try data.write(to: directory.appendingPathComponent(name + ".png"))
return directory.appendingPathComponent(name + ".png").absoluteString
} catch {
print(error.localizedDescription)
return ""
return
}
awaitDataWrite(data: data, name: name) { _, result in
do {
let friend = try self.coreContext.mCore.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 = self.coreContext.mCore.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] = []
contact.phoneNumbers.forEach { phone in
do {
if (friendPhoneNumbers.firstIndex(where: {$0.numLabel == phone.numLabel})) == nil {
let phoneNumber = try Factory.Instance.createFriendPhoneNumber(phoneNumber: phone.num, label: phone.numLabel)
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.done()
_ = self.friendList!.addLocalFriend(linphoneFriend: friend)
self.friendList!.updateSubscriptions()
} catch let error {
print("Failed to enumerate contact", error)
}
}
}
func textToImage(firstName: String, lastName: String) -> UIImage {
let lblNameInitialize = UILabel()
lblNameInitialize.frame.size = CGSize(width: 100.0, height: 100.0)
lblNameInitialize.font = UIFont(name: "NotoSans-ExtraBold", size: 40)
lblNameInitialize.textColor = UIColor(Color.grayMain2c600)
var textToDisplay = ""
if firstName.first != nil {
textToDisplay += String(firstName.first!)
}
if lastName.first != nil {
textToDisplay += String(lastName.first!)
}
lblNameInitialize.text = textToDisplay.uppercased()
lblNameInitialize.textAlignment = .center
lblNameInitialize.backgroundColor = UIColor(Color.grayMain2c200)
lblNameInitialize.layer.cornerRadius = 10.0
var IBImgViewUserProfile = UIImage()
UIGraphicsBeginImageContext(lblNameInitialize.frame.size)
lblNameInitialize.layer.render(in: UIGraphicsGetCurrentContext()!)
IBImgViewUserProfile = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return IBImgViewUserProfile
}
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
}
}
}
}
struct PhoneNumber {

View file

@ -23,13 +23,22 @@ import SwiftUI
struct LinphoneApp: App {
@ObservedObject private var coreContext = CoreContext.shared
@ObservedObject private var sharedMainViewModel = SharedMainViewModel()
@State private var isActive = false
var body: some Scene {
WindowGroup {
if isActive {
ContentView(sharedMainViewModel: SharedMainViewModel(), contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel())
.toast(isShowing: $coreContext.toastMessage)
if !sharedMainViewModel.welcomeViewDisplayed {
WelcomeView(sharedMainViewModel: sharedMainViewModel)
} else if coreContext.mCore.defaultAccount == nil || sharedMainViewModel.displayProfileMode {
AssistantView(sharedMainViewModel: sharedMainViewModel)
.toast(isShowing: $coreContext.toastMessage)
} else {
ContentView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel())
.toast(isShowing: $coreContext.toastMessage)
}
} else {
SplashScreen(isActive: $isActive)
}

View file

@ -21,6 +21,7 @@ import SwiftUI
struct ContactsFragment: View {
@ObservedObject var magicSearch = MagicSearchSingleton.shared
@ObservedObject var contactViewModel: ContactViewModel
@State private var orientation = UIDevice.current.orientation
@ -29,42 +30,43 @@ struct ContactsFragment: View {
var body: some View {
VStack(alignment: .leading) {
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 !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())
.zIndex(-1)
.transition(.move(edge: .top))
}
HStack(alignment: .center) {
Text("All contacts")
.default_text_style_800(styleSize: 16)
Spacer()
if isFavoriteOpen {
FavoriteContactsListFragment(contactViewModel: contactViewModel, favoriteContactsListViewModel: FavoriteContactsListViewModel())
.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)
}
.padding(.top, 10)
.padding(.horizontal, 16)
ContactsListFragment(contactViewModel: contactViewModel, contactsListViewModel: ContactsListViewModel())
}
.navigationBarHidden(true)

View file

@ -64,7 +64,7 @@ struct ContactsListFragment: View {
.frame(width: 45, height: 45)
.clipShape(Circle())
case .failure:
Image("profile-image-example")
Image("profil-picture-default")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
@ -73,7 +73,7 @@ struct ContactsListFragment: View {
}
}
} else {
Image("profile-image-example")
Image("profil-picture-default")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())

View file

@ -29,7 +29,7 @@ struct FavoriteContactsListFragment: View {
var body: some View {
ScrollView(.horizontal) {
HStack {
ForEach(0..<magicSearch.lastSearch.count, id: \.self) { index in
ForEach(0..<magicSearch.lastSearch.count, id: \.self) { index in
Button {
withAnimation {
contactViewModel.contactTitle = (magicSearch.lastSearch[index].friend?.name)!
@ -48,7 +48,7 @@ struct FavoriteContactsListFragment: View {
.frame(width: 45, height: 45)
.clipShape(Circle())
case .failure:
Image("profile-image-example")
Image("profil-picture-default")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
@ -57,7 +57,7 @@ struct FavoriteContactsListFragment: View {
}
}
} else {
Image("profile-image-example")
Image("profil-picture-default")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
@ -67,7 +67,7 @@ struct FavoriteContactsListFragment: View {
.frame( maxWidth: .infinity, alignment: .center)
}
}
.frame(maxWidth: 70)
.frame(minWidth: 70, maxWidth: 70)
}
}
.padding(.all, 10)

View file

@ -23,5 +23,9 @@ class ContactViewModel: ObservableObject {
@Published var contactTitle: String = ""
init() {}
private var magicSearch = MagicSearchSingleton.shared
init() {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)}
}

View file

@ -21,11 +21,5 @@ import linphonesw
class ContactsListViewModel: ObservableObject {
private var magicSearch = MagicSearchSingleton.shared
private var coreContext = CoreContext.shared
init() {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
}
init() {}
}

View file

@ -23,418 +23,411 @@ import linphonesw
struct ContentView: View {
var contactManager = ContactsManager.shared
var magicSearch = MagicSearchSingleton.shared
@ObservedObject var sharedMainViewModel: SharedMainViewModel
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var historyViewModel: HistoryViewModel
@ObservedObject private var coreContext = CoreContext.shared
@State var index = 0
@State private var orientation = UIDevice.current.orientation
@State var sideMenuIsOpen: Bool = false
@State private var searchIsActive = false
@State private var text = ""
@FocusState private var focusedField: Bool
var magicSearch = MagicSearchSingleton.shared
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var historyViewModel: HistoryViewModel
@ObservedObject private var coreContext = CoreContext.shared
@State var index = 0
@State private var orientation = UIDevice.current.orientation
@State var sideMenuIsOpen: Bool = false
@State private var searchIsActive = false
@State private var text = ""
@FocusState private var focusedField: Bool
@State var isMenuOpen: Bool = false
var body: some View {
if !sharedMainViewModel.welcomeViewDisplayed {
WelcomeView(sharedMainViewModel: sharedMainViewModel)
} else if coreContext.mCore.defaultAccount == nil || sharedMainViewModel.displayProfileMode {
AssistantView(sharedMainViewModel: sharedMainViewModel)
} else {
GeometryReader { geometry in
ZStack {
VStack(spacing: 0) {
HStack(spacing: 0) {
if orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height {
VStack {
Group {
Spacer()
Button(action: {
self.index = 0
}, label: {
VStack {
Image("address-book")
.renderingMode(.template)
.resizable()
.foregroundStyle(self.index == 0 ? Color.orangeMain500 : Color.grayMain2c600)
.frame(width: 25, height: 25)
if self.index == 0 {
Text("Contacts")
.default_text_style_700(styleSize: 10)
} else {
Text("Contacts")
.default_text_style(styleSize: 10)
}
}
})
Spacer()
Button(action: {
self.index = 1
contactViewModel.contactTitle = ""
}, label: {
VStack {
Image("phone")
.renderingMode(.template)
.resizable()
.foregroundStyle(self.index == 1 ? Color.orangeMain500 : Color.grayMain2c600)
.frame(width: 25, height: 25)
if self.index == 1 {
Text("Calls")
.default_text_style_700(styleSize: 10)
} else {
Text("Calls")
.default_text_style(styleSize: 10)
}
}
})
Spacer()
}
}
.frame(width: 75)
.padding(.leading,
orientation == .landscapeRight && geometry.safeAreaInsets.bottom > 0
? -geometry.safeAreaInsets.leading
: 0)
}
VStack(spacing: 0) {
if searchIsActive == false {
HStack {
Image("profile-image-example")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
.onTapGesture {
openMenu()
}
Text(index == 0 ? "Contacts" : "Calls")
.default_text_style_white_800(styleSize: 20)
.padding(.leading, 10)
Spacer()
Button {
withAnimation {
searchIsActive.toggle()
}
} label: {
Image("search")
}
Menu {
Button {
isMenuOpen = false
magicSearch.allContact = true
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
} label: {
HStack {
Text("See all")
Spacer()
if magicSearch.allContact {
Image("green-check")
}
}
var body: some View {
GeometryReader { geometry in
ZStack {
VStack(spacing: 0) {
HStack(spacing: 0) {
if orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height {
VStack {
Group {
Spacer()
Button(action: {
self.index = 0
}, label: {
VStack {
Image("address-book")
.renderingMode(.template)
.resizable()
.foregroundStyle(self.index == 0 ? Color.orangeMain500 : Color.grayMain2c600)
.frame(width: 25, height: 25)
if self.index == 0 {
Text("Contacts")
.default_text_style_700(styleSize: 10)
} else {
Text("Contacts")
.default_text_style(styleSize: 10)
}
Button {
isMenuOpen = false
magicSearch.allContact = false
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
} label: {
HStack {
Text("See Linphone contact")
Spacer()
if !magicSearch.allContact {
Image("green-check")
}
}
}
} label: {
Image(index == 0 ? "filtres" : "more")
}
.padding(.leading)
.onTapGesture {
isMenuOpen = true
}
}
.frame(maxWidth: .infinity)
.frame(height: 50)
.padding(.horizontal)
.padding(.bottom, 5)
.background(Color.orangeMain500)
} else {
HStack {
Button {
withAnimation {
self.focusedField = false
searchIsActive.toggle()
}
text = ""
magicSearch.currentFilter = ""
})
Spacer()
Button(action: {
self.index = 1
contactViewModel.contactTitle = ""
}, label: {
VStack {
Image("phone")
.renderingMode(.template)
.resizable()
.foregroundStyle(self.index == 1 ? Color.orangeMain500 : Color.grayMain2c600)
.frame(width: 25, height: 25)
if self.index == 1 {
Text("Calls")
.default_text_style_700(styleSize: 10)
} else {
Text("Calls")
.default_text_style(styleSize: 10)
}
}
})
Spacer()
}
}
.frame(width: 75)
.padding(.leading,
orientation == .landscapeRight && geometry.safeAreaInsets.bottom > 0
? -geometry.safeAreaInsets.leading
: 0)
}
VStack(spacing: 0) {
if searchIsActive == false {
HStack {
Image("profile-image-example")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
.onTapGesture {
openMenu()
}
Text(index == 0 ? "Contacts" : "Calls")
.default_text_style_white_800(styleSize: 20)
.padding(.leading, 10)
Spacer()
Button {
withAnimation {
searchIsActive.toggle()
}
} label: {
Image("search")
}
Menu {
Button {
isMenuOpen = false
magicSearch.allContact = true
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
} label: {
Image("caret-left")
.renderingMode(.template)
.resizable()
.foregroundStyle(.white)
.frame(width: 25, height: 25, alignment: .leading)
}
if #available(iOS 16.0, *) {
TextEditor(text: Binding(
get: {
return text
},
set: { value in
var newValue = value
if value.contains("\n") {
newValue = value.replacingOccurrences(of: "\n", with: "")
}
text = newValue
}
))
.default_text_style_white_700(styleSize: 15)
.padding(.all, 6)
.accentColor(.white)
.scrollContentBackground(.hidden)
.focused($focusedField)
.onAppear {
self.focusedField = true
}
.onChange(of: text) { newValue in
magicSearch.currentFilter = newValue
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
}
} else {
TextEditor(text: Binding(
get: {
return text
},
set: { value in
var newValue = value
if value.contains("\n") {
newValue = value.replacingOccurrences(of: "\n", with: "")
}
text = newValue
}
))
.default_text_style_white_700(styleSize: 15)
.padding(.all, 6)
.accentColor(.white)
.focused($focusedField)
.onAppear {
self.focusedField = true
}
.onChange(of: text) { newValue in
magicSearch.currentFilter = newValue
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
}
}
Button {
text = ""
} label: {
Image("x")
.renderingMode(.template)
.resizable()
.foregroundStyle(.white)
.frame(width: 25, height: 25, alignment: .leading)
}
.padding(.leading)
}
.frame(maxWidth: .infinity)
.frame(height: 50)
.padding(.horizontal)
.padding(.bottom, 5)
.background(Color.orangeMain500)
}
if self.index == 0 {
ContactsView(contactViewModel: contactViewModel, historyViewModel: historyViewModel)
} else if self.index == 1 {
HistoryView()
}
}
.frame(maxWidth:
(orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
? geometry.size.width/100*40
: .infinity
)
.background(
Color.white
.shadow(color: Color.gray200, radius: 4, x: 0, y: 0)
.mask(Rectangle().padding(.horizontal, -8))
)
if orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height {
Spacer()
}
}
if !(orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) && !searchIsActive {
HStack {
Group {
Spacer()
Button(action: {
self.index = 0
}, label: {
VStack {
Image("address-book")
.renderingMode(.template)
.resizable()
.foregroundStyle(self.index == 0 ? Color.orangeMain500 : Color.grayMain2c600)
.frame(width: 25, height: 25)
if self.index == 0 {
Text("Contacts")
.default_text_style_700(styleSize: 10)
} else {
Text("Contacts")
.default_text_style(styleSize: 10)
}
}
})
.padding(.top)
Spacer()
Button(action: {
self.index = 1
contactViewModel.contactTitle = ""
}, label: {
VStack {
Image("phone")
.renderingMode(.template)
.resizable()
.foregroundStyle(self.index == 1 ? Color.orangeMain500 : Color.grayMain2c600)
.frame(width: 25, height: 25)
if self.index == 1 {
Text("Calls")
.default_text_style_700(styleSize: 10)
} else {
Text("Calls")
.default_text_style(styleSize: 10)
}
}
})
.padding(.top)
Spacer()
}
}
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 15)
.background(
Color.white
.shadow(color: Color.gray200, radius: 4, x: 0, y: 0)
.mask(Rectangle().padding(.top, -8))
)
}
}
if !contactViewModel.contactTitle.isEmpty || !historyViewModel.historyTitle.isEmpty {
HStack(spacing: 0) {
Spacer()
.frame(maxWidth:
(orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
? (geometry.size.width/100*40) + 75
: 0
)
if self.index == 0 {
ContactFragment(contactViewModel: contactViewModel)
.frame(maxWidth: .infinity)
.background(Color.gray100)
.ignoresSafeArea(.keyboard)
} else if self.index == 1 {
HistoryContactFragment()
.frame(maxWidth: .infinity)
.background(Color.gray100)
.ignoresSafeArea(.keyboard)
}
}
.onAppear {
if !(orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
&& searchIsActive {
self.focusedField = false
}
}
.onDisappear {
if !(orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
&& searchIsActive {
self.focusedField = true
}
}
.padding(.leading,
orientation == .landscapeRight && geometry.safeAreaInsets.bottom > 0
? -geometry.safeAreaInsets.leading
: 0)
.transition(.move(edge: .trailing))
.zIndex(1)
}
SideMenu(
width: geometry.size.width / 5 * 4,
isOpen: self.sideMenuIsOpen,
menuClose: self.openMenu,
safeAreaInsets: geometry.safeAreaInsets
)
.ignoresSafeArea(.all)
.zIndex(2)
}
}
.overlay {
if isMenuOpen {
Color.white.opacity(0.001)
} label: {
HStack {
Text("See all")
Spacer()
if magicSearch.allContact {
Image("green-check")
}
}
}
Button {
isMenuOpen = false
magicSearch.allContact = false
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
} label: {
HStack {
Text("See Linphone contact")
Spacer()
if !magicSearch.allContact {
Image("green-check")
}
}
}
} label: {
Image(index == 0 ? "filtres" : "more")
}
.padding(.leading)
.onTapGesture {
isMenuOpen = true
}
}
.frame(maxWidth: .infinity)
.frame(height: 50)
.padding(.horizontal)
.padding(.bottom, 5)
.background(Color.orangeMain500)
} else {
HStack {
Button {
withAnimation {
self.focusedField = false
searchIsActive.toggle()
}
text = ""
magicSearch.currentFilter = ""
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
} label: {
Image("caret-left")
.renderingMode(.template)
.resizable()
.foregroundStyle(.white)
.frame(width: 25, height: 25, alignment: .leading)
}
if #available(iOS 16.0, *) {
TextEditor(text: Binding(
get: {
return text
},
set: { value in
var newValue = value
if value.contains("\n") {
newValue = value.replacingOccurrences(of: "\n", with: "")
}
text = newValue
}
))
.default_text_style_white_700(styleSize: 15)
.padding(.all, 6)
.accentColor(.white)
.scrollContentBackground(.hidden)
.focused($focusedField)
.onAppear {
self.focusedField = true
}
.onChange(of: text) { newValue in
magicSearch.currentFilter = newValue
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
}
} else {
TextEditor(text: Binding(
get: {
return text
},
set: { value in
var newValue = value
if value.contains("\n") {
newValue = value.replacingOccurrences(of: "\n", with: "")
}
text = newValue
}
))
.default_text_style_white_700(styleSize: 15)
.padding(.all, 6)
.accentColor(.white)
.focused($focusedField)
.onAppear {
self.focusedField = true
}
.onChange(of: text) { newValue in
magicSearch.currentFilter = newValue
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
}
}
Button {
text = ""
} label: {
Image("x")
.renderingMode(.template)
.resizable()
.foregroundStyle(.white)
.frame(width: 25, height: 25, alignment: .leading)
}
.padding(.leading)
}
.frame(maxWidth: .infinity)
.frame(height: 50)
.padding(.horizontal)
.padding(.bottom, 5)
.background(Color.orangeMain500)
}
if self.index == 0 {
ContactsView(contactViewModel: contactViewModel, historyViewModel: historyViewModel)
} else if self.index == 1 {
HistoryView()
}
}
.frame(maxWidth:
(orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
? geometry.size.width/100*40
: .infinity
)
.background(
Color.white
.shadow(color: Color.gray200, radius: 4, x: 0, y: 0)
.mask(Rectangle().padding(.horizontal, -8))
)
if orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height {
Spacer()
}
}
if !(orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) && !searchIsActive {
HStack {
Group {
Spacer()
Button(action: {
self.index = 0
}, label: {
VStack {
Image("address-book")
.renderingMode(.template)
.resizable()
.foregroundStyle(self.index == 0 ? Color.orangeMain500 : Color.grayMain2c600)
.frame(width: 25, height: 25)
if self.index == 0 {
Text("Contacts")
.default_text_style_700(styleSize: 10)
} else {
Text("Contacts")
.default_text_style(styleSize: 10)
}
}
})
.padding(.top)
Spacer()
Button(action: {
self.index = 1
contactViewModel.contactTitle = ""
}, label: {
VStack {
Image("phone")
.renderingMode(.template)
.resizable()
.foregroundStyle(self.index == 1 ? Color.orangeMain500 : Color.grayMain2c600)
.frame(width: 25, height: 25)
if self.index == 1 {
Text("Calls")
.default_text_style_700(styleSize: 10)
} else {
Text("Calls")
.default_text_style(styleSize: 10)
}
}
})
.padding(.top)
Spacer()
}
}
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 15)
.background(
Color.white
.shadow(color: Color.gray200, radius: 4, x: 0, y: 0)
.mask(Rectangle().padding(.top, -8))
)
}
}
if !contactViewModel.contactTitle.isEmpty || !historyViewModel.historyTitle.isEmpty {
HStack(spacing: 0) {
Spacer()
.frame(maxWidth:
(orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
? (geometry.size.width/100*40) + 75
: 0
)
if self.index == 0 {
ContactFragment(contactViewModel: contactViewModel)
.frame(maxWidth: .infinity)
.background(Color.gray100)
.ignoresSafeArea(.keyboard)
} else if self.index == 1 {
HistoryContactFragment()
.frame(maxWidth: .infinity)
.background(Color.gray100)
.ignoresSafeArea(.keyboard)
}
}
.onAppear {
if !(orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
&& searchIsActive {
self.focusedField = false
}
}
.onDisappear {
if !(orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
&& searchIsActive {
self.focusedField = true
}
}
.padding(.leading,
orientation == .landscapeRight && geometry.safeAreaInsets.bottom > 0
? -geometry.safeAreaInsets.leading
: 0)
.transition(.move(edge: .trailing))
.zIndex(1)
}
SideMenu(
width: geometry.size.width / 5 * 4,
isOpen: self.sideMenuIsOpen,
menuClose: self.openMenu,
safeAreaInsets: geometry.safeAreaInsets
)
.ignoresSafeArea(.all)
.zIndex(2)
}
}
.overlay {
if isMenuOpen {
Color.white.opacity(0.001)
.ignoresSafeArea()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onTapGesture {
isMenuOpen = false
}
}
}
.onRotate { newOrientation in
if (!contactViewModel.contactTitle.isEmpty || !historyViewModel.historyTitle.isEmpty) && searchIsActive {
self.focusedField = false
} else if searchIsActive {
self.focusedField = true
}
orientation = newOrientation
}
}
}
func openMenu() {
withAnimation {
self.sideMenuIsOpen.toggle()
}
}
}
.onRotate { newOrientation in
if (!contactViewModel.contactTitle.isEmpty || !historyViewModel.historyTitle.isEmpty) && searchIsActive {
self.focusedField = false
} else if searchIsActive {
self.focusedField = true
}
orientation = newOrientation
}
}
func openMenu() {
withAnimation {
self.sideMenuIsOpen.toggle()
}
}
}
#Preview {
ContentView(sharedMainViewModel: SharedMainViewModel(), contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel())
ContentView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel())
}