Add contacts list

This commit is contained in:
benoit.martins 2023-10-19 17:25:50 +02:00
parent 146682e555
commit 9ef96bbd78
27 changed files with 1376 additions and 376 deletions

View file

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
886500223A8E518D3EE5FCB7 /* Pods_Linphone.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A334B8FDAD2893691A734BE /* Pods_Linphone.framework */; };
D706BA822ADD72D100278F45 /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D706BA812ADD72D100278F45 /* DeviceRotationViewModifier.swift */; };
D70C93DE2AC2D0F60063CA3B /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = D70C93DD2AC2D0F60063CA3B /* Localizable.xcstrings */; };
D717071E2AC5922E0037746F /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D717071D2AC5922E0037746F /* ColorExtension.swift */; };
@ -18,6 +19,9 @@
D719ABC92ABC6FD700B41C10 /* CoreContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D719ABC82ABC6FD700B41C10 /* CoreContext.swift */; };
D719ABCC2ABC769C00B41C10 /* AssistantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D719ABCB2ABC769C00B41C10 /* AssistantView.swift */; };
D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D719ABCE2ABC779A00B41C10 /* AccountLoginViewModel.swift */; };
D71FCA7F2AE1397200D2E43E /* ContactsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71FCA7E2AE1397200D2E43E /* ContactsListViewModel.swift */; };
D71FCA812AE14CFC00D2E43E /* ContactsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71FCA802AE14CFC00D2E43E /* ContactsListFragment.swift */; };
D71FCA832AE14D6E00D2E43E /* ContactFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71FCA822AE14D6E00D2E43E /* ContactFragment.swift */; };
D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72250622ADE9615008FB426 /* HistoryViewModel.swift */; };
D72250692ADFBF2D008FB426 /* SideMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72250682ADFBF2D008FB426 /* SideMenu.swift */; };
D72343302ACEFEF8009AA24E /* QrCodeScannerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723432F2ACEFEF8009AA24E /* QrCodeScannerFragment.swift */; };
@ -34,12 +38,14 @@
D74C9D012ACB098C0021626A /* PermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74C9D002ACB098C0021626A /* PermissionManager.swift */; };
D750D3392AD3E6EE00EC99C5 /* PopupLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D750D3382AD3E6EE00EC99C5 /* PopupLoadingView.swift */; };
D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7702EF12AC7205000557C00 /* WelcomeView.swift */; };
D78290B82ADD3910004AA85C /* ContactFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78290B72ADD3910004AA85C /* ContactFragment.swift */; };
D777DBB32AE12C5900565A99 /* ContactsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D777DBB22AE12C5900565A99 /* ContactsManager.swift */; };
D78290B82ADD3910004AA85C /* ContactsFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78290B72ADD3910004AA85C /* ContactsFragment.swift */; };
D78290BB2ADD40B2004AA85C /* ContactViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78290BA2ADD40B2004AA85C /* ContactViewModel.swift */; };
D7A03FBD2ACC2DB60081A588 /* ContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A03FBC2ACC2DB60081A588 /* ContactsView.swift */; };
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 */; };
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 */; };
D7D24D152AC1B4E800C6F35B /* NotoSans-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D7D24D0F2AC1B4E800C6F35B /* NotoSans-Light.ttf */; };
@ -48,14 +54,15 @@
D7D24D182AC1B4E800C6F35B /* NotoSans-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D7D24D122AC1B4E800C6F35B /* NotoSans-ExtraBold.ttf */; };
D7DA67622ACCB2FA00E95002 /* LoginFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DA67612ACCB2FA00E95002 /* LoginFragment.swift */; };
D7DA67642ACCB31700E95002 /* ProfileModeFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DA67632ACCB31700E95002 /* ProfileModeFragment.swift */; };
D7E6D0492AE933AD00A57AAF /* FavoriteContactsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6D0482AE933AD00A57AAF /* FavoriteContactsListFragment.swift */; };
D7E6D04B2AE9347D00A57AAF /* FavoriteContactsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6D04A2AE9347D00A57AAF /* FavoriteContactsListViewModel.swift */; };
D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */; };
D7FB55112AD447FD00A5AB15 /* RegisterFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */; };
F4BB8DFBA0FF08430EBA9351 /* Pods_Linphone.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F5B27C5576B1EAED2F205EB /* Pods_Linphone.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
377E0B5C2B1F38192E694334 /* Pods-Linphone.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Linphone.debug.xcconfig"; path = "Target Support Files/Pods-Linphone/Pods-Linphone.debug.xcconfig"; sourceTree = "<group>"; };
6F5B27C5576B1EAED2F205EB /* Pods_Linphone.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Linphone.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1A334B8FDAD2893691A734BE /* Pods_Linphone.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Linphone.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1DE4CD5FD6E1F01639F27E3B /* Pods-Linphone.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Linphone.release.xcconfig"; path = "Target Support Files/Pods-Linphone/Pods-Linphone.release.xcconfig"; sourceTree = "<group>"; };
D706BA812ADD72D100278F45 /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
D70C93DD2AC2D0F60063CA3B /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
D717071D2AC5922E0037746F /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = "<group>"; };
@ -69,6 +76,9 @@
D719ABC82ABC6FD700B41C10 /* CoreContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreContext.swift; sourceTree = "<group>"; };
D719ABCB2ABC769C00B41C10 /* AssistantView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssistantView.swift; sourceTree = "<group>"; };
D719ABCE2ABC779A00B41C10 /* AccountLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLoginViewModel.swift; sourceTree = "<group>"; };
D71FCA7E2AE1397200D2E43E /* ContactsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsListViewModel.swift; sourceTree = "<group>"; };
D71FCA802AE14CFC00D2E43E /* ContactsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsListFragment.swift; sourceTree = "<group>"; };
D71FCA822AE14D6E00D2E43E /* ContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactFragment.swift; sourceTree = "<group>"; };
D72250622ADE9615008FB426 /* HistoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryViewModel.swift; sourceTree = "<group>"; };
D72250682ADFBF2D008FB426 /* SideMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenu.swift; sourceTree = "<group>"; };
D723432F2ACEFEF8009AA24E /* QrCodeScannerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrCodeScannerFragment.swift; sourceTree = "<group>"; };
@ -85,13 +95,15 @@
D74C9D002ACB098C0021626A /* PermissionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionManager.swift; sourceTree = "<group>"; };
D750D3382AD3E6EE00EC99C5 /* PopupLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupLoadingView.swift; sourceTree = "<group>"; };
D7702EF12AC7205000557C00 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
D78290B72ADD3910004AA85C /* ContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactFragment.swift; sourceTree = "<group>"; };
D777DBB22AE12C5900565A99 /* ContactsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsManager.swift; sourceTree = "<group>"; };
D78290B72ADD3910004AA85C /* ContactsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsFragment.swift; sourceTree = "<group>"; };
D78290BA2ADD40B2004AA85C /* ContactViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactViewModel.swift; sourceTree = "<group>"; };
D7A03FBC2ACC2DB60081A588 /* ContactsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsView.swift; sourceTree = "<group>"; };
D7A03FBF2ACC2E390081A588 /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
D7D24D0F2AC1B4E800C6F35B /* NotoSans-Light.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSans-Light.ttf"; sourceTree = "<group>"; };
@ -100,9 +112,11 @@
D7D24D122AC1B4E800C6F35B /* NotoSans-ExtraBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSans-ExtraBold.ttf"; sourceTree = "<group>"; };
D7DA67612ACCB2FA00E95002 /* LoginFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginFragment.swift; sourceTree = "<group>"; };
D7DA67632ACCB31700E95002 /* ProfileModeFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModeFragment.swift; sourceTree = "<group>"; };
D7E6D0482AE933AD00A57AAF /* FavoriteContactsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteContactsListFragment.swift; sourceTree = "<group>"; };
D7E6D04A2AE9347D00A57AAF /* FavoriteContactsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteContactsListViewModel.swift; sourceTree = "<group>"; };
D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsFragment.swift; sourceTree = "<group>"; };
D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterFragment.swift; sourceTree = "<group>"; };
F76FB87556A3109F61F9E2D5 /* Pods-Linphone.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Linphone.release.xcconfig"; path = "Target Support Files/Pods-Linphone/Pods-Linphone.release.xcconfig"; sourceTree = "<group>"; };
FB718F405DAF7B9993AEB878 /* Pods-Linphone.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Linphone.debug.xcconfig"; path = "Target Support Files/Pods-Linphone/Pods-Linphone.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -110,17 +124,17 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F4BB8DFBA0FF08430EBA9351 /* Pods_Linphone.framework in Frameworks */,
886500223A8E518D3EE5FCB7 /* Pods_Linphone.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
52EFCC310713B3CA01062945 /* Frameworks */ = {
1CD95087B17CAD149119B7C2 /* Frameworks */ = {
isa = PBXGroup;
children = (
6F5B27C5576B1EAED2F205EB /* Pods_Linphone.framework */,
1A334B8FDAD2893691A734BE /* Pods_Linphone.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -128,8 +142,8 @@
A31AF2AB8C6A3D7B7EA3B424 /* Pods */ = {
isa = PBXGroup;
children = (
377E0B5C2B1F38192E694334 /* Pods-Linphone.debug.xcconfig */,
F76FB87556A3109F61F9E2D5 /* Pods-Linphone.release.xcconfig */,
FB718F405DAF7B9993AEB878 /* Pods-Linphone.debug.xcconfig */,
1DE4CD5FD6E1F01639F27E3B /* Pods-Linphone.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
@ -140,6 +154,7 @@
D717071D2AC5922E0037746F /* ColorExtension.swift */,
D717071F2AC5989C0037746F /* TextExtension.swift */,
D74C9D002ACB098C0021626A /* PermissionManager.swift */,
D7D1698B2AE66FA500109A5C /* MagicSearchSingleton.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -150,7 +165,7 @@
D719ABB52ABC67BF00B41C10 /* Linphone */,
D719ABB42ABC67BF00B41C10 /* Products */,
A31AF2AB8C6A3D7B7EA3B424 /* Pods */,
52EFCC310713B3CA01062945 /* Frameworks */,
1CD95087B17CAD149119B7C2 /* Frameworks */,
);
sourceTree = "<group>";
};
@ -167,6 +182,7 @@
children = (
D7A03FC52ACC458A0081A588 /* SplashScreen.swift */,
D719ABB62ABC67BF00B41C10 /* LinphoneApp.swift */,
D777DBB12AE12C4000565A99 /* Contacts */,
D719ABC72ABC6FB200B41C10 /* Core */,
D719ABC52ABC6EE800B41C10 /* UI */,
D717071C2AC591EF0037746F /* Utils */,
@ -285,10 +301,21 @@
path = Welcome;
sourceTree = "<group>";
};
D777DBB12AE12C4000565A99 /* Contacts */ = {
isa = PBXGroup;
children = (
D777DBB22AE12C5900565A99 /* ContactsManager.swift */,
);
path = Contacts;
sourceTree = "<group>";
};
D78290B62ADD38F9004AA85C /* Fragments */ = {
isa = PBXGroup;
children = (
D78290B72ADD3910004AA85C /* ContactFragment.swift */,
D78290B72ADD3910004AA85C /* ContactsFragment.swift */,
D71FCA802AE14CFC00D2E43E /* ContactsListFragment.swift */,
D71FCA822AE14D6E00D2E43E /* ContactFragment.swift */,
D7E6D0482AE933AD00A57AAF /* FavoriteContactsListFragment.swift */,
);
path = Fragments;
sourceTree = "<group>";
@ -297,6 +324,8 @@
isa = PBXGroup;
children = (
D78290BA2ADD40B2004AA85C /* ContactViewModel.swift */,
D71FCA7E2AE1397200D2E43E /* ContactsListViewModel.swift */,
D7E6D04A2AE9347D00A57AAF /* FavoriteContactsListViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
@ -363,12 +392,12 @@
isa = PBXNativeTarget;
buildConfigurationList = D719ABC22ABC67BF00B41C10 /* Build configuration list for PBXNativeTarget "Linphone" */;
buildPhases = (
6FE8573A5CFC1DA89D3172B5 /* [CP] Check Pods Manifest.lock */,
BE9432280D0A11AA770A50FD /* [CP] Check Pods Manifest.lock */,
D719ABAF2ABC67BF00B41C10 /* Sources */,
D719ABB02ABC67BF00B41C10 /* Frameworks */,
D719ABB12ABC67BF00B41C10 /* Resources */,
D7FB55122AD53FE200A5AB15 /* Run Script */,
230129DD87A6EBB04DF458AD /* [CP] Embed Pods Frameworks */,
D5CA1ECD620857DB91E334A5 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -432,24 +461,7 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
230129DD87A6EBB04DF458AD /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Linphone/Pods-Linphone-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Linphone/Pods-Linphone-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Linphone/Pods-Linphone-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
6FE8573A5CFC1DA89D3172B5 /* [CP] Check Pods Manifest.lock */ = {
BE9432280D0A11AA770A50FD /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -471,6 +483,23 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
D5CA1ECD620857DB91E334A5 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Linphone/Pods-Linphone-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Linphone/Pods-Linphone-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Linphone/Pods-Linphone-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
D7FB55122AD53FE200A5AB15 /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -499,16 +528,21 @@
files = (
D71707202AC5989C0037746F /* TextExtension.swift in Sources */,
D719ABB92ABC67BF00B41C10 /* ContentView.swift in Sources */,
D71FCA832AE14D6E00D2E43E /* ContactFragment.swift in Sources */,
D750D3392AD3E6EE00EC99C5 /* PopupLoadingView.swift in Sources */,
D7E6D0492AE933AD00A57AAF /* FavoriteContactsListFragment.swift in Sources */,
D706BA822ADD72D100278F45 /* DeviceRotationViewModifier.swift in Sources */,
D719ABC92ABC6FD700B41C10 /* CoreContext.swift in Sources */,
D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */,
D777DBB32AE12C5900565A99 /* ContactsManager.swift in Sources */,
D7A03FBD2ACC2DB60081A588 /* ContactsView.swift in Sources */,
D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */,
D78290BB2ADD40B2004AA85C /* ContactViewModel.swift in Sources */,
D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */,
D74C9D012ACB098C0021626A /* PermissionManager.swift in Sources */,
D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */,
D71FCA7F2AE1397200D2E43E /* ContactsListViewModel.swift in Sources */,
D71FCA812AE14CFC00D2E43E /* ContactsListFragment.swift in Sources */,
D719ABB72ABC67BF00B41C10 /* LinphoneApp.swift in Sources */,
D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */,
D7A2EDD62AC18115005D90FC /* SharedMainViewModel.swift in Sources */,
@ -522,12 +556,14 @@
D72343322ACEFF58009AA24E /* QRScannerController.swift in Sources */,
D72343342ACEFFC3009AA24E /* QRScanner.swift in Sources */,
D72343302ACEFEF8009AA24E /* QrCodeScannerFragment.swift in Sources */,
D7D1698C2AE66FA500109A5C /* MagicSearchSingleton.swift in Sources */,
D72250692ADFBF2D008FB426 /* SideMenu.swift in Sources */,
D717071E2AC5922E0037746F /* ColorExtension.swift in Sources */,
D78290B82ADD3910004AA85C /* ContactFragment.swift in Sources */,
D78290B82ADD3910004AA85C /* ContactsFragment.swift in Sources */,
D7DA67642ACCB31700E95002 /* ProfileModeFragment.swift in Sources */,
D74C9CFC2ACACF370021626A /* WelcomePage3Fragment.swift in Sources */,
D719ABCC2ABC769C00B41C10 /* AssistantView.swift in Sources */,
D7E6D04B2AE9347D00A57AAF /* FavoriteContactsListViewModel.swift in Sources */,
D74C9CFA2ACACF2D0021626A /* WelcomePage2Fragment.swift in Sources */,
D74C9CFF2ACAEC5E0021626A /* PopupView.swift in Sources */,
D7DA67622ACCB2FA00E95002 /* LoginFragment.swift in Sources */,
@ -652,7 +688,7 @@
};
D719ABC32ABC67BF00B41C10 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 377E0B5C2B1F38192E694334 /* Pods-Linphone.debug.xcconfig */;
baseConfigurationReference = FB718F405DAF7B9993AEB878 /* Pods-Linphone.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
@ -668,7 +704,8 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Linphone/Info.plist;
INFOPLIST_KEY_NSCameraUsageDescription = "Share photos with your friends and customize avatars";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "";
INFOPLIST_KEY_NSContactsUsageDescription = "Make calls with your friends";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Share photos with your friends and customize avatars";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@ -696,7 +733,7 @@
};
D719ABC42ABC67BF00B41C10 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F76FB87556A3109F61F9E2D5 /* Pods-Linphone.release.xcconfig */;
baseConfigurationReference = 1DE4CD5FD6E1F01639F27E3B /* Pods-Linphone.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
@ -712,7 +749,8 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Linphone/Info.plist;
INFOPLIST_KEY_NSCameraUsageDescription = "Share photos with your friends and customize avatars";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "";
INFOPLIST_KEY_NSContactsUsageDescription = "Make calls with your friends";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Share photos with your friends and customize avatars";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D719ABB22ABC67BF00B41C10"
BuildableName = "Linphone.app"
BlueprintName = "Linphone"
ReferencedContainer = "container:Linphone.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D719ABB22ABC67BF00B41C10"
BuildableName = "Linphone.app"
BlueprintName = "Linphone"
ReferencedContainer = "container:Linphone.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D719ABB22ABC67BF00B41C10"
BuildableName = "Linphone.app"
BlueprintName = "Linphone"
ReferencedContainer = "container:Linphone.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "caret-up.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="M213.66,165.66a8,8,0,0,1-11.32,0L128,91.31,53.66,165.66a8,8,0,0,1-11.32-11.32l80-80a8,8,0,0,1,11.32,0l80,80A8,8,0,0,1,213.66,165.66Z"></path></svg>

After

Width:  |  Height:  |  Size: 256 B

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "check.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="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"></path></svg>

After

Width:  |  Height:  |  Size: 239 B

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "green-check.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="M20.789 6.72975L9.26952 18.2492C9.20266 18.3162 9.12325 18.3693 9.03585 18.4055C8.94845 18.4417 8.85476 18.4604 8.76014 18.4604C8.66553 18.4604 8.57184 18.4417 8.48444 18.4055C8.39704 18.3693 8.31763 18.3162 8.25077 18.2492L3.21099 13.2095C3.0759 13.0744 3 12.8911 3 12.7001C3 12.509 3.0759 12.3258 3.21099 12.1907C3.34609 12.0556 3.52931 11.9797 3.72037 11.9797C3.91142 11.9797 4.09465 12.0556 4.22975 12.1907L8.76014 16.722L19.7703 5.71099C19.9053 5.5759 20.0886 5.5 20.2796 5.5C20.4707 5.5 20.6539 5.5759 20.789 5.71099C20.9241 5.84609 21 6.02931 21 6.22037C21 6.41142 20.9241 6.59465 20.789 6.72975Z" fill="#4FAE80"/>
</svg>

After

Width:  |  Height:  |  Size: 734 B

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "x.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="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path></svg>

After

Width:  |  Height:  |  Size: 308 B

View file

@ -0,0 +1,250 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of Linphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import linphonesw
import Contacts
import SwiftUI
final class ContactsManager: ObservableObject {
static let shared = ContactsManager()
private var coreContext = CoreContext.shared
@Published var contacts: [Contact] = []
private let nativeAddressBookFriendList = "Native address-book"
let linphoneAddressBookFirendList = "Linphone address-book"
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 {
print("$TAG ${friends.size} friends created")
let fetchedFriends = friends
let nativeFriendList = self.coreContext.mCore.getFriendListByName(name: self.nativeAddressBookFriendList)
var friendList = nativeFriendList
if friendList == nil {
do {
friendList = try self.coreContext.mCore.createFriendList()
} catch let error {
print("Failed to enumerate contact", error)
}
}
if friendList!.displayName == nil || 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
friendList!.displayName = self.nativeAddressBookFriendList
self.coreContext.mCore.addFriendList(list: 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)
}
}
fetchedFriends.forEach { friend in
_ = friendList!.addLocalFriend(linphoneFriend: friend)
}
friends.removeAll()
print("$TAG Friends added")
friendList!.updateSubscriptions()
print("$TAG Subscription(s) updated")
}
}
}
func saveImage(image: UIImage, name: String) -> String {
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 ""
}
}
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
}
}
struct PhoneNumber {
var numLabel: String
var num: String
}
struct Contact: Identifiable {
var id = UUID()
var firstName: String
var lastName: String
var organizationName: String
var displayName: String
var sipAddresses: [String] = []
var phoneNumbers: [PhoneNumber] = []
var imageData: String
}

View file

@ -40,6 +40,9 @@ final class CoreContext: ObservableObject {
let factory = Factory.Instance
let configDir = factory.getConfigDir(context: nil)
try? mCore = Factory.Instance.createCore(configPath: "\(configDir)/MyConfig", factoryConfigPath: "", systemContext: nil)
mCore.friendsDatabasePath = "\(configDir)/friends.db"
try? mCore.start()
// Create a Core listener to listen for the callback we need

View file

@ -28,7 +28,7 @@ struct LinphoneApp: App {
var body: some Scene {
WindowGroup {
if isActive {
ContentView(sharedMainViewModel: SharedMainViewModel(), contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel())
ContentView(sharedMainViewModel: SharedMainViewModel(), contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel())
.toast(isShowing: $coreContext.toastMessage)
} else {
SplashScreen(isActive: $isActive)

View file

@ -36,9 +36,6 @@
},
"**Notifications** : Pour vous informé quand vous recevez un message ou un appel." : {
},
"%lld" : {
},
"%lld Book (Example)" : {
"extractionState" : "manual",
@ -95,6 +92,9 @@
},
"Accept all" : {
},
"All contacts" : {
},
"assistant_account_login" : {
"extractionState" : "manual",
@ -160,6 +160,9 @@
},
"Error" : {
},
"Favourites" : {
},
"History Contact fragment" : {
@ -252,6 +255,12 @@
},
"Sécurisé" : {
},
"See all" : {
},
"See Linphone contact" : {
},
"sip.linphone.org" : {

View file

@ -192,6 +192,7 @@ struct PermissionsFragment: View {
.padding(.horizontal)
Button {
permissionManager.contactsRequestPermission()
permissionManager.cameraRequestPermission()
} label: {
Text("D'accord")

View file

@ -23,58 +23,12 @@ struct ContactsView: View {
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var historyViewModel: HistoryViewModel
@State private var orientation = UIDevice.current.orientation
@State private var selectedIndex = 0
var objects: [Int] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39
]
var body: some View {
NavigationView {
ZStack(alignment: .bottomTrailing) {
VStack(spacing: 0) {
VStack {
List {
ForEach(objects, id: \.self) { index in
Button {
withAnimation {
contactViewModel.contactTitle = String(index)
}
} label: {
Text("\(index)")
.frame( maxWidth: .infinity, alignment: .leading)
.foregroundStyle(Color.orangeMain500)
}
.buttonStyle(.borderless)
.listRowSeparator(.hidden)
}
}
.listStyle(.plain)
.overlay(
VStack {
if objects.isEmpty {
Spacer()
Image("illus-belledonne1")
.resizable()
.scaledToFit()
.clipped()
.padding(.all)
Text("No contacts for the moment...")
.default_text_style_800(styleSize: 16)
Spacer()
Spacer()
}
}
.padding(.all)
)
}
}
.onRotate { newOrientation in
orientation = newOrientation
}
ContactsFragment(contactViewModel: contactViewModel)
Button {
// Action

View file

@ -20,58 +20,50 @@
import SwiftUI
struct ContactFragment: View {
@ObservedObject var contactViewModel: ContactViewModel
@State private var orientation = UIDevice.current.orientation
@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(contactViewModel.contactTitle)
.frame(maxWidth: .infinity)
List {
ForEach(1...40, id: \.self) { index in
Button {
contactViewModel.contactTitle = String(index)
} label: {
Text("\(index)")
.frame( maxWidth: .infinity, alignment: .leading)
}
.buttonStyle(.borderless)
}
}
}
.navigationBarHidden(true)
.onRotate { newOrientation in
orientation = newOrientation
}
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
}
}
}
#Preview {
ContactFragment(contactViewModel: ContactViewModel())
ContactFragment(contactViewModel: ContactViewModel())
}

View file

@ -0,0 +1,80 @@
/*
* 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 ContactsFragment: View {
@ObservedObject var contactViewModel: ContactViewModel
@State private var orientation = UIDevice.current.orientation
@State var isFavoriteOpen: Bool = true
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 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)
ContactsListFragment(contactViewModel: contactViewModel, contactsListViewModel: ContactsListViewModel())
}
.navigationBarHidden(true)
.onRotate { newOrientation in
orientation = newOrientation
}
}
}
#Preview {
ContactsFragment(contactViewModel: ContactViewModel())
}

View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import SwiftUI
import linphonesw
struct ContactsListFragment: View {
@ObservedObject var magicSearch = MagicSearchSingleton.shared
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var contactsListViewModel: ContactsListViewModel
var body: some View {
VStack {
List {
ForEach(0..<magicSearch.lastSearch.count, id: \.self) { index in
Button {
withAnimation {
contactViewModel.contactTitle = (magicSearch.lastSearch[index].friend?.name)!
}
} 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)!))
.contact_text_style_500(styleSize: 20)
.frame(width: 18)
.padding(.leading, -5)
.padding(.trailing, 10)
} else {
Text("")
.contact_text_style_500(styleSize: 20)
.frame(width: 18)
.padding(.leading, -5)
.padding(.trailing, 10)
}
if magicSearch.lastSearch[index].friend!.photo != nil && !magicSearch.lastSearch[index].friend!.photo!.isEmpty {
AsyncImage(url: URL(string: magicSearch.lastSearch[index].friend!.photo!)) { image in
switch image {
case .empty:
ProgressView()
.frame(width: 45, height: 45)
case .success(let image):
image
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
case .failure:
Image("profile-image-example")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
@unknown default:
EmptyView()
}
}
} else {
Image("profile-image-example")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
}
Text((magicSearch.lastSearch[index].friend?.name)!)
.default_text_style(styleSize: 16)
.frame( maxWidth: .infinity, alignment: .leading)
.foregroundStyle(Color.orangeMain500)
}
}
.buttonStyle(.borderless)
.listRowSeparator(.hidden)
}
}
.listStyle(.plain)
.overlay(
VStack {
if magicSearch.lastSearch.isEmpty {
Spacer()
Image("illus-belledonne1")
.resizable()
.scaledToFit()
.clipped()
.padding(.all)
Text("No contacts for the moment...")
.default_text_style_800(styleSize: 16)
Spacer()
Spacer()
}
}
.padding(.all)
)
}
}
}
#Preview {
ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel())
}

View file

@ -0,0 +1,80 @@
/*
* 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 FavoriteContactsListFragment: View {
@ObservedObject var magicSearch = MagicSearchSingleton.shared
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var favoriteContactsListViewModel: FavoriteContactsListViewModel
var body: some View {
ScrollView(.horizontal) {
HStack {
ForEach(0..<magicSearch.lastSearch.count, id: \.self) { index in
Button {
withAnimation {
contactViewModel.contactTitle = (magicSearch.lastSearch[index].friend?.name)!
}
} label: {
VStack {
if magicSearch.lastSearch[index].friend!.photo != nil && !magicSearch.lastSearch[index].friend!.photo!.isEmpty {
AsyncImage(url: URL(string: magicSearch.lastSearch[index].friend!.photo!)) { image in
switch image {
case .empty:
ProgressView()
.frame(width: 45, height: 45)
case .success(let image):
image
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
case .failure:
Image("profile-image-example")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
@unknown default:
EmptyView()
}
}
} else {
Image("profile-image-example")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
}
Text((magicSearch.lastSearch[index].friend?.name)!)
.default_text_style(styleSize: 16)
.frame( maxWidth: .infinity, alignment: .center)
}
}
.frame(maxWidth: 70)
}
}
.padding(.all, 10)
}
}
}
#Preview {
FavoriteContactsListFragment(contactViewModel: ContactViewModel(), favoriteContactsListViewModel: FavoriteContactsListViewModel())
}

View file

@ -17,11 +17,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
class ContactViewModel: ObservableObject {
@Published var contactTitle: String = ""
@Published var contactTitle: String = ""
init() {}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import linphonesw
class ContactsListViewModel: ObservableObject {
private var magicSearch = MagicSearchSingleton.shared
private var coreContext = CoreContext.shared
init() {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import linphonesw
class FavoriteContactsListViewModel: ObservableObject {
init() {}
}

View file

@ -18,252 +18,421 @@
*/
import SwiftUI
import linphonesw
struct ContentView: View {
@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 menuOpen: 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)
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
@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")
}
}
}
})
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)
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")
}
}
}
})
Spacer()
}
}
.frame(width: 75)
.padding(.leading,
orientation == .landscapeRight && geometry.safeAreaInsets.bottom > 0
? -geometry.safeAreaInsets.leading
: 0)
}
VStack(spacing: 0) {
HStack {
Image("profile-image-example")
.resizable()
.frame(width: 40, height: 40)
.clipShape(Circle())
} label: {
Image(index == 0 ? "filtres" : "more")
}
.padding(.leading)
.onTapGesture {
openMenu()
isMenuOpen = true
}
Text(index == 0 ? "Contacts" : "Calls")
.default_text_style_white_800(styleSize: 20)
.padding(.leading, 10)
Spacer()
Button {
} label: {
Image("search")
}
Button {
} label: {
Image(index == 0 ? "filtres" : "more")
}
.padding(.leading)
}
.frame(maxWidth: .infinity)
.frame(height: 50)
.padding(.horizontal)
.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) {
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))
)
}
}
.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
}
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)
} else if self.index == 1 {
HistoryContactFragment()
.frame(maxWidth: .infinity)
.background(Color.gray100)
}
}
.padding(.leading,
orientation == .landscapeRight && geometry.safeAreaInsets.bottom > 0
? -geometry.safeAreaInsets.leading
: 0)
.transition(.move(edge: .trailing))
}
SideMenu(
width: geometry.size.width / 5 * 4,
isOpen: self.menuOpen,
menuClose: self.openMenu,
safeAreaInsets: geometry.safeAreaInsets
)
.ignoresSafeArea(.all)
}
}
.onRotate { newOrientation in
orientation = newOrientation
}
}
}
func openMenu() {
withAnimation {
self.menuOpen.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 {

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import linphonesw
final class MagicSearchSingleton: ObservableObject {
static let shared = MagicSearchSingleton()
private var coreContext = CoreContext.shared
private var magicSearch: MagicSearch!
var magicSearchDelegate: MagicSearchDelegate?
@objc var currentFilter: String = ""
var previousFilter: String?
var needUpdateLastSearchContacts = false
@Published var lastSearch: [SearchResult] = []
private var limitSearchToLinphoneAccounts = true
@Published var allContact = false
private var domainDefaultAccount = ""
private init() {
domainDefaultAccount = coreContext.mCore.defaultAccount!.params!.domain!
magicSearch = try? coreContext.mCore.createMagicSearch()
magicSearch.limitedSearch = false
magicSearchDelegate = MagicSearchDelegateStub(onSearchResultsReceived: { (magicSearch: MagicSearch) in
self.needUpdateLastSearchContacts = true
self.lastSearch = magicSearch.lastSearch
})
magicSearch.addDelegate(delegate: magicSearchDelegate!)
}
func searchForContacts(sourceFlags: Int) {
if let oldFilter = previousFilter {
if oldFilter.count > currentFilter.count || oldFilter != currentFilter {
magicSearch.resetSearchCache()
}
}
previousFilter = currentFilter
magicSearch.getContactsListAsync(
filter: currentFilter,
domain: allContact ? "" : domainDefaultAccount,
sourceFlags: sourceFlags,
aggregation: MagicSearch.Aggregation.Friend)
}
}

View file

@ -19,6 +19,7 @@
import Foundation
import Photos
import Contacts
class PermissionManager: ObservableObject {
@ -26,6 +27,7 @@ class PermissionManager: ObservableObject {
@Published var photoLibraryPermissionGranted = false
@Published var cameraPermissionGranted = false
@Published var contactsPermissionGranted = false
private init() {}
@ -44,4 +46,13 @@ class PermissionManager: ObservableObject {
}
})
}
func contactsRequestPermission() {
let store = CNContactStore()
store.requestAccess(for: .contacts) { success, _ in
DispatchQueue.main.async {
self.contactsPermissionGranted = success
}
}
}
}

View file

@ -136,4 +136,9 @@ extension View {
self.font(Font.custom("NotoSans-Regular", size: styleSize))
.foregroundStyle(Color.grayMain2c600)
}
func contact_text_style_500(styleSize: CGFloat) -> some View {
self.font(Font.custom("NotoSans-Medium", size: styleSize))
.foregroundStyle(Color.grayMain2c400)
}
}