Add contact fragment (detail)

This commit is contained in:
Benoit Martins 2023-10-27 17:37:20 +02:00
parent 5dc38b6c91
commit 2292512e4d
29 changed files with 1465 additions and 521 deletions

View file

@ -45,6 +45,8 @@
D7A03FC02ACC2E390081A588 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A03FBF2ACC2E390081A588 /* HistoryView.swift */; };
D7A03FC62ACC458A0081A588 /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A03FC52ACC458A0081A588 /* SplashScreen.swift */; };
D7A2EDD62AC18115005D90FC /* SharedMainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A2EDD52AC18115005D90FC /* SharedMainViewModel.swift */; };
D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */; };
D7C365082AEFAB7F00FE6142 /* ContactListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C365072AEFAB7F00FE6142 /* ContactListBottomSheet.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 */; };
@ -106,6 +108,8 @@
D7A03FC52ACC458A0081A588 /* SplashScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashScreen.swift; sourceTree = "<group>"; };
D7A2EDD52AC18115005D90FC /* SharedMainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedMainViewModel.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
@ -325,6 +329,8 @@
D7E6D0482AE933AD00A57AAF /* FavoriteContactsListFragment.swift */,
D7E6D0502AEBDBD500A57AAF /* ContactsListBottomSheet.swift */,
D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */,
D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */,
D7C365072AEFAB7F00FE6142 /* ContactListBottomSheet.swift */,
);
path = Fragments;
sourceTree = "<group>";
@ -548,6 +554,7 @@
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 */,
D74C9D012ACB098C0021626A /* PermissionManager.swift in Sources */,
D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */,
@ -575,6 +582,7 @@
D7DA67642ACCB31700E95002 /* ProfileModeFragment.swift in Sources */,
D74C9CFC2ACACF370021626A /* WelcomePage3Fragment.swift in Sources */,
D719ABCC2ABC769C00B41C10 /* AssistantView.swift in Sources */,
D7C365082AEFAB7F00FE6142 /* ContactListBottomSheet.swift in Sources */,
D7E6D04B2AE9347D00A57AAF /* FavoriteContactsListViewModel.swift in Sources */,
D74C9CFA2ACACF2D0021626A /* WelcomePage2Fragment.swift in Sources */,
D74C9CFF2ACAEC5E0021626A /* PopupView.swift in Sources */,

View file

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

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.87389 3.92007C5.8138 3.85155 5.74087 3.79578 5.65931 3.75599C5.57775 3.7162 5.4892 3.69317 5.39878 3.68825C5.30836 3.68333 5.21788 3.69661 5.13257 3.72733C5.04726 3.75804 4.96883 3.80557 4.90182 3.86717C4.83481 3.92877 4.78055 4.00321 4.74219 4.08617C4.70383 4.16914 4.68213 4.25898 4.67835 4.35049C4.67457 4.442 4.68879 4.53335 4.72017 4.61927C4.75155 4.70518 4.79948 4.78394 4.86118 4.85097L6.29301 6.44459C5.68492 7.50032 5.3655 8.70069 5.36754 9.92252C5.36754 12.98 4.66103 15.3194 4.18633 16.1465C4.0651 16.3567 4.00083 16.5956 4.00001 16.839C3.99918 17.0824 4.06183 17.3218 4.18162 17.5329C4.30142 17.744 4.47413 17.9194 4.68234 18.0414C4.89055 18.1635 5.12689 18.2279 5.36754 18.228H8.85729C9.01434 19.0103 9.43394 19.7137 10.045 20.2188C10.6561 20.724 11.421 21 12.2102 21C12.9994 21 13.7643 20.724 14.3754 20.2188C14.9864 19.7137 15.406 19.0103 15.5631 18.228H16.8837L18.5465 20.0777C18.6066 20.1462 18.6795 20.202 18.7611 20.2418C18.8426 20.2816 18.9312 20.3046 19.0216 20.3095C19.112 20.3145 19.2025 20.3012 19.2878 20.2705C19.3731 20.2398 19.4515 20.1922 19.5186 20.1306C19.5856 20.069 19.6398 19.9946 19.6782 19.9116C19.7165 19.8287 19.7382 19.7388 19.742 19.6473C19.7458 19.5558 19.7316 19.4644 19.7002 19.3785C19.6688 19.2926 19.6209 19.2139 19.5592 19.1468L5.87389 3.92007ZM12.2102 19.6123C11.7858 19.6121 11.3719 19.479 11.0254 19.2311C10.6789 18.9832 10.4169 18.6328 10.2754 18.228H14.1449C14.0035 18.6328 13.7415 18.9832 13.395 19.2311C13.0485 19.479 12.6346 19.6121 12.2102 19.6123ZM5.36754 16.8438C6.02614 15.6983 6.73607 13.044 6.73607 9.92252C6.73487 9.09544 6.91788 8.2787 7.27151 7.53296L15.6392 16.8438H5.36754ZM19.566 16.4346C19.4861 16.4659 19.4011 16.482 19.3154 16.4821C19.178 16.482 19.0438 16.44 18.9303 16.3616C18.8168 16.2832 18.7292 16.1721 18.6791 16.0426C18.0564 14.4378 17.6843 12.1503 17.6843 9.92252C17.6846 8.95407 17.4337 8.00249 16.9568 7.16275C16.4798 6.32301 15.7935 5.62449 14.9662 5.1369C14.139 4.64932 13.1998 4.38972 12.2424 4.38403C11.2849 4.37834 10.3428 4.62675 9.50991 5.10447C9.35242 5.19078 9.16776 5.21119 8.99557 5.16132C8.82337 5.11145 8.67738 4.99527 8.58892 4.83773C8.50046 4.6802 8.47661 4.49386 8.52246 4.31872C8.56832 4.14359 8.68024 3.99363 8.83419 3.90103C9.8752 3.30377 11.0529 2.99313 12.2497 3.00012C13.4465 3.0071 14.6205 3.33147 15.6546 3.94084C16.6888 4.55022 17.5468 5.42328 18.1431 6.4729C18.7394 7.52251 19.0531 8.71196 19.0528 9.92252C19.0528 12.9809 19.7414 14.9915 19.9526 15.5357C20.019 15.7065 20.0155 15.897 19.943 16.0652C19.8705 16.2335 19.7349 16.3657 19.566 16.4328V16.4346Z" fill="#4E6074"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

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

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M168,112a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,112Zm-8,24H96a8,8,0,0,0,0,16h64a8,8,0,0,0,0-16Zm72-12A100.11,100.11,0,0,1,132,224H47.67A15.69,15.69,0,0,1,32,208.33V124a100,100,0,0,1,200,0Zm-16,0a84,84,0,0,0-168,0v84h84A84.09,84.09,0,0,0,216,124Z"></path></svg>

After

Width:  |  Height:  |  Size: 379 B

View file

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

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"></path></svg>

After

Width:  |  Height:  |  Size: 303 B

View file

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

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.6212 9.46731L13.0875 12L15.6212 14.5327C15.6926 14.6042 15.7493 14.689 15.788 14.7824C15.8267 14.8758 15.8466 14.9758 15.8466 15.0769C15.8466 15.178 15.8267 15.2781 15.788 15.3715C15.7493 15.4648 15.6926 15.5497 15.6212 15.6212C15.5497 15.6926 15.4648 15.7493 15.3715 15.788C15.2781 15.8267 15.178 15.8466 15.0769 15.8466C14.9759 15.8466 14.8758 15.8267 14.7824 15.788C14.689 15.7493 14.6042 15.6926 14.5327 15.6212L12 13.0875L9.46731 15.6212C9.39584 15.6926 9.31099 15.7493 9.21762 15.788C9.12424 15.8267 9.02415 15.8466 8.92308 15.8466C8.82201 15.8466 8.72192 15.8267 8.62854 15.788C8.53516 15.7493 8.45032 15.6926 8.37885 15.6212C8.30738 15.5497 8.25069 15.4648 8.21201 15.3715C8.17333 15.2781 8.15342 15.178 8.15342 15.0769C8.15342 14.9758 8.17333 14.8758 8.21201 14.7824C8.25069 14.689 8.30738 14.6042 8.37885 14.5327L10.9125 12L8.37885 9.46731C8.23451 9.32297 8.15342 9.1272 8.15342 8.92308C8.15342 8.71895 8.23451 8.52318 8.37885 8.37884C8.52319 8.23451 8.71895 8.15342 8.92308 8.15342C9.12721 8.15342 9.32297 8.23451 9.46731 8.37884L12 10.9125L14.5327 8.37884C14.6042 8.30737 14.689 8.25068 14.7824 8.212C14.8758 8.17332 14.9759 8.15342 15.0769 8.15342C15.178 8.15342 15.2781 8.17332 15.3715 8.212C15.4648 8.25068 15.5497 8.30737 15.6212 8.37884C15.6926 8.45031 15.7493 8.53516 15.788 8.62854C15.8267 8.72192 15.8466 8.822 15.8466 8.92308C15.8466 9.02415 15.8267 9.12423 15.788 9.21761C15.7493 9.31099 15.6926 9.39584 15.6212 9.46731ZM22 12C22 13.9778 21.4135 15.9112 20.3147 17.5557C19.2159 19.2002 17.6541 20.4819 15.8268 21.2388C13.9996 21.9957 11.9889 22.1937 10.0491 21.8078C8.10929 21.422 6.32746 20.4696 4.92894 19.0711C3.53041 17.6725 2.578 15.8907 2.19215 13.9509C1.8063 12.0111 2.00433 10.0004 2.76121 8.17316C3.51809 6.3459 4.79981 4.78412 6.4443 3.6853C8.08879 2.58649 10.0222 2 12 2C14.6513 2.0028 17.1932 3.05727 19.068 4.93202C20.9427 6.80678 21.9972 9.34869 22 12ZM20.4615 12C20.4615 10.3265 19.9653 8.69051 19.0355 7.29902C18.1057 5.90753 16.7842 4.82299 15.2381 4.18256C13.6919 3.54212 11.9906 3.37456 10.3492 3.70105C8.70786 4.02754 7.20016 4.83342 6.01679 6.01679C4.83343 7.20016 4.02754 8.70786 3.70105 10.3492C3.37456 11.9906 3.54213 13.6919 4.18256 15.2381C4.823 16.7842 5.90753 18.1057 7.29902 19.0355C8.69052 19.9653 10.3265 20.4615 12 20.4615C14.2434 20.459 16.3941 19.5667 17.9804 17.9804C19.5667 16.3941 20.459 14.2434 20.4615 12Z" fill="#4E6074"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

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

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M228.44,89.34l-96-64a8,8,0,0,0-8.88,0l-96,64A8,8,0,0,0,24,96V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V96A8,8,0,0,0,228.44,89.34ZM128,41.61l81.91,54.61-67,47.78H113.11l-67-47.78ZM40,200V111.53l65.9,47a8,8,0,0,0,4.65,1.49h34.9a8,8,0,0,0,4.65-1.49l65.9-47V200Z"></path></svg>

After

Width:  |  Height:  |  Size: 384 B

View file

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

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z"></path></svg>

After

Width:  |  Height:  |  Size: 372 B

View file

@ -179,7 +179,8 @@ final class ContactsManager: ObservableObject {
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)
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)
}
@ -190,6 +191,8 @@ final class ContactsManager: ObservableObject {
let contactImage = result.dropFirst(8)
friend.photo = "file:/" + contactImage
friend.organization = contact.organizationName
friend.done()

View file

@ -27,6 +27,9 @@
},
"**Camera** : Pour capturer votre vidéo lors des appels vidéo et conférence." : {
},
"**Company :** %@" : {
},
"**Contacts** : Pour vous afficher vos contacts et retrouver qui utilise Linphone." : {
@ -98,6 +101,9 @@
},
"All contacts" : {
},
"Appel" : {
},
"assistant_account_login" : {
"extractionState" : "manual",
@ -115,9 +121,21 @@
}
}
}
},
"Block" : {
},
"Block the address" : {
},
"Block the number" : {
},
"Calls" : {
},
"Cancel" : {
},
"Ce mode vous permet dêtre interopérable avec dautres services SIP.\nVos communications seront chiffrées de point à point. " : {
@ -136,6 +154,12 @@
},
"Continue" : {
},
"Copy address" : {
},
"Copy number" : {
},
"D'accord" : {
@ -148,6 +172,12 @@
},
"Delete" : {
},
"Delete %@?" : {
},
"Delete this contact" : {
},
"Demande dautorisations" : {
@ -160,12 +190,21 @@
},
"Domain" : {
},
"Edit" : {
},
"En continuant, vous acceptez ces conditions, " : {
},
"En ligne" : {
},
"Error" : {
},
"Error Name" : {
},
"Favourites" : {
@ -178,6 +217,9 @@
},
"I understand" : {
},
"Information" : {
},
"Interoperable" : {
@ -190,6 +232,9 @@
},
"Invalide URI" : {
},
"Invitation" : {
},
"Linphone" : {
@ -199,6 +244,12 @@
},
"Logout" : {
},
"Message" : {
},
"Mute" : {
},
"My Profile" : {
@ -214,12 +265,18 @@
},
"Not account yet?" : {
},
"Ok" : {
},
"Open source" : {
},
"Opération en cours..." : {
},
"Other actions" : {
},
"password" : {
"extractionState" : "manual",
@ -240,6 +297,9 @@
},
"Personnalize your profil mode" : {
},
"Phone (%@) :" : {
},
"Plus tard" : {
@ -273,6 +333,9 @@
},
"Share" : {
},
"SIP address :" : {
},
"sip.linphone.org" : {
@ -288,6 +351,9 @@
},
"The user name or password is incorrects" : {
},
"This contact will be deleted definitively." : {
},
"TLS" : {
@ -329,6 +395,9 @@
}
}
}
},
"Video Call" : {
},
"Vos communications sont en sécurité grâce aux **Chiffrement de bout en bout**." : {

View file

@ -79,11 +79,6 @@ struct PermissionsFragment: View {
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 20, height: 20, alignment: .leading)
.onTapGesture {
withAnimation {
dismiss()
}
}
}
.padding(16)
.background(Color.grayMain2c200)
@ -102,11 +97,6 @@ struct PermissionsFragment: View {
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 20, height: 20, alignment: .leading)
.onTapGesture {
withAnimation {
dismiss()
}
}
}
.padding(16)
.background(Color.grayMain2c200)
@ -125,11 +115,6 @@ struct PermissionsFragment: View {
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 20, height: 20, alignment: .leading)
.onTapGesture {
withAnimation {
dismiss()
}
}
}
.padding(16)
.background(Color.grayMain2c200)
@ -148,11 +133,6 @@ struct PermissionsFragment: View {
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 20, height: 20, alignment: .leading)
.onTapGesture {
withAnimation {
dismiss()
}
}
}
.padding(16)
.background(Color.grayMain2c200)

View file

@ -77,11 +77,6 @@ struct ThirdPartySipAccountWarningFragment: View {
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 20, height: 20, alignment: .leading)
.onTapGesture {
withAnimation {
dismiss()
}
}
}
.padding(16)
.background(Color.grayMain2c200)
@ -94,11 +89,6 @@ struct ThirdPartySipAccountWarningFragment: View {
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 20, height: 20, alignment: .leading)
.onTapGesture {
withAnimation {
dismiss()
}
}
}
.padding(16)
.background(Color.grayMain2c200)

View file

@ -23,12 +23,14 @@ struct ContactsView: View {
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var historyViewModel: HistoryViewModel
@Binding var isShowDeletePopup: Bool
var body: some View {
NavigationView {
ZStack(alignment: .bottomTrailing) {
ContactsFragment(contactViewModel: contactViewModel)
ContactsFragment(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup)
Button {
// Action
@ -48,5 +50,5 @@ struct ContactsView: View {
}
#Preview {
ContactsView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel())
ContactsView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel(), isShowDeletePopup: .constant(false))
}

View file

@ -20,50 +20,30 @@
import SwiftUI
struct ContactFragment: View {
@ObservedObject var contactViewModel: ContactViewModel
@State private var orientation = UIDevice.current.orientation
var body: some View {
VStack(alignment: .leading) {
if !(orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
HStack {
Image("caret-left")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.top, 20)
.onTapGesture {
withAnimation {
contactViewModel.contactTitle = ""
}
}
Spacer()
}
.padding(.leading)
}
Spacer()
Text("Contact Fragment " + contactViewModel.contactTitle)
.frame(maxWidth: .infinity)
Spacer()
}
.navigationBarHidden(true)
.onRotate { newOrientation in
orientation = newOrientation
}
}
@ObservedObject var contactViewModel: ContactViewModel
@Binding var isShowDeletePopup: Bool
@State private var showingSheet = false
var body: some View {
if #available(iOS 16.0, *) {
ContactInnerFragment(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet)
.sheet(isPresented: $showingSheet) {
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
.presentationDetents([.fraction(0.2)])
}
} else {
ContactInnerFragment(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet)
.halfSheet(showSheet: $showingSheet) {
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
} onDismiss: {}
}
}
}
#Preview {
ContactFragment(contactViewModel: ContactViewModel())
ContactFragment(contactViewModel: ContactViewModel(), isShowDeletePopup: .constant(false))
}

View file

@ -0,0 +1,564 @@
/*
* 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
struct ContactInnerFragment: View {
@ObservedObject var contactViewModel: ContactViewModel
@State private var orientation = UIDevice.current.orientation
@State private var informationIsOpen = true
@Binding var isShowDeletePopup: Bool
@Binding var showingSheet: 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
}
}
}
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) {
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:
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 {
}
}
}
.padding(16)
.background(Color.grayMain2c200)
.cornerRadius(40)
Text("Appel")
.default_text_style(styleSize: 14)
}
})
Spacer()
Button(action: {
}, 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)
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()
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 = 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")
.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 = contactViewModel.displayedFriend!.phoneNumbersWithLabel[index].phoneNumber
showingSheet.toggle()
}
)
.highPriorityGesture(
TapGesture()
.onEnded { _ in
withAnimation {
}
}
)
if index < contactViewModel.displayedFriend!.phoneNumbers.count - 1 {
VStack {
Divider()
}
.padding(.horizontal)
}
}
}
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.zIndex(-1)
.transition(.move(edge: .top))
}
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.displayedFriend!.starred.toggle()
}
} label: {
HStack {
Image("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-ringing_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("empty")
.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))
}
}
.background(Color.gray100)
}
.background(.white)
.navigationBarHidden(true)
.onRotate { newOrientation in
orientation = newOrientation
}
}
}
#Preview {
ContactInnerFragment(contactViewModel: ContactViewModel(), isShowDeletePopup: .constant(false), showingSheet: .constant(false))
}

View file

@ -0,0 +1,156 @@
/*
* 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 UniformTypeIdentifiers
struct ContactListBottomSheet: View {
@ObservedObject var magicSearch = MagicSearchSingleton.shared
@ObservedObject var contactViewModel: ContactViewModel
@State private var orientation = UIDevice.current.orientation
@Environment(\.dismiss) var dismiss
@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 {
UIPasteboard.general.setValue(
contactViewModel.stringToCopy.prefix(4) == "sip:"
? contactViewModel.stringToCopy.dropFirst(4)
: contactViewModel.stringToCopy,
forPasteboardType: UTType.plainText.identifier)
if #available(iOS 16.0, *) {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
} label: {
HStack {
Image("copy")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25, alignment: .leading)
Text(contactViewModel.stringToCopy.prefix(4) == "sip:"
? "Copy address" : "Copy number")
.default_text_style(styleSize: 16)
Spacer()
}
.frame(maxHeight: .infinity)
}
.padding(.horizontal, 30)
.background(Color.gray100)
VStack {
Divider()
}
.frame(maxWidth: .infinity)
if contactViewModel.stringToCopy.prefix(4) != "sip:" {
Button {
if #available(iOS 16.0, *) {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
} label: {
HStack {
Image("envelope-simple-open")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25, alignment: .leading)
Text("Invitation")
.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("empty")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25, alignment: .leading)
Text(contactViewModel.stringToCopy.prefix(4) == "sip:"
? "Block the address" : "Block the number")
.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)
}
}
#Preview {
ContactListBottomSheet(contactViewModel: ContactViewModel(), showingSheet: .constant(false))
}

View file

@ -22,26 +22,29 @@ import SwiftUI
struct ContactsFragment: View {
@ObservedObject var contactViewModel: ContactViewModel
@Binding var isShowDeletePopup: Bool
@State private var showingSheet = false
var body: some View {
if #available(iOS 16.0, *) {
ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet)
.sheet(isPresented: $showingSheet) {
ContactsListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
.presentationDetents([.fraction(0.2)])
}
} else {
ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet)
.halfSheet(showSheet: $showingSheet) {
ContactsListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
} onDismiss: {}
}
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)])
}
} else {
ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet)
.halfSheet(showSheet: $showingSheet) {
ContactsListBottomSheet(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet)
} onDismiss: {}
}
}
}
}
#Preview {
ContactsFragment(contactViewModel: ContactViewModel())
ContactsFragment(contactViewModel: ContactViewModel(), isShowDeletePopup: .constant(false))
}

View file

@ -53,9 +53,12 @@ struct ContactsInnerFragment: View {
}
if isFavoriteOpen {
FavoriteContactsListFragment(contactViewModel: contactViewModel, favoriteContactsListViewModel: FavoriteContactsListViewModel(), showingSheet: $showingSheet)
.zIndex(-1)
.transition(.move(edge: .top))
FavoriteContactsListFragment(
contactViewModel: contactViewModel,
favoriteContactsListViewModel: FavoriteContactsListViewModel(),
showingSheet: $showingSheet)
.zIndex(-1)
.transition(.move(edge: .top))
}
HStack(alignment: .center) {

View file

@ -29,6 +29,8 @@ struct ContactsListBottomSheet: View {
@State private var orientation = UIDevice.current.orientation
@Environment(\.dismiss) var dismiss
@Binding var isShowDeletePopup: Bool
@Binding var showingSheet: Bool
@ -118,9 +120,8 @@ struct ContactsListBottomSheet: View {
Button {
if contactViewModel.selectedFriend != nil {
contactViewModel.selectedFriend!.remove()
isShowDeletePopup.toggle()
}
self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
if #available(iOS 16.0, *) {
showingSheet.toggle()

View file

@ -36,9 +36,21 @@ struct ContactsListFragment: View {
Button {
} label: {
HStack {
if index == 0 || magicSearch.lastSearch[index].friend?.name!.lowercased().folding(options: .diacriticInsensitive, locale: .current).first !=
magicSearch.lastSearch[index-1].friend?.name!.lowercased().folding(options: .diacriticInsensitive, locale: .current).first {
Text(String((magicSearch.lastSearch[index].friend?.name!.uppercased().folding(options: .diacriticInsensitive, locale: .current).first)!))
if index == 0
|| magicSearch.lastSearch[index].friend?.name!.lowercased().folding(
options: .diacriticInsensitive,
locale: .current
).first
!= magicSearch.lastSearch[index-1].friend?.name!.lowercased().folding(
options: .diacriticInsensitive,
locale: .current
).first {
Text(
String(
(magicSearch.lastSearch[index].friend?.name!.uppercased().folding(
options: .diacriticInsensitive,
locale: .current
).first)!))
.contact_text_style_500(styleSize: 20)
.frame(width: 18)
.padding(.leading, -5)
@ -79,7 +91,7 @@ struct ContactsListFragment: View {
}
Text((magicSearch.lastSearch[index].friend?.name)!)
.default_text_style(styleSize: 16)
.frame( maxWidth: .infinity, alignment: .leading)
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundStyle(Color.orangeMain500)
}
}
@ -94,7 +106,7 @@ struct ContactsListFragment: View {
TapGesture()
.onEnded { _ in
withAnimation {
contactViewModel.contactTitle = (magicSearch.lastSearch[index].friend?.name)!
contactViewModel.displayedFriend = magicSearch.lastSearch[index].friend
}
}
)

View file

@ -38,7 +38,9 @@ struct FavoriteContactsListFragment: View {
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
AsyncImage(
url: URL(string: magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend!.photo!)
) { image in
switch image {
case .empty:
ProgressView()
@ -79,7 +81,9 @@ struct FavoriteContactsListFragment: View {
TapGesture()
.onEnded { _ in
withAnimation {
contactViewModel.contactTitle = (magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend?.name)!
contactViewModel.displayedFriend = (
magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend
)!
}
}
)
@ -92,5 +96,8 @@ struct FavoriteContactsListFragment: View {
}
#Preview {
FavoriteContactsListFragment(contactViewModel: ContactViewModel(), favoriteContactsListViewModel: FavoriteContactsListViewModel(), showingSheet: .constant(false))
FavoriteContactsListFragment(
contactViewModel: ContactViewModel(),
favoriteContactsListViewModel: FavoriteContactsListViewModel(),
showingSheet: .constant(false))
}

View file

@ -21,7 +21,8 @@ import linphonesw
class ContactViewModel: ObservableObject {
@Published var contactTitle: String = ""
@Published var displayedFriend: Friend?
var stringToCopy: String = ""
var selectedFriend: Friend?

View file

@ -21,413 +21,451 @@ import SwiftUI
import linphonesw
struct ContentView: View {
var contactManager = ContactsManager.shared
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 {
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")
}
}
}
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()
}
}
var contactManager = ContactsManager.shared
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
@State var isShowDeletePopup = false
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)
}
}
})
Spacer()
Button(action: {
self.index = 1
contactViewModel.displayedFriend = nil
}, 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")
}
}
}
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, isShowDeletePopup: $isShowDeletePopup)
} 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.displayedFriend = nil
}, 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.displayedFriend != nil || !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, isShowDeletePopup: $isShowDeletePopup)
.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)
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")),
content: Text("This contact will be deleted definitively."),
titleFirstButton: Text("Cancel"),
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 {
withAnimation {
contactViewModel.displayedFriend = nil
}
}
} else if contactViewModel.displayedFriend != nil {
contactViewModel.displayedFriend!.remove()
withAnimation {
contactViewModel.displayedFriend = nil
}
}
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
self.isShowDeletePopup.toggle()
})
.background(.black.opacity(0.65))
.zIndex(3)
.onTapGesture {
self.isShowDeletePopup.toggle()
}
}
}
}
.overlay {
if isMenuOpen {
Color.white.opacity(0.001)
.ignoresSafeArea()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onTapGesture {
isMenuOpen = false
}
}
}
.onRotate { newOrientation in
if (contactViewModel.displayedFriend != nil || !historyViewModel.historyTitle.isEmpty) && searchIsActive {
self.focusedField = false
} else if searchIsActive {
self.focusedField = true
}
orientation = newOrientation
}
}
func openMenu() {
withAnimation {
self.sideMenuIsOpen.toggle()
}
}
}
#Preview {
ContentView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel())
ContentView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel())
}

View file

@ -20,7 +20,6 @@
import SwiftUI
extension View {
//binding show bariable...
func halfSheet<Content: View>(
showSheet: Binding<Bool>,
@ViewBuilder content: @escaping () -> Content,
@ -33,7 +32,6 @@ extension View {
}
}
// UIKit integration
struct HalfSheetHelper<Content: View>: UIViewControllerRepresentable {
var sheetView: Content
@ -58,7 +56,6 @@ struct HalfSheetHelper<Content: View>: UIViewControllerRepresentable {
}
}
//on dismiss...
final class Coordinator: NSObject, UISheetPresentationControllerDelegate {
var parent: HalfSheetHelper
@ -73,7 +70,6 @@ struct HalfSheetHelper<Content: View>: UIViewControllerRepresentable {
}
}
// Custom UIHostingController for halfSheet...
final class CustomHostingController<Content: View>: UIHostingController<Content> {
override func viewDidLoad() {
view.backgroundColor = .clear
@ -82,16 +78,11 @@ final class CustomHostingController<Content: View>: UIHostingController<Content>
.medium()
]
//MARK: - sheet grabber visbility
presentationController.prefersGrabberVisible = false // i wanted to design my own grabber hehehe
presentationController.prefersGrabberVisible = false
// this allows you to scroll even during medium detent
presentationController.prefersScrollingExpandsWhenScrolledToEdge = false
//MARK: - sheet corner radius
presentationController.preferredCornerRadius = 30
// for more sheet customisation check out this great article https://sarunw.com/posts/bottom-sheet-in-ios-15-with-uisheetpresentationcontroller/#scrolling
}
}
}