mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-05-03 20:46:28 +00:00
Merge branch 'feature/start_call_view' into 'master'
Merge tous les changements de benoit : call history, History call details, presence, start call view See merge request BC/private/linphone-iphone-6.0!12
This commit is contained in:
commit
1dde8873a8
65 changed files with 3435 additions and 499 deletions
|
|
@ -27,8 +27,17 @@
|
|||
D72343322ACEFF58009AA24E /* QRScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72343312ACEFF58009AA24E /* QRScannerController.swift */; };
|
||||
D72343342ACEFFC3009AA24E /* QRScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72343332ACEFFC3009AA24E /* QRScanner.swift */; };
|
||||
D72343362AD037AF009AA24E /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72343352AD037AF009AA24E /* ToastView.swift */; };
|
||||
D726E4392B16440C0083C415 /* ContactAvatarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D726E4382B16440C0083C415 /* ContactAvatarModel.swift */; };
|
||||
D726E43D2B19E4FE0083C415 /* StartCallFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D726E43C2B19E4FE0083C415 /* StartCallFragment.swift */; };
|
||||
D726E43F2B19E56F0083C415 /* StartCallViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D726E43E2B19E56F0083C415 /* StartCallViewModel.swift */; };
|
||||
D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72992382ADD7F68003AF125 /* HistoryContactFragment.swift */; };
|
||||
D732A9092AFD235500DB42BA /* ShareSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A9082AFD235500DB42BA /* ShareSheetController.swift */; };
|
||||
D732A90C2B0376F500DB42BA /* linphonerc-default in Resources */ = {isa = PBXBuildFile; fileRef = D732A90A2B0376F500DB42BA /* linphonerc-default */; };
|
||||
D732A90D2B0376F500DB42BA /* linphonerc-factory in Resources */ = {isa = PBXBuildFile; fileRef = D732A90B2B0376F500DB42BA /* linphonerc-factory */; };
|
||||
D732A90F2B04C3B400DB42BA /* HistoryFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A90E2B04C3B400DB42BA /* HistoryFragment.swift */; };
|
||||
D732A9132B04C7A300DB42BA /* HistoryListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A9122B04C7A300DB42BA /* HistoryListFragment.swift */; };
|
||||
D732A9152B04C7FE00DB42BA /* HistoryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A9142B04C7FE00DB42BA /* HistoryListViewModel.swift */; };
|
||||
D732A91B2B061BD900DB42BA /* HistoryListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A91A2B061BD900DB42BA /* HistoryListBottomSheet.swift */; };
|
||||
D748BF2C2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */; };
|
||||
D748BF2E2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */; };
|
||||
D74C9CF82ACACECE0021626A /* WelcomePage1Fragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74C9CF72ACACECE0021626A /* WelcomePage1Fragment.swift */; };
|
||||
|
|
@ -37,14 +46,20 @@
|
|||
D74C9CFF2ACAEC5E0021626A /* PopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74C9CFE2ACAEC5E0021626A /* PopupView.swift */; };
|
||||
D74C9D012ACB098C0021626A /* PermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74C9D002ACB098C0021626A /* PermissionManager.swift */; };
|
||||
D750D3392AD3E6EE00EC99C5 /* PopupLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D750D3382AD3E6EE00EC99C5 /* PopupLoadingView.swift */; };
|
||||
D76005F62B0798B00054B79A /* IntExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76005F52B0798B00054B79A /* IntExtension.swift */; };
|
||||
D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7702EF12AC7205000557C00 /* WelcomeView.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 */; };
|
||||
D783C77C2B1089B200622CC2 /* assistant_linphone_default_values in Resources */ = {isa = PBXBuildFile; fileRef = D783C77A2B1089B200622CC2 /* assistant_linphone_default_values */; };
|
||||
D783C77D2B1089B200622CC2 /* assistant_third_party_default_values in Resources */ = {isa = PBXBuildFile; fileRef = D783C77B2B1089B200622CC2 /* assistant_third_party_default_values */; };
|
||||
D79622342B1DFE600037EACD /* DialerBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D79622332B1DFE600037EACD /* DialerBottomSheet.swift */; };
|
||||
D796F2002B0BB61A0041115F /* ToastViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D796F1FF2B0BB61A0041115F /* ToastViewModel.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 */; };
|
||||
D7ADF6002AFE356400212231 /* Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADF5FF2AFE356400212231 /* Avatar.swift */; };
|
||||
D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */; };
|
||||
D7C365082AEFAB7F00FE6142 /* ContactListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C365072AEFAB7F00FE6142 /* ContactListBottomSheet.swift */; };
|
||||
D7C3650A2AF001C300FE6142 /* EditContactFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C365092AF001C300FE6142 /* EditContactFragment.swift */; };
|
||||
|
|
@ -93,8 +108,17 @@
|
|||
D72343312ACEFF58009AA24E /* QRScannerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScannerController.swift; sourceTree = "<group>"; };
|
||||
D72343332ACEFFC3009AA24E /* QRScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScanner.swift; sourceTree = "<group>"; };
|
||||
D72343352AD037AF009AA24E /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
|
||||
D726E4382B16440C0083C415 /* ContactAvatarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactAvatarModel.swift; sourceTree = "<group>"; };
|
||||
D726E43C2B19E4FE0083C415 /* StartCallFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartCallFragment.swift; sourceTree = "<group>"; };
|
||||
D726E43E2B19E56F0083C415 /* StartCallViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartCallViewModel.swift; sourceTree = "<group>"; };
|
||||
D72992382ADD7F68003AF125 /* HistoryContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryContactFragment.swift; sourceTree = "<group>"; };
|
||||
D732A9082AFD235500DB42BA /* ShareSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheetController.swift; sourceTree = "<group>"; };
|
||||
D732A90A2B0376F500DB42BA /* linphonerc-default */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "linphonerc-default"; sourceTree = "<group>"; };
|
||||
D732A90B2B0376F500DB42BA /* linphonerc-factory */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "linphonerc-factory"; sourceTree = "<group>"; };
|
||||
D732A90E2B04C3B400DB42BA /* HistoryFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryFragment.swift; sourceTree = "<group>"; };
|
||||
D732A9122B04C7A300DB42BA /* HistoryListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryListFragment.swift; sourceTree = "<group>"; };
|
||||
D732A9142B04C7FE00DB42BA /* HistoryListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryListViewModel.swift; sourceTree = "<group>"; };
|
||||
D732A91A2B061BD900DB42BA /* HistoryListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryListBottomSheet.swift; sourceTree = "<group>"; };
|
||||
D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountLoginFragment.swift; sourceTree = "<group>"; };
|
||||
D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountWarningFragment.swift; sourceTree = "<group>"; };
|
||||
D74C9CF72ACACECE0021626A /* WelcomePage1Fragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomePage1Fragment.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -103,15 +127,21 @@
|
|||
D74C9CFE2ACAEC5E0021626A /* PopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupView.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
D76005F52B0798B00054B79A /* IntExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntExtension.swift; sourceTree = "<group>"; };
|
||||
D7702EF12AC7205000557C00 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.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>"; };
|
||||
D783C77A2B1089B200622CC2 /* assistant_linphone_default_values */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = assistant_linphone_default_values; sourceTree = "<group>"; };
|
||||
D783C77B2B1089B200622CC2 /* assistant_third_party_default_values */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = assistant_third_party_default_values; sourceTree = "<group>"; };
|
||||
D79622332B1DFE600037EACD /* DialerBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialerBottomSheet.swift; sourceTree = "<group>"; };
|
||||
D796F1FF2B0BB61A0041115F /* ToastViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastViewModel.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>"; };
|
||||
D7ADF5FF2AFE356400212231 /* Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Avatar.swift; sourceTree = "<group>"; };
|
||||
D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInnerFragment.swift; sourceTree = "<group>"; };
|
||||
D7C365072AEFAB7F00FE6142 /* ContactListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactListBottomSheet.swift; sourceTree = "<group>"; };
|
||||
D7C365092AF001C300FE6142 /* EditContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditContactFragment.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -160,11 +190,13 @@
|
|||
children = (
|
||||
D717071D2AC5922E0037746F /* ColorExtension.swift */,
|
||||
D717071F2AC5989C0037746F /* TextExtension.swift */,
|
||||
D76005F52B0798B00054B79A /* IntExtension.swift */,
|
||||
D74C9D002ACB098C0021626A /* PermissionManager.swift */,
|
||||
D7D1698B2AE66FA500109A5C /* MagicSearchSingleton.swift */,
|
||||
D7C3650D2AF15BF200FE6142 /* PhotoPicker.swift */,
|
||||
D7C48DF32AFA66F900D938CB /* EditContactController.swift */,
|
||||
D732A9082AFD235500DB42BA /* ShareSheetController.swift */,
|
||||
D7ADF5FF2AFE356400212231 /* Avatar.swift */,
|
||||
);
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -201,6 +233,7 @@
|
|||
D70C93DD2AC2D0F60063CA3B /* Localizable.xcstrings */,
|
||||
D719ABBD2ABC67BF00B41C10 /* Preview Content */,
|
||||
D7D24D0C2AC1B4C700C6F35B /* Fonts */,
|
||||
D7ADF6012AFE5C7C00212231 /* Ressources */,
|
||||
);
|
||||
path = Linphone;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -267,14 +300,29 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D72250622ADE9615008FB426 /* HistoryViewModel.swift */,
|
||||
D732A9142B04C7FE00DB42BA /* HistoryListViewModel.swift */,
|
||||
D726E43E2B19E56F0083C415 /* StartCallViewModel.swift */,
|
||||
);
|
||||
path = ViewModel;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D726E4372B1643FF0083C415 /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D726E4382B16440C0083C415 /* ContactAvatarModel.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D72992372ADD7F1C003AF125 /* Fragments */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D72992382ADD7F68003AF125 /* HistoryContactFragment.swift */,
|
||||
D732A90E2B04C3B400DB42BA /* HistoryFragment.swift */,
|
||||
D732A9122B04C7A300DB42BA /* HistoryListFragment.swift */,
|
||||
D732A91A2B061BD900DB42BA /* HistoryListBottomSheet.swift */,
|
||||
D726E43C2B19E4FE0083C415 /* StartCallFragment.swift */,
|
||||
D79622332B1DFE600037EACD /* DialerBottomSheet.swift */,
|
||||
);
|
||||
path = Fragments;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -351,6 +399,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D78290B62ADD38F9004AA85C /* Fragments */,
|
||||
D726E4372B1643FF0083C415 /* Model */,
|
||||
D78290B92ADD409D004AA85C /* ViewModel */,
|
||||
D7A03FBC2ACC2DB60081A588 /* ContactsView.swift */,
|
||||
);
|
||||
|
|
@ -371,10 +420,22 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D7A2EDD52AC18115005D90FC /* SharedMainViewModel.swift */,
|
||||
D796F1FF2B0BB61A0041115F /* ToastViewModel.swift */,
|
||||
);
|
||||
path = Viewmodel;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D7ADF6012AFE5C7C00212231 /* Ressources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D783C77A2B1089B200622CC2 /* assistant_linphone_default_values */,
|
||||
D783C77B2B1089B200622CC2 /* assistant_third_party_default_values */,
|
||||
D732A90A2B0376F500DB42BA /* linphonerc-default */,
|
||||
D732A90B2B0376F500DB42BA /* linphonerc-factory */,
|
||||
);
|
||||
path = Ressources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D7D24D0C2AC1B4C700C6F35B /* Fonts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -464,11 +525,15 @@
|
|||
D7D24D142AC1B4E800C6F35B /* NotoSans-Regular.ttf in Resources */,
|
||||
D7D24D182AC1B4E800C6F35B /* NotoSans-ExtraBold.ttf in Resources */,
|
||||
D7D24D152AC1B4E800C6F35B /* NotoSans-Light.ttf in Resources */,
|
||||
D783C77D2B1089B200622CC2 /* assistant_third_party_default_values in Resources */,
|
||||
D7D24D162AC1B4E800C6F35B /* NotoSans-SemiBold.ttf in Resources */,
|
||||
D7D24D172AC1B4E800C6F35B /* NotoSans-Bold.ttf in Resources */,
|
||||
D719ABBF2ABC67BF00B41C10 /* Preview Assets.xcassets in Resources */,
|
||||
D719ABBB2ABC67BF00B41C10 /* Assets.xcassets in Resources */,
|
||||
D7D24D132AC1B4E800C6F35B /* NotoSans-Medium.ttf in Resources */,
|
||||
D732A90C2B0376F500DB42BA /* linphonerc-default in Resources */,
|
||||
D732A90D2B0376F500DB42BA /* linphonerc-factory in Resources */,
|
||||
D783C77C2B1089B200622CC2 /* assistant_linphone_default_values in Resources */,
|
||||
D70C93DE2AC2D0F60063CA3B /* Localizable.xcstrings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -503,6 +568,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D7C3650E2AF15BF200FE6142 /* PhotoPicker.swift in Sources */,
|
||||
D7ADF6002AFE356400212231 /* Avatar.swift in Sources */,
|
||||
D71707202AC5989C0037746F /* TextExtension.swift in Sources */,
|
||||
D719ABB92ABC67BF00B41C10 /* ContentView.swift in Sources */,
|
||||
D71FCA832AE14D6E00D2E43E /* ContactFragment.swift in Sources */,
|
||||
|
|
@ -510,12 +576,16 @@
|
|||
D750D3392AD3E6EE00EC99C5 /* PopupLoadingView.swift in Sources */,
|
||||
D7E6D0492AE933AD00A57AAF /* FavoriteContactsListFragment.swift in Sources */,
|
||||
D706BA822ADD72D100278F45 /* DeviceRotationViewModifier.swift in Sources */,
|
||||
D732A9132B04C7A300DB42BA /* HistoryListFragment.swift in Sources */,
|
||||
D719ABC92ABC6FD700B41C10 /* CoreContext.swift in Sources */,
|
||||
D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */,
|
||||
D777DBB32AE12C5900565A99 /* ContactsManager.swift in Sources */,
|
||||
D796F2002B0BB61A0041115F /* ToastViewModel.swift in Sources */,
|
||||
D7C3650A2AF001C300FE6142 /* EditContactFragment.swift in Sources */,
|
||||
D7A03FBD2ACC2DB60081A588 /* ContactsView.swift in Sources */,
|
||||
D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */,
|
||||
D732A90F2B04C3B400DB42BA /* HistoryFragment.swift in Sources */,
|
||||
D79622342B1DFE600037EACD /* DialerBottomSheet.swift in Sources */,
|
||||
D78290BB2ADD40B2004AA85C /* ContactViewModel.swift in Sources */,
|
||||
D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */,
|
||||
D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */,
|
||||
|
|
@ -523,10 +593,14 @@
|
|||
D7C48DF42AFA66F900D938CB /* EditContactController.swift in Sources */,
|
||||
D74C9D012ACB098C0021626A /* PermissionManager.swift in Sources */,
|
||||
D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */,
|
||||
D732A9152B04C7FE00DB42BA /* HistoryListViewModel.swift in Sources */,
|
||||
D71FCA7F2AE1397200D2E43E /* ContactsListViewModel.swift in Sources */,
|
||||
D71FCA812AE14CFC00D2E43E /* ContactsListFragment.swift in Sources */,
|
||||
D719ABB72ABC67BF00B41C10 /* LinphoneApp.swift in Sources */,
|
||||
D732A91B2B061BD900DB42BA /* HistoryListBottomSheet.swift in Sources */,
|
||||
D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */,
|
||||
D726E4392B16440C0083C415 /* ContactAvatarModel.swift in Sources */,
|
||||
D76005F62B0798B00054B79A /* IntExtension.swift in Sources */,
|
||||
D7E6D0512AEBDBD500A57AAF /* ContactsListBottomSheet.swift in Sources */,
|
||||
D7A2EDD62AC18115005D90FC /* SharedMainViewModel.swift in Sources */,
|
||||
D7A03FC62ACC458A0081A588 /* SplashScreen.swift in Sources */,
|
||||
|
|
@ -541,7 +615,9 @@
|
|||
D7C48DF62AFCDF4700D938CB /* ContactInnerActionsFragment.swift in Sources */,
|
||||
D72343322ACEFF58009AA24E /* QRScannerController.swift in Sources */,
|
||||
D72343342ACEFFC3009AA24E /* QRScanner.swift in Sources */,
|
||||
D726E43D2B19E4FE0083C415 /* StartCallFragment.swift in Sources */,
|
||||
D72343302ACEFEF8009AA24E /* QrCodeScannerFragment.swift in Sources */,
|
||||
D726E43F2B19E56F0083C415 /* StartCallViewModel.swift in Sources */,
|
||||
D7D1698C2AE66FA500109A5C /* MagicSearchSingleton.swift in Sources */,
|
||||
D72250692ADFBF2D008FB426 /* SideMenu.swift in Sources */,
|
||||
D717071E2AC5922E0037746F /* ColorExtension.swift in Sources */,
|
||||
|
|
|
|||
BIN
Linphone/.DS_Store
vendored
BIN
Linphone/.DS_Store
vendored
Binary file not shown.
21
Linphone/Assets.xcassets/dialer.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/dialer.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "dialer.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
3
Linphone/Assets.xcassets/dialer.imageset/dialer.svg
vendored
Normal file
3
Linphone/Assets.xcassets/dialer.imageset/dialer.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 19.5C10.9 19.5 10 20.4 10 21.5C10 22.6 10.9 23.5 12 23.5C13.1 23.5 14 22.6 14 21.5C14 20.4 13.1 19.5 12 19.5ZM6 1.5C4.9 1.5 4 2.4 4 3.5C4 4.6 4.9 5.5 6 5.5C7.1 5.5 8 4.6 8 3.5C8 2.4 7.1 1.5 6 1.5ZM6 7.5C4.9 7.5 4 8.4 4 9.5C4 10.6 4.9 11.5 6 11.5C7.1 11.5 8 10.6 8 9.5C8 8.4 7.1 7.5 6 7.5ZM6 13.5C4.9 13.5 4 14.4 4 15.5C4 16.6 4.9 17.5 6 17.5C7.1 17.5 8 16.6 8 15.5C8 14.4 7.1 13.5 6 13.5ZM18 5.5C19.1 5.5 20 4.6 20 3.5C20 2.4 19.1 1.5 18 1.5C16.9 1.5 16 2.4 16 3.5C16 4.6 16.9 5.5 18 5.5ZM12 13.5C10.9 13.5 10 14.4 10 15.5C10 16.6 10.9 17.5 12 17.5C13.1 17.5 14 16.6 14 15.5C14 14.4 13.1 13.5 12 13.5ZM18 13.5C16.9 13.5 16 14.4 16 15.5C16 16.6 16.9 17.5 18 17.5C19.1 17.5 20 16.6 20 15.5C20 14.4 19.1 13.5 18 13.5ZM18 7.5C16.9 7.5 16 8.4 16 9.5C16 10.6 16.9 11.5 18 11.5C19.1 11.5 20 10.6 20 9.5C20 8.4 19.1 7.5 18 7.5ZM12 7.5C10.9 7.5 10 8.4 10 9.5C10 10.6 10.9 11.5 12 11.5C13.1 11.5 14 10.6 14 9.5C14 8.4 13.1 7.5 12 7.5ZM12 1.5C10.9 1.5 10 2.4 10 3.5C10 4.6 10.9 5.5 12 5.5C13.1 5.5 14 4.6 14 3.5C14 2.4 13.1 1.5 12 1.5Z" fill="#4E6074"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
21
Linphone/Assets.xcassets/presence-busy.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/presence-busy.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "presence-busy.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
5
Linphone/Assets.xcassets/presence-busy.imageset/presence-busy.svg
vendored
Normal file
5
Linphone/Assets.xcassets/presence-busy.imageset/presence-busy.svg
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="23" height="23" viewBox="0 0 23 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Pastille de présence">
|
||||
<circle id="Ellipse 2" cx="11.7222" cy="11.8889" r="10.1111" fill="#DBB820" stroke="white" stroke-width="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 259 B |
21
Linphone/Assets.xcassets/presence-online.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/presence-online.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "presence-online.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
5
Linphone/Assets.xcassets/presence-online.imageset/presence-online.svg
vendored
Normal file
5
Linphone/Assets.xcassets/presence-online.imageset/presence-online.svg
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="23" height="23" viewBox="0 0 23 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Pastille de présence">
|
||||
<circle id="Ellipse 2" cx="11.7224" cy="11.8889" r="10.1111" fill="#4FAE80" stroke="white" stroke-width="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 259 B |
21
Linphone/Assets.xcassets/trash-simple-red.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/trash-simple-red.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "trash-simple-red.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
3
Linphone/Assets.xcassets/trash-simple-red.imageset/trash-simple-red.svg
vendored
Normal file
3
Linphone/Assets.xcassets/trash-simple-red.imageset/trash-simple-red.svg
vendored
Normal 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="M19.3333 5.76923H4.66667C4.48986 5.76923 4.32029 5.84217 4.19526 5.972C4.07024 6.10184 4 6.27793 4 6.46154C4 6.64515 4.07024 6.82124 4.19526 6.95107C4.32029 7.08091 4.48986 7.15385 4.66667 7.15385H5.33333V19.6154C5.33333 19.9826 5.47381 20.3348 5.72386 20.5945C5.97391 20.8541 6.31304 21 6.66667 21H17.3333C17.687 21 18.0261 20.8541 18.2761 20.5945C18.5262 20.3348 18.6667 19.9826 18.6667 19.6154V7.15385H19.3333C19.5101 7.15385 19.6797 7.08091 19.8047 6.95107C19.9298 6.82124 20 6.64515 20 6.46154C20 6.27793 19.9298 6.10184 19.8047 5.972C19.6797 5.84217 19.5101 5.76923 19.3333 5.76923ZM17.3333 19.6154H6.66667V7.15385H17.3333V19.6154ZM8 3.69231C8 3.5087 8.07024 3.33261 8.19526 3.20277C8.32029 3.07294 8.48986 3 8.66667 3H15.3333C15.5101 3 15.6797 3.07294 15.8047 3.20277C15.9298 3.33261 16 3.5087 16 3.69231C16 3.87592 15.9298 4.05201 15.8047 4.18184C15.6797 4.31168 15.5101 4.38462 15.3333 4.38462H8.66667C8.48986 4.38462 8.32029 4.31168 8.19526 4.18184C8.07024 4.05201 8 3.87592 8 3.69231Z" fill="#DD5F5F"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -22,12 +22,11 @@ import Contacts
|
|||
import SwiftUI
|
||||
import ContactsUI
|
||||
|
||||
final class ContactsManager {
|
||||
final class ContactsManager: ObservableObject {
|
||||
|
||||
static let shared = ContactsManager()
|
||||
|
||||
private var coreContext = CoreContext.shared
|
||||
private var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
private let nativeAddressBookFriendList = "Native address-book"
|
||||
let linphoneAddressBookFriendList = "Linphone address-book"
|
||||
|
|
@ -35,6 +34,10 @@ final class ContactsManager {
|
|||
var friendList: FriendList?
|
||||
var linphoneFriendList: FriendList?
|
||||
|
||||
@Published var lastSearch: [SearchResult] = []
|
||||
@Published var lastSearchSuggestions: [SearchResult] = []
|
||||
@Published var avatarListModel: [ContactAvatarModel] = []
|
||||
|
||||
private init() {
|
||||
fetchContacts()
|
||||
}
|
||||
|
|
@ -119,7 +122,8 @@ final class ContactsManager {
|
|||
&& contact.phoneNumbers.first?.value.stringValue != nil
|
||||
? contact.phoneNumbers.first!.value.stringValue
|
||||
: contact.givenName, lastName: contact.familyName),
|
||||
name: contact.givenName + contact.familyName + String(Int.random(in: 1...1000)) + ((imageThumbnail == nil) ? "-default" : ""),
|
||||
name: contact.givenName + contact.familyName,
|
||||
prefix: ((imageThumbnail == nil) ? "-default" : ""),
|
||||
contact: newContact, linphoneFriend: false, existingFriend: nil)
|
||||
}
|
||||
})
|
||||
|
|
@ -132,7 +136,8 @@ final class ContactsManager {
|
|||
print("\(#function) - access denied")
|
||||
}
|
||||
}
|
||||
self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
||||
MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,16 +169,16 @@ final class ContactsManager {
|
|||
return IBImgViewUserProfile
|
||||
}
|
||||
|
||||
func saveImage(image: UIImage, name: String, contact: Contact, linphoneFriend: Bool, existingFriend: Friend?) {
|
||||
func saveImage(image: UIImage, name: String, prefix: String, contact: Contact, linphoneFriend: Bool, existingFriend: Friend?) {
|
||||
guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else {
|
||||
return
|
||||
}
|
||||
|
||||
awaitDataWrite(data: data, name: name) { _, result in
|
||||
awaitDataWrite(data: data, name: name, prefix: prefix) { _, result in
|
||||
self.saveFriend(result: result, contact: contact, existingFriend: existingFriend) { resultFriend in
|
||||
if resultFriend != nil {
|
||||
if linphoneFriend && existingFriend == nil {
|
||||
_ = self.linphoneFriendList?.addLocalFriend(linphoneFriend: resultFriend!)
|
||||
_ = self.linphoneFriendList?.addFriend(linphoneFriend: resultFriend!)
|
||||
self.linphoneFriendList?.updateSubscriptions()
|
||||
} else if existingFriend == nil {
|
||||
_ = self.friendList?.addLocalFriend(linphoneFriend: resultFriend!)
|
||||
|
|
@ -188,7 +193,7 @@ final class ContactsManager {
|
|||
self.coreContext.doOnCoreQueue { core in
|
||||
do {
|
||||
let friend = try existingFriend ?? core.createFriend()
|
||||
|
||||
|
||||
friend.edit()
|
||||
friend.nativeUri = contact.identifier
|
||||
try friend.setName(newValue: contact.firstName + " " + contact.lastName)
|
||||
|
|
@ -236,7 +241,11 @@ final class ContactsManager {
|
|||
friend.organization = contact.organizationName
|
||||
friend.jobTitle = contact.jobTitle
|
||||
|
||||
try friend.setSubscribesenabled(newValue: false)
|
||||
try friend.setIncsubscribepolicy(newValue: .SPDeny)
|
||||
|
||||
friend.done()
|
||||
|
||||
completion(friend)
|
||||
} catch let error {
|
||||
print("Failed to enumerate contact", error)
|
||||
|
|
@ -253,15 +262,17 @@ final class ContactsManager {
|
|||
return imagePath
|
||||
}
|
||||
|
||||
func awaitDataWrite(data: Data, name: String, completion: @escaping ((), String) -> Void) {
|
||||
func awaitDataWrite(data: Data, name: String, prefix: String,completion: @escaping ((), String) -> Void) {
|
||||
let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
|
||||
|
||||
if directory != nil {
|
||||
DispatchQueue.main.async {
|
||||
do {
|
||||
let urlName = URL(string: name)
|
||||
let imagePath = urlName != nil ? urlName!.absoluteString.replacingOccurrences(of: "%", with: "") : String(Int.random(in: 1...1000))
|
||||
let urlName = URL(string: name + prefix)
|
||||
let imagePath = urlName != nil ? urlName!.absoluteString.replacingOccurrences(of: "%", with: "") : "ImageError"
|
||||
|
||||
let decodedData: () = try data.write(to: directory!.appendingPathComponent(imagePath + ".png"))
|
||||
|
||||
completion(decodedData, imagePath + ".png")
|
||||
} catch {
|
||||
print("Error: ", error)
|
||||
|
|
@ -271,9 +282,24 @@ final class ContactsManager {
|
|||
}
|
||||
}
|
||||
|
||||
func getFriend(contact: Contact) -> Friend? {
|
||||
func getFriendWithContact(contact: Contact) -> Friend? {
|
||||
if friendList != nil {
|
||||
let friend = friendList!.friends.first(where: {$0.nativeUri == contact.identifier})
|
||||
if friend == nil && friendList != nil {
|
||||
return linphoneFriendList!.friends.first(where: {$0.nativeUri == contact.identifier})
|
||||
}
|
||||
return friend
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getFriendWithAddress(address: Address) -> Friend? {
|
||||
if friendList != nil {
|
||||
var friend = friendList!.friends.first(where: {$0.addresses.contains(where: {$0.asStringUriOnly() == address.asStringUriOnly()})})
|
||||
if friend == nil {
|
||||
friend = linphoneFriendList!.friends.first(where: {$0.addresses.contains(where: {$0.asStringUriOnly() == address.asStringUriOnly()})})
|
||||
}
|
||||
return friend
|
||||
} else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -24,17 +24,24 @@ import Combine
|
|||
final class CoreContext: ObservableObject {
|
||||
|
||||
static let shared = CoreContext()
|
||||
private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
var coreVersion: String = Core.getVersion
|
||||
@Published var loggedIn: Bool = false
|
||||
@Published var loggingInProgress: Bool = false
|
||||
@Published var toastMessage: String = ""
|
||||
@Published var defaultAccount: Account?
|
||||
@Published var coreIsStarted: Bool = false
|
||||
|
||||
private var mCore: Core!
|
||||
private var mIteratePublisher: AnyCancellable?
|
||||
|
||||
private init() {}
|
||||
private init() {
|
||||
do {
|
||||
try initialiseCore()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func doOnCoreQueue(synchronous: Bool = false, lambda: @escaping (Core) -> Void) {
|
||||
if synchronous {
|
||||
|
|
@ -53,17 +60,45 @@ final class CoreContext: ObservableObject {
|
|||
|
||||
coreQueue.async {
|
||||
let configDir = Factory.Instance.getConfigDir(context: nil)
|
||||
try? self.mCore = Factory.Instance.createCore(configPath: "\(configDir)/MyConfig", factoryConfigPath: "", systemContext: nil)
|
||||
|
||||
Factory.Instance.logCollectionPath = configDir
|
||||
Factory.Instance.enableLogCollection(state: LogCollectionState.Enabled)
|
||||
|
||||
let url = NSURL(fileURLWithPath: configDir)
|
||||
if let pathComponent = url.appendingPathComponent("linphonerc") {
|
||||
let filePath = pathComponent.path
|
||||
let fileManager = FileManager.default
|
||||
if !fileManager.fileExists(atPath: filePath) {
|
||||
let path = Bundle.main.path(forResource: "linphonerc-default", ofType: nil)
|
||||
if path != nil {
|
||||
try? FileManager.default.copyItem(at: NSURL(fileURLWithPath: path!) as URL, to: pathComponent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let config = try? Factory.Instance.createConfigWithFactory(
|
||||
path: "\(configDir)/linphonerc",
|
||||
factoryPath: Bundle.main.path(forResource: "linphonerc-factory", ofType: nil)
|
||||
)
|
||||
if config != nil {
|
||||
self.mCore = try? Factory.Instance.createCoreWithConfig(config: config!, systemContext: nil)
|
||||
}
|
||||
|
||||
self.mCore.autoIterateEnabled = false
|
||||
self.mCore.friendsDatabasePath = "\(configDir)/friends.db"
|
||||
|
||||
self.mCore.friendListSubscriptionEnabled = true
|
||||
|
||||
self.mCore.publisher?.onGlobalStateChanged?.postOnMainQueue { (cbVal: (core: Core, state: GlobalState, message: String)) in
|
||||
if cbVal.state == GlobalState.On {
|
||||
self.defaultAccount = self.mCore.defaultAccount
|
||||
self.coreIsStarted = true
|
||||
} else if cbVal.state == GlobalState.Off {
|
||||
self.defaultAccount = nil
|
||||
self.coreIsStarted = true
|
||||
}
|
||||
}
|
||||
|
||||
try? self.mCore.start()
|
||||
|
||||
// Create a Core listener to listen for the callback we need
|
||||
|
|
@ -71,9 +106,11 @@ final class CoreContext: ObservableObject {
|
|||
self.mCore.publisher?.onConfiguringStatus?.postOnMainQueue { (cbVal: (core: Core, status: Config.ConfiguringState, message: String)) in
|
||||
NSLog("New configuration state is \(cbVal.status) = \(cbVal.message)\n")
|
||||
if cbVal.status == Config.ConfiguringState.Successful {
|
||||
self.toastMessage = "Successful"
|
||||
ToastViewModel.shared.toastMessage = "Successful"
|
||||
ToastViewModel.shared.displayToast.toggle()
|
||||
} else {
|
||||
self.toastMessage = "Failed"
|
||||
ToastViewModel.shared.toastMessage = "Failed"
|
||||
ToastViewModel.shared.displayToast.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,10 +124,14 @@ final class CoreContext: ObservableObject {
|
|||
if cbVal.state == .Ok {
|
||||
self.loggingInProgress = false
|
||||
self.loggedIn = true
|
||||
if self.mCore.consolidatedPresence != ConsolidatedPresence.Online {
|
||||
self.onForeground()
|
||||
}
|
||||
} else if cbVal.state == .Progress {
|
||||
self.loggingInProgress = true
|
||||
} else {
|
||||
self.toastMessage = "Registration failed"
|
||||
ToastViewModel.shared.toastMessage = "Registration failed"
|
||||
ToastViewModel.shared.displayToast.toggle()
|
||||
self.loggingInProgress = false
|
||||
self.loggedIn = false
|
||||
}
|
||||
|
|
@ -117,6 +158,30 @@ final class CoreContext: ObservableObject {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
func onForeground() {
|
||||
coreQueue.async {
|
||||
// We can't rely on defaultAccount?.params?.isPublishEnabled
|
||||
// as it will be modified by the SDK when changing the presence status
|
||||
if self.mCore.config!.getBool(section: "app", key: "publish_presence", defaultValue: true) {
|
||||
NSLog("App is in foreground, PUBLISHING presence as Online")
|
||||
self.mCore.consolidatedPresence = ConsolidatedPresence.Online
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func onBackground() {
|
||||
coreQueue.async {
|
||||
// We can't rely on defaultAccount?.params?.isPublishEnabled
|
||||
// as it will be modified by the SDK when changing the presence status
|
||||
if self.mCore.config!.getBool(section: "app", key: "publish_presence", defaultValue: true) {
|
||||
NSLog("App is in background, un-PUBLISHING presence info")
|
||||
// We don't use ConsolidatedPresence.Busy but Offline to do an unsubscribe,
|
||||
// Flexisip will handle the Busy status depending on other devices
|
||||
self.mCore.consolidatedPresence = ConsolidatedPresence.Offline
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:enable large_tuple
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.belledonne-communications.linphone</string>
|
||||
<string>group.org.linphone.phone.linphoneExtension</string>
|
||||
<string>group.org.linphone.phone.msgNotification</string>
|
||||
</array>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -23,24 +23,46 @@ import SwiftUI
|
|||
struct LinphoneApp: App {
|
||||
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel()
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
@State private var isActive = false
|
||||
@State private var contactViewModel: ContactViewModel?
|
||||
@State private var editContactViewModel: EditContactViewModel?
|
||||
@State private var historyViewModel: HistoryViewModel?
|
||||
@State private var historyListViewModel: HistoryListViewModel?
|
||||
@State private var startCallViewModel: StartCallViewModel?
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
if isActive {
|
||||
if coreContext.coreIsStarted {
|
||||
if !sharedMainViewModel.welcomeViewDisplayed {
|
||||
WelcomeView(sharedMainViewModel: sharedMainViewModel)
|
||||
WelcomeView()
|
||||
} else if coreContext.defaultAccount == nil || sharedMainViewModel.displayProfileMode {
|
||||
AssistantView(sharedMainViewModel: sharedMainViewModel)
|
||||
.toast(isShowing: $coreContext.toastMessage)
|
||||
} else if coreContext.defaultAccount != nil {
|
||||
ContentView(contactViewModel: ContactViewModel(), editContactViewModel: EditContactViewModel(), historyViewModel: HistoryViewModel())
|
||||
.toast(isShowing: $coreContext.toastMessage)
|
||||
AssistantView()
|
||||
} else if coreContext.defaultAccount != nil
|
||||
&& contactViewModel != nil
|
||||
&& editContactViewModel != nil
|
||||
&& historyViewModel != nil
|
||||
&& historyListViewModel != nil
|
||||
&& startCallViewModel != nil {
|
||||
ContentView(
|
||||
contactViewModel: contactViewModel!,
|
||||
editContactViewModel: editContactViewModel!,
|
||||
historyViewModel: historyViewModel!,
|
||||
historyListViewModel: historyListViewModel!,
|
||||
startCallViewModel: startCallViewModel!
|
||||
)
|
||||
} else {
|
||||
SplashScreen()
|
||||
}
|
||||
} else {
|
||||
SplashScreen(isActive: $isActive)
|
||||
SplashScreen()
|
||||
.onDisappear {
|
||||
contactViewModel = ContactViewModel()
|
||||
editContactViewModel = EditContactViewModel()
|
||||
historyViewModel = HistoryViewModel()
|
||||
historyListViewModel = HistoryListViewModel()
|
||||
startCallViewModel = StartCallViewModel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@
|
|||
},
|
||||
"[notre politique de confidentialité](https://linphone.org/privacy-policy)" : {
|
||||
|
||||
},
|
||||
"*" : {
|
||||
|
||||
},
|
||||
"**Camera** : Pour capturer votre vidéo lors des appels vidéo et conférence." : {
|
||||
|
||||
|
|
@ -45,6 +48,9 @@
|
|||
},
|
||||
"**Notifications** : Pour vous informé quand vous recevez un message ou un appel." : {
|
||||
|
||||
},
|
||||
"#" : {
|
||||
|
||||
},
|
||||
"%lld Book (Example)" : {
|
||||
"extractionState" : "manual",
|
||||
|
|
@ -98,15 +104,57 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"+" : {
|
||||
|
||||
},
|
||||
"0" : {
|
||||
|
||||
},
|
||||
"1" : {
|
||||
|
||||
},
|
||||
"2" : {
|
||||
|
||||
},
|
||||
"3" : {
|
||||
|
||||
},
|
||||
"4" : {
|
||||
|
||||
},
|
||||
"5" : {
|
||||
|
||||
},
|
||||
"6" : {
|
||||
|
||||
},
|
||||
"7" : {
|
||||
|
||||
},
|
||||
"8" : {
|
||||
|
||||
},
|
||||
"9" : {
|
||||
|
||||
},
|
||||
"Accept all" : {
|
||||
|
||||
},
|
||||
"Add a picture" : {
|
||||
|
||||
},
|
||||
"Add the contact" : {
|
||||
|
||||
},
|
||||
"Add to contacts" : {
|
||||
|
||||
},
|
||||
"Add to favourites" : {
|
||||
|
||||
},
|
||||
"All calls will be removed from the history." : {
|
||||
|
||||
},
|
||||
"All contacts" : {
|
||||
|
||||
|
|
@ -139,6 +187,9 @@
|
|||
},
|
||||
"Block the number" : {
|
||||
|
||||
},
|
||||
"Call history" : {
|
||||
|
||||
},
|
||||
"Calls" : {
|
||||
|
||||
|
|
@ -151,6 +202,9 @@
|
|||
},
|
||||
"Chiffrement de bout en bout de tous vos échanges, grâce au mode default vos communications sont à l’abri des regards." : {
|
||||
|
||||
},
|
||||
"Clear logs" : {
|
||||
|
||||
},
|
||||
"Close" : {
|
||||
|
||||
|
|
@ -172,6 +226,9 @@
|
|||
},
|
||||
"Copy number" : {
|
||||
|
||||
},
|
||||
"Copy SIP address" : {
|
||||
|
||||
},
|
||||
"D'accord" : {
|
||||
|
||||
|
|
@ -187,6 +244,12 @@
|
|||
},
|
||||
"Delete %@?" : {
|
||||
|
||||
},
|
||||
"Delete all history" : {
|
||||
|
||||
},
|
||||
"Delete history" : {
|
||||
|
||||
},
|
||||
"Delete this contact" : {
|
||||
|
||||
|
|
@ -199,6 +262,9 @@
|
|||
},
|
||||
"Display Name" : {
|
||||
|
||||
},
|
||||
"Do you really want to delete all calls history?" : {
|
||||
|
||||
},
|
||||
"Domain" : {
|
||||
|
||||
|
|
@ -220,9 +286,6 @@
|
|||
},
|
||||
"En continuant, vous acceptez ces conditions, " : {
|
||||
|
||||
},
|
||||
"En ligne" : {
|
||||
|
||||
},
|
||||
"Error" : {
|
||||
|
||||
|
|
@ -239,7 +302,7 @@
|
|||
"First name*" : {
|
||||
|
||||
},
|
||||
"History Contact fragment" : {
|
||||
"History has been deleted" : {
|
||||
|
||||
},
|
||||
"I prefere create an account" : {
|
||||
|
|
@ -247,6 +310,9 @@
|
|||
},
|
||||
"I understand" : {
|
||||
|
||||
},
|
||||
"Incoming Call" : {
|
||||
|
||||
},
|
||||
"Information" : {
|
||||
|
||||
|
|
@ -286,6 +352,9 @@
|
|||
},
|
||||
"My Profile" : {
|
||||
|
||||
},
|
||||
"New call" : {
|
||||
|
||||
},
|
||||
"New contact" : {
|
||||
|
||||
|
|
@ -293,7 +362,7 @@
|
|||
"Next" : {
|
||||
|
||||
},
|
||||
"No calls for the moment..." : {
|
||||
"No call for the moment..." : {
|
||||
|
||||
},
|
||||
"No contacts for the moment..." : {
|
||||
|
|
@ -313,6 +382,9 @@
|
|||
},
|
||||
"Other actions" : {
|
||||
|
||||
},
|
||||
"Outgoing Call" : {
|
||||
|
||||
},
|
||||
"password" : {
|
||||
"extractionState" : "manual",
|
||||
|
|
@ -345,9 +417,6 @@
|
|||
},
|
||||
"Plus tard" : {
|
||||
|
||||
},
|
||||
"Posts" : {
|
||||
|
||||
},
|
||||
"Pour vous permettre de vous profitez pleinement de Linphone nous avons besoin des autorisations suivantes :" : {
|
||||
|
||||
|
|
@ -366,15 +435,24 @@
|
|||
},
|
||||
"Scan QR code" : {
|
||||
|
||||
},
|
||||
"Search contact or history call" : {
|
||||
|
||||
},
|
||||
"Sécurisé" : {
|
||||
|
||||
},
|
||||
"See all" : {
|
||||
|
||||
},
|
||||
"See contact" : {
|
||||
|
||||
},
|
||||
"See Linphone contact" : {
|
||||
|
||||
},
|
||||
"Send logs" : {
|
||||
|
||||
},
|
||||
"Share" : {
|
||||
|
||||
|
|
@ -384,6 +462,9 @@
|
|||
},
|
||||
"SIP address :" : {
|
||||
|
||||
},
|
||||
"SIP address copied into clipboard" : {
|
||||
|
||||
},
|
||||
"sip.linphone.org" : {
|
||||
|
||||
|
|
@ -393,6 +474,9 @@
|
|||
},
|
||||
"Start" : {
|
||||
|
||||
},
|
||||
"Suggestions" : {
|
||||
|
||||
},
|
||||
"TCP" : {
|
||||
|
||||
|
|
@ -443,6 +527,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Username error" : {
|
||||
|
||||
},
|
||||
"Video Call" : {
|
||||
|
||||
|
|
|
|||
36
Linphone/Ressources/assistant_linphone_default_values
Normal file
36
Linphone/Ressources/assistant_linphone_default_values
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config xmlns="http://www.linphone.org/xsds/lpconfig.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.linphone.org/xsds/lpconfig.xsd lpconfig.xsd">
|
||||
<section name="proxy_default_values">
|
||||
<entry name="avpf" overwrite="true">1</entry>
|
||||
<entry name="dial_escape_plus" overwrite="true">0</entry>
|
||||
<entry name="publish" overwrite="true">1</entry>
|
||||
<entry name="publish_expires" overwrite="true">120</entry>
|
||||
<entry name="quality_reporting_collector" overwrite="true">sip:voip-metrics@sip.linphone.org;transport=tls</entry>
|
||||
<entry name="quality_reporting_enabled" overwrite="true">1</entry>
|
||||
<entry name="quality_reporting_interval" overwrite="true">180</entry>
|
||||
<entry name="reg_expires" overwrite="true">31536000</entry>
|
||||
<entry name="reg_identity" overwrite="true">sip:?@sip.linphone.org</entry>
|
||||
<entry name="reg_proxy" overwrite="true"><sip:sip.linphone.org;transport=tls></entry>
|
||||
<entry name="reg_route" overwrite="true"><sip:sip.linphone.org;transport=tls></entry>
|
||||
<entry name="reg_sendregister" overwrite="true">1</entry>
|
||||
<entry name="nat_policy_ref" overwrite="true">nat_policy_default_values</entry>
|
||||
<entry name="realm" overwrite="true">sip.linphone.org</entry>
|
||||
<entry name="conference_factory_uri" overwrite="true">sip:conference-factory@sip.linphone.org</entry>
|
||||
<entry name="audio_video_conference_factory_uri" overwrite="true">sip:videoconference-factory@sip.linphone.org</entry>
|
||||
<entry name="push_notification_allowed" overwrite="true">1</entry>
|
||||
<entry name="cpim_in_basic_chat_rooms_enabled" overwrite="true">1</entry>
|
||||
<entry name="rtp_bundle" overwrite="true">1</entry>
|
||||
<entry name="lime_server_url" overwrite="true">https://lime.linphone.org/lime-server/lime-server.php</entry>
|
||||
</section>
|
||||
<section name="nat_policy_default_values">
|
||||
<entry name="stun_server" overwrite="true">stun.linphone.org</entry>
|
||||
<entry name="protocols" overwrite="true">stun,ice</entry>
|
||||
</section>
|
||||
<section name="sip">
|
||||
<entry name="media_encryption" overwrite="true">zrtp</entry>
|
||||
<entry name="media_encryption_mandatory" overwrite="true">1</entry>
|
||||
</section>
|
||||
<section name="net">
|
||||
<entry name="friendlist_subscription_enabled" overwrite="true">1</entry>
|
||||
</section>
|
||||
</config>
|
||||
25
Linphone/Ressources/assistant_third_party_default_values
Normal file
25
Linphone/Ressources/assistant_third_party_default_values
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config xmlns="http://www.linphone.org/xsds/lpconfig.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.linphone.org/xsds/lpconfig.xsd lpconfig.xsd">
|
||||
<section name="proxy_default_values">
|
||||
<entry name="avpf" overwrite="true">0</entry>
|
||||
<entry name="dial_escape_plus" overwrite="true">0</entry>
|
||||
<entry name="publish" overwrite="true">0</entry>
|
||||
<entry name="publish_expires" overwrite="true">-1</entry>
|
||||
<entry name="quality_reporting_collector" overwrite="true"></entry>
|
||||
<entry name="quality_reporting_enabled" overwrite="true">0</entry>
|
||||
<entry name="quality_reporting_interval" overwrite="true">0</entry>
|
||||
<entry name="reg_expires" overwrite="true">3600</entry>
|
||||
<entry name="reg_identity" overwrite="true"></entry>
|
||||
<entry name="reg_proxy" overwrite="true"></entry>
|
||||
<entry name="reg_route" overwrite="true"></entry>
|
||||
<entry name="reg_sendregister" overwrite="true">1</entry>
|
||||
<entry name="nat_policy_ref" overwrite="true"></entry>
|
||||
<entry name="realm" overwrite="true"></entry>
|
||||
<entry name="conference_factory_uri" overwrite="true"></entry>
|
||||
<entry name="audio_video_conference_factory_uri" overwrite="true"></entry>
|
||||
<entry name="push_notification_allowed" overwrite="true">0</entry>
|
||||
<entry name="cpim_in_basic_chat_rooms_enabled" overwrite="true">0</entry>
|
||||
<entry name="rtp_bundle" overwrite="true">0</entry>
|
||||
<entry name="lime_server_url" overwrite="true"></entry>
|
||||
</section>
|
||||
</config>
|
||||
39
Linphone/Ressources/linphonerc-default
Normal file
39
Linphone/Ressources/linphonerc-default
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
## Start of default rc
|
||||
|
||||
[sip]
|
||||
contact="Linphone iPhone" <sip:linphone.iphone@unknown-host>
|
||||
use_info=0
|
||||
use_ipv6=1
|
||||
keepalive_period=30000
|
||||
sip_port=-1
|
||||
sip_tcp_port=-1
|
||||
sip_tls_port=-1
|
||||
media_encryption=none
|
||||
update_presence_model_timestamp_before_publish_expires_refresh=1
|
||||
|
||||
[net]
|
||||
#Because dynamic bitrate adaption can increase bitrate, we must allow "no limit"
|
||||
download_bw=0
|
||||
upload_bw=0
|
||||
|
||||
[video]
|
||||
size=vga
|
||||
|
||||
[app]
|
||||
tunnel=disabled
|
||||
auto_start=1
|
||||
record_aware=1
|
||||
|
||||
[tunnel]
|
||||
host=
|
||||
port=443
|
||||
|
||||
[misc]
|
||||
log_collection_upload_server_url=https://www.linphone.org:444/lft.php
|
||||
file_transfer_server_url=https://www.linphone.org:444/lft.php
|
||||
version_check_url_root=https://www.linphone.org/releases
|
||||
max_calls=10
|
||||
conference_layout=1
|
||||
|
||||
## End of default rc
|
||||
65
Linphone/Ressources/linphonerc-factory
Normal file
65
Linphone/Ressources/linphonerc-factory
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
|
||||
## Start of factory rc
|
||||
|
||||
# This file shall not contain path referencing package name, in order to be portable when app is renamed.
|
||||
# Paths to resources must be set from LinphoneManager, after creating LinphoneCore.
|
||||
|
||||
[net]
|
||||
mtu=1300
|
||||
force_ice_disablement=0
|
||||
|
||||
[rtp]
|
||||
accept_any_encryption=1
|
||||
|
||||
[sip]
|
||||
guess_hostname=1
|
||||
register_only_when_network_is_up=1
|
||||
auto_net_state_mon=1
|
||||
auto_answer_replacing_calls=1
|
||||
ping_with_options=0
|
||||
use_cpim=1
|
||||
zrtp_key_agreements_suites=MS_ZRTP_KEY_AGREEMENT_K255_KYB512
|
||||
chat_messages_aggregation_delay=1000
|
||||
chat_messages_aggregation=1
|
||||
update_presence_model_timestamp_before_publish_expires_refresh=1
|
||||
rls_uri=sips:rls@sip.linphone.org
|
||||
|
||||
[sound]
|
||||
#remove this property for any application that is not Linphone public version itself
|
||||
ec_calibrator_cool_tones=1
|
||||
|
||||
[video]
|
||||
displaytype=MSAndroidTextureDisplay
|
||||
auto_resize_preview_to_keep_ratio=1
|
||||
max_conference_size=vga
|
||||
|
||||
[misc]
|
||||
enable_basic_to_client_group_chat_room_migration=0
|
||||
enable_simple_group_chat_message_state=0
|
||||
aggregate_imdn=1
|
||||
notify_each_friend_individually_when_presence_received=0
|
||||
store_friends=0
|
||||
|
||||
[app]
|
||||
activation_code_length=4
|
||||
prefer_basic_chat_room=1
|
||||
record_aware=1
|
||||
|
||||
[account_creator]
|
||||
backend=1
|
||||
# 1 means FlexiAPI, 0 is XMLRPC
|
||||
url=https://subscribe.linphone.org/api/
|
||||
# replace above URL by https://staging-subscribe.linphone.org/api/ for testing
|
||||
|
||||
[lime]
|
||||
lime_update_threshold=86400
|
||||
|
||||
[alerts]
|
||||
alerts_enabled=1
|
||||
|
||||
[assistant]
|
||||
algorithm=SHA-256
|
||||
password_min_length=6
|
||||
username_regex=^[a-z0-9+_.\-]*$
|
||||
|
||||
## End of factory rc
|
||||
|
|
@ -21,9 +21,6 @@ import SwiftUI
|
|||
|
||||
struct SplashScreen: View {
|
||||
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
@Binding var isActive: Bool
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { _ in
|
||||
VStack {
|
||||
|
|
@ -38,17 +35,9 @@ struct SplashScreen: View {
|
|||
|
||||
}
|
||||
.ignoresSafeArea(.all)
|
||||
.onAppear {
|
||||
Task {
|
||||
try coreContext.initialiseCore()
|
||||
withAnimation {
|
||||
self.isActive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SplashScreen(isActive: .constant(true))
|
||||
SplashScreen()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,18 +21,18 @@ import SwiftUI
|
|||
|
||||
struct AssistantView: View {
|
||||
|
||||
@ObservedObject var sharedMainViewModel: SharedMainViewModel
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
|
||||
var body: some View {
|
||||
if sharedMainViewModel.displayProfileMode && coreContext.loggedIn {
|
||||
ProfileModeFragment(sharedMainViewModel: sharedMainViewModel)
|
||||
ProfileModeFragment()
|
||||
} else {
|
||||
LoginFragment(accountLoginViewModel: AccountLoginViewModel(), sharedMainViewModel: sharedMainViewModel)
|
||||
LoginFragment(accountLoginViewModel: AccountLoginViewModel())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
LoginFragment(accountLoginViewModel: AccountLoginViewModel(), sharedMainViewModel: SharedMainViewModel())
|
||||
LoginFragment(accountLoginViewModel: AccountLoginViewModel())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ import SwiftUI
|
|||
struct LoginFragment: View {
|
||||
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
@ObservedObject var accountLoginViewModel: AccountLoginViewModel
|
||||
@ObservedObject var sharedMainViewModel: SharedMainViewModel
|
||||
|
||||
@State private var isSecured: Bool = true
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ struct LoginFragment: View {
|
|||
.padding(.bottom)
|
||||
|
||||
NavigationLink(isActive: $isLinkSIPActive, destination: {
|
||||
ThirdPartySipAccountWarningFragment(sharedMainViewModel: sharedMainViewModel, accountLoginViewModel: accountLoginViewModel)
|
||||
ThirdPartySipAccountWarningFragment(accountLoginViewModel: accountLoginViewModel)
|
||||
}, label: {
|
||||
Text("Use SIP Account")
|
||||
.default_text_style_orange_600(styleSize: 20)
|
||||
|
|
@ -268,7 +268,7 @@ struct LoginFragment: View {
|
|||
let contentPopup3 = Text(" et ")
|
||||
let contentPopup4 = Text("[nos conditions d’utilisation](https://linphone.org/general-terms)").underline()
|
||||
let contentPopup5 = Text(".")
|
||||
PopupView(sharedMainViewModel: sharedMainViewModel, isShowPopup: $isShowPopup,
|
||||
PopupView(isShowPopup: $isShowPopup,
|
||||
title: Text("Conditions de service"),
|
||||
content: contentPopup1 + contentPopup2 + contentPopup3 + contentPopup4 + contentPopup5,
|
||||
titleFirstButton: Text("Deny all"),
|
||||
|
|
@ -283,7 +283,7 @@ struct LoginFragment: View {
|
|||
}
|
||||
|
||||
if coreContext.loggingInProgress {
|
||||
PopupLoadingView(sharedMainViewModel: sharedMainViewModel)
|
||||
PopupLoadingView()
|
||||
.background(.black.opacity(0.65))
|
||||
}
|
||||
}
|
||||
|
|
@ -306,5 +306,5 @@ struct LoginFragment: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
LoginFragment(accountLoginViewModel: AccountLoginViewModel(), sharedMainViewModel: SharedMainViewModel())
|
||||
LoginFragment(accountLoginViewModel: AccountLoginViewModel())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import SwiftUI
|
|||
|
||||
struct PermissionsFragment: View {
|
||||
|
||||
@ObservedObject var sharedMainViewModel: SharedMainViewModel
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
var permissionManager = PermissionManager.shared
|
||||
|
||||
|
|
@ -172,8 +172,7 @@ struct PermissionsFragment: View {
|
|||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
permissionManager.contactsRequestPermission()
|
||||
permissionManager.cameraRequestPermission()
|
||||
permissionManager.getPermissions()
|
||||
} label: {
|
||||
Text("D'accord")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
|
|
@ -193,7 +192,7 @@ struct PermissionsFragment: View {
|
|||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.navigationBarHidden(true)
|
||||
.onReceive(permissionManager.$cameraPermissionGranted, perform: { (granted) in
|
||||
.onReceive(permissionManager.$contactsPermissionGranted, perform: { (granted) in
|
||||
if granted {
|
||||
withAnimation {
|
||||
sharedMainViewModel.changeWelcomeView()
|
||||
|
|
@ -204,5 +203,5 @@ struct PermissionsFragment: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
PermissionsFragment(sharedMainViewModel: SharedMainViewModel())
|
||||
PermissionsFragment()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import SwiftUI
|
|||
|
||||
struct ProfileModeFragment: View {
|
||||
|
||||
@ObservedObject var sharedMainViewModel: SharedMainViewModel
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
@State var options: Int = 1
|
||||
@State private var isShowPopup = false
|
||||
|
|
@ -142,7 +142,7 @@ struct ProfileModeFragment: View {
|
|||
}
|
||||
|
||||
if self.isShowPopup {
|
||||
PopupView(sharedMainViewModel: sharedMainViewModel, isShowPopup: $isShowPopup,
|
||||
PopupView(isShowPopup: $isShowPopup,
|
||||
title: Text(isShowPopupForDefault ? "Default mode" : "Interoperable mode"),
|
||||
content: Text(
|
||||
isShowPopupForDefault
|
||||
|
|
@ -167,5 +167,5 @@ struct ProfileModeFragment: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
ProfileModeFragment(sharedMainViewModel: SharedMainViewModel())
|
||||
ProfileModeFragment()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import SwiftUI
|
|||
struct QrCodeScannerFragment: View {
|
||||
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
|
|
@ -54,13 +55,15 @@ struct QrCodeScannerFragment: View {
|
|||
.edgesIgnoringSafeArea(.all)
|
||||
.navigationBarHidden(true)
|
||||
|
||||
if coreContext.toastMessage == "Successful" {
|
||||
/*
|
||||
if $isShowToast {
|
||||
ZStack {
|
||||
|
||||
}.onAppear {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import SwiftUI
|
|||
|
||||
struct ThirdPartySipAccountLoginFragment: View {
|
||||
|
||||
@ObservedObject var sharedMainViewModel: SharedMainViewModel
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
@ObservedObject var accountLoginViewModel: AccountLoginViewModel
|
||||
|
||||
|
|
@ -233,5 +233,5 @@ struct ThirdPartySipAccountLoginFragment: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
ThirdPartySipAccountLoginFragment(sharedMainViewModel: SharedMainViewModel(), accountLoginViewModel: AccountLoginViewModel())
|
||||
ThirdPartySipAccountLoginFragment(accountLoginViewModel: AccountLoginViewModel())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import SwiftUI
|
|||
|
||||
struct ThirdPartySipAccountWarningFragment: View {
|
||||
|
||||
@ObservedObject var sharedMainViewModel: SharedMainViewModel
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
@ObservedObject var accountLoginViewModel: AccountLoginViewModel
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ struct ThirdPartySipAccountWarningFragment: View {
|
|||
.padding(.horizontal)
|
||||
|
||||
NavigationLink(destination: {
|
||||
ThirdPartySipAccountLoginFragment(sharedMainViewModel: sharedMainViewModel, accountLoginViewModel: accountLoginViewModel)
|
||||
ThirdPartySipAccountLoginFragment(accountLoginViewModel: accountLoginViewModel)
|
||||
}, label: {
|
||||
Text("I understand")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
|
|
@ -178,5 +178,5 @@ struct ThirdPartySipAccountWarningFragment: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
ThirdPartySipAccountWarningFragment(sharedMainViewModel: SharedMainViewModel(), accountLoginViewModel: AccountLoginViewModel())
|
||||
ThirdPartySipAccountWarningFragment(accountLoginViewModel: AccountLoginViewModel())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,17 @@ class AccountLoginViewModel: ObservableObject {
|
|||
func login() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
do {
|
||||
|
||||
if self.domain != "sip.linphone.org" {
|
||||
if let assistantLinphone = Bundle.main.path(forResource: "assistant_third_party_default_values", ofType: nil) {
|
||||
core.loadConfigFromXml(xmlUri: assistantLinphone)
|
||||
}
|
||||
} else {
|
||||
if let assistantLinphone = Bundle.main.path(forResource: "assistant_linphone_default_values", ofType: nil) {
|
||||
core.loadConfigFromXml(xmlUri: assistantLinphone)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the transport protocol to use.
|
||||
// TLS is strongly recommended
|
||||
// Only use UDP if you don't have the choice
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ struct QRScanner: UIViewControllerRepresentable {
|
|||
class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
|
||||
|
||||
private var coreContext = CoreContext.shared
|
||||
private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
@Binding var scanResult: String
|
||||
private var lastResult: String = ""
|
||||
|
|
@ -76,10 +77,12 @@ class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
|
|||
try? core.start()
|
||||
}
|
||||
} else {
|
||||
coreContext.toastMessage = "Invalide URI"
|
||||
ToastViewModel.shared.toastMessage = "Invalide URI"
|
||||
ToastViewModel.shared.displayToast.toggle()
|
||||
}
|
||||
} else {
|
||||
coreContext.toastMessage = "Invalide URI"
|
||||
ToastViewModel.shared.toastMessage = "Invalide URI"
|
||||
ToastViewModel.shared.displayToast.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,9 +34,11 @@ struct ContactFragment: View {
|
|||
@State private var showShareSheet = false
|
||||
|
||||
var body: some View {
|
||||
let indexDisplayed = contactViewModel.indexDisplayedFriend != nil ? contactViewModel.indexDisplayedFriend! : 0
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
ContactInnerFragment(
|
||||
contactAvatarModel: ContactsManager.shared.avatarListModel[indexDisplayed],
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
cnContact: CNContact(),
|
||||
|
|
@ -50,12 +52,13 @@ struct ContactFragment: View {
|
|||
.presentationDetents([.fraction(0.2)])
|
||||
}
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(friendToShare: MagicSearchSingleton.shared.lastSearch[contactViewModel.indexDisplayedFriend!].friend!)
|
||||
ShareSheet(friendToShare: ContactsManager.shared.lastSearch[contactViewModel.indexDisplayedFriend!].friend!)
|
||||
.presentationDetents([.medium])
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
} else {
|
||||
ContactInnerFragment(
|
||||
contactAvatarModel: ContactsManager.shared.avatarListModel[indexDisplayed],
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
cnContact: CNContact(),
|
||||
|
|
@ -68,12 +71,13 @@ struct ContactFragment: View {
|
|||
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
} onDismiss: {}
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(friendToShare: MagicSearchSingleton.shared.lastSearch[contactViewModel.indexDisplayedFriend!].friend!)
|
||||
ShareSheet(friendToShare: ContactsManager.shared.lastSearch[contactViewModel.indexDisplayedFriend!].friend!)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ContactInnerFragment(
|
||||
contactAvatarModel: ContactsManager.shared.avatarListModel[indexDisplayed],
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
cnContact: CNContact(),
|
||||
|
|
@ -86,7 +90,7 @@ struct ContactFragment: View {
|
|||
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
} onDismiss: {}
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(friendToShare: MagicSearchSingleton.shared.lastSearch[contactViewModel.indexDisplayedFriend!].friend!)
|
||||
ShareSheet(friendToShare: ContactsManager.shared.lastSearch[contactViewModel.indexDisplayedFriend!].friend!)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,28 @@
|
|||
//
|
||||
// ContactInnerActionsFragment.swift
|
||||
// Linphone
|
||||
//
|
||||
// Created by Benoît Martins on 09/11/2023.
|
||||
//
|
||||
/*
|
||||
* 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 ContactInnerActionsFragment: View {
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
|
||||
|
|
@ -47,8 +60,8 @@ struct ContactInnerActionsFragment: View {
|
|||
|
||||
if informationIsOpen {
|
||||
VStack(spacing: 0) {
|
||||
if contactViewModel.indexDisplayedFriend != nil && magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
|
||||
ForEach(0..<magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses.count, id: \.self) { index in
|
||||
if contactViewModel.indexDisplayedFriend != nil && contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
|
||||
ForEach(0..<contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses.count, id: \.self) { index in
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
|
|
@ -56,7 +69,7 @@ struct ContactInnerActionsFragment: View {
|
|||
Text("SIP address :")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Text(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses[index].asStringUriOnly().dropFirst(4))
|
||||
Text(contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses[index].asStringUriOnly().dropFirst(4))
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
|
|
@ -81,7 +94,7 @@ struct ContactInnerActionsFragment: View {
|
|||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.stringToCopy = magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses[index].asStringUriOnly()
|
||||
contactViewModel.stringToCopy = contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses[index].asStringUriOnly()
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
|
|
@ -94,8 +107,8 @@ struct ContactInnerActionsFragment: View {
|
|||
}
|
||||
)
|
||||
|
||||
if !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbers.isEmpty
|
||||
|| index < magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses.count - 1 {
|
||||
if !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbers.isEmpty
|
||||
|| index < contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses.count - 1 {
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
|
|
@ -103,14 +116,14 @@ struct ContactInnerActionsFragment: View {
|
|||
}
|
||||
}
|
||||
|
||||
ForEach(0..<magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbers.count, id: \.self) { index in
|
||||
ForEach(0..<contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbers.count, id: \.self) { index in
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
VStack {
|
||||
if magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].label != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].label!.isEmpty {
|
||||
Text("Phone (\(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].label!)) :")
|
||||
if contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].label != nil
|
||||
&& !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].label!.isEmpty {
|
||||
Text("Phone (\(contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].label!)) :")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {
|
||||
|
|
@ -118,7 +131,7 @@ struct ContactInnerActionsFragment: View {
|
|||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
Text(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].phoneNumber)
|
||||
Text(contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].phoneNumber)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
|
|
@ -144,7 +157,7 @@ struct ContactInnerActionsFragment: View {
|
|||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.stringToCopy =
|
||||
magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].phoneNumber
|
||||
contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbersWithLabel[index].phoneNumber
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
|
|
@ -157,7 +170,7 @@ struct ContactInnerActionsFragment: View {
|
|||
}
|
||||
)
|
||||
|
||||
if index < magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbers.count - 1 {
|
||||
if index < contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.phoneNumbers.count - 1 {
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
|
|
@ -174,28 +187,28 @@ struct ContactInnerActionsFragment: View {
|
|||
}
|
||||
|
||||
if contactViewModel.indexDisplayedFriend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& ((magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!.isEmpty)
|
||||
|| (magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle!.isEmpty)) {
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& ((contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization != nil
|
||||
&& !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!.isEmpty)
|
||||
|| (contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle != nil
|
||||
&& !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle!.isEmpty)) {
|
||||
VStack {
|
||||
if magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!.isEmpty {
|
||||
Text("**Company :** \(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!)")
|
||||
if contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization != nil
|
||||
&& !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!.isEmpty {
|
||||
Text("**Company :** \(contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!)")
|
||||
.default_text_style(styleSize: 14)
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
if magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle!.isEmpty {
|
||||
Text("**Job :** \(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle!)")
|
||||
if contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle != nil
|
||||
&& !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle!.isEmpty {
|
||||
Text("**Job :** \(contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.jobTitle!)")
|
||||
.default_text_style(styleSize: 14)
|
||||
.padding(.top,
|
||||
magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!.isEmpty
|
||||
contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization != nil
|
||||
&& !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!.isEmpty
|
||||
? 0 : 15
|
||||
)
|
||||
.padding(.bottom, 15)
|
||||
|
|
@ -226,10 +239,10 @@ struct ContactInnerActionsFragment: View {
|
|||
.background(Color.gray100)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < magicSearch.lastSearch.count
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri!.isEmpty {
|
||||
if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri != nil
|
||||
&& !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri!.isEmpty {
|
||||
Button {
|
||||
actionEditButton()
|
||||
} label: {
|
||||
|
|
@ -253,6 +266,7 @@ struct ContactInnerActionsFragment: View {
|
|||
} else {
|
||||
NavigationLink(destination: EditContactFragment(
|
||||
editContactViewModel: editContactViewModel,
|
||||
contactViewModel: contactViewModel,
|
||||
isShowEditContactFragment: .constant(false),
|
||||
isShowDismissPopup: $isShowDismissPopup)) {
|
||||
HStack {
|
||||
|
|
@ -274,7 +288,7 @@ struct ContactInnerActionsFragment: View {
|
|||
}
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
editContactViewModel.selectedEditFriend = magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend
|
||||
editContactViewModel.selectedEditFriend = contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend
|
||||
editContactViewModel.resetValues()
|
||||
}
|
||||
)
|
||||
|
|
@ -286,22 +300,22 @@ struct ContactInnerActionsFragment: View {
|
|||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
if magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
|
||||
if contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
|
||||
contactViewModel.objectWillChange.send()
|
||||
magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred.toggle()
|
||||
contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred.toggle()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(contactViewModel.indexDisplayedFriend != nil && magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred == true ? "heart-fill" : "heart")
|
||||
Image(contactViewModel.indexDisplayedFriend != nil && contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred == true ? "heart-fill" : "heart")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(contactViewModel.indexDisplayedFriend != nil && magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred == true ? Color.redDanger500 : Color.grayMain2c500)
|
||||
.foregroundStyle(contactViewModel.indexDisplayedFriend != nil && contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred == true ? Color.redDanger500 : Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
Text(contactViewModel.indexDisplayedFriend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred == true
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred == true
|
||||
? "Remove from favourites"
|
||||
: "Add to favourites")
|
||||
.default_text_style(styleSize: 14)
|
||||
|
|
@ -398,7 +412,7 @@ struct ContactInnerActionsFragment: View {
|
|||
*/
|
||||
|
||||
Button {
|
||||
if magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
|
||||
if contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
|
||||
isShowDeletePopup.toggle()
|
||||
}
|
||||
} label: {
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ import ContactsUI
|
|||
|
||||
struct ContactInnerFragment: View {
|
||||
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel()
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
|
||||
@ObservedObject var contactAvatarModel: ContactAvatarModel
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
|
||||
|
|
@ -65,10 +65,10 @@ struct ContactInnerFragment: View {
|
|||
}
|
||||
|
||||
Spacer()
|
||||
if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < magicSearch.lastSearch.count
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri!.isEmpty {
|
||||
if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri != nil
|
||||
&& !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri!.isEmpty {
|
||||
Button(action: {
|
||||
editNativeContact()
|
||||
}, label: {
|
||||
|
|
@ -82,6 +82,7 @@ struct ContactInnerFragment: View {
|
|||
} else {
|
||||
NavigationLink(destination: EditContactFragment(
|
||||
editContactViewModel: editContactViewModel,
|
||||
contactViewModel: contactViewModel,
|
||||
isShowEditContactFragment: .constant(false),
|
||||
isShowDismissPopup: $isShowDismissPopup)) {
|
||||
Image("pencil-simple")
|
||||
|
|
@ -93,7 +94,7 @@ struct ContactInnerFragment: View {
|
|||
}
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
editContactViewModel.selectedEditFriend = magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend
|
||||
editContactViewModel.selectedEditFriend = contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend
|
||||
editContactViewModel.resetValues()
|
||||
}
|
||||
)
|
||||
|
|
@ -109,50 +110,31 @@ struct ContactInnerFragment: View {
|
|||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < magicSearch.lastSearch.count
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.photo != nil
|
||||
&& !magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.photo!.isEmpty {
|
||||
AsyncImage(
|
||||
url: ContactsManager.shared.getImagePath(
|
||||
friendPhotoPath: magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.photo!)) { image in
|
||||
switch image {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
.frame(width: 100, height: 100)
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
case .failure:
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
} else if contactViewModel.indexDisplayedFriend != nil && magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
|
||||
if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.photo != nil
|
||||
&& !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.photo!.isEmpty {
|
||||
Avatar(contactAvatarModel: contactAvatarModel, avatarSize: 100)
|
||||
} else if contactViewModel.indexDisplayedFriend != nil && contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
if contactViewModel.indexDisplayedFriend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend?.name != nil {
|
||||
Text((magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend?.name)!)
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
|
||||
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend?.name != nil {
|
||||
Text((contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend?.name)!)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
|
||||
Text("En ligne")
|
||||
.foregroundStyle(Color.greenSuccess500)
|
||||
Text(contactAvatarModel.lastPresenceInfo)
|
||||
.foregroundStyle(contactAvatarModel.lastPresenceInfo == "Online"
|
||||
? Color.greenSuccess500
|
||||
: Color.orangeWarning600)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
@ -289,7 +271,7 @@ struct ContactInnerFragment: View {
|
|||
let store = CNContactStore()
|
||||
let descriptor = CNContactViewController.descriptorForRequiredKeys()
|
||||
cnContact = try store.unifiedContact(
|
||||
withIdentifier: magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri!,
|
||||
withIdentifier: contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri!,
|
||||
keysToFetch: [descriptor]
|
||||
)
|
||||
|
||||
|
|
@ -304,6 +286,7 @@ struct ContactInnerFragment: View {
|
|||
|
||||
#Preview {
|
||||
ContactInnerFragment(
|
||||
contactAvatarModel: ContactAvatarModel(friend: nil, withPresence: true),
|
||||
contactViewModel: ContactViewModel(),
|
||||
editContactViewModel: EditContactViewModel(),
|
||||
isShowDeletePopup: .constant(false),
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ struct ContactListBottomSheet: View {
|
|||
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
||||
|
|
@ -68,6 +68,10 @@ struct ContactListBottomSheet: View {
|
|||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Success_copied_into_clipboard"
|
||||
ToastViewModel.shared.displayToast.toggle()
|
||||
|
||||
} label: {
|
||||
HStack {
|
||||
Image("copy")
|
||||
|
|
|
|||
|
|
@ -42,10 +42,10 @@ struct ContactsFragment: View {
|
|||
showingSheet: $showingSheet,
|
||||
showShareSheet: $showShareSheet
|
||||
)
|
||||
.presentationDetents([.fraction(0.2)])
|
||||
.presentationDetents([.fraction(0.2)])
|
||||
}
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(friendToShare: contactViewModel.selectedFriendToShare!)
|
||||
ShareSheet(friendToShare: contactViewModel.selectedFriendToShare!)
|
||||
.presentationDetents([.medium])
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,8 @@ import linphonesw
|
|||
|
||||
struct ContactsInnerFragment: View {
|
||||
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
||||
@State private var isFavoriteOpen = true
|
||||
|
|
@ -33,7 +32,7 @@ struct ContactsInnerFragment: View {
|
|||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if !magicSearch.lastSearch.filter({ $0.friend?.starred == true }).isEmpty {
|
||||
if !contactsManager.lastSearch.filter({ $0.friend?.starred == true }).isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("Favourites")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
|
@ -73,19 +72,31 @@ struct ContactsInnerFragment: View {
|
|||
.padding(.top, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
ContactsListFragment(contactViewModel: contactViewModel, contactsListViewModel: ContactsListViewModel(), showingSheet: $showingSheet)
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
.onChange(of: scenePhase) { newPhase in
|
||||
if newPhase == .active {
|
||||
ContactsManager.shared.fetchContacts()
|
||||
print("Active")
|
||||
} else if newPhase == .inactive {
|
||||
print("Inactive")
|
||||
} else if newPhase == .background {
|
||||
print("Background")
|
||||
|
||||
VStack {
|
||||
List {
|
||||
ContactsListFragment(contactViewModel: contactViewModel, contactsListViewModel: ContactsListViewModel(), showingSheet: $showingSheet)}
|
||||
.listStyle(.plain)
|
||||
.overlay(
|
||||
VStack {
|
||||
if contactsManager.lastSearch.isEmpty {
|
||||
Spacer()
|
||||
Image("illus-belledonne")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipped()
|
||||
.padding(.all)
|
||||
Text("No contacts for the moment...")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.all)
|
||||
)
|
||||
}
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ struct ContactsListBottomSheet: View {
|
|||
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
|
|
@ -61,7 +60,8 @@ struct ContactsListBottomSheet: View {
|
|||
if contactViewModel.selectedFriend != nil {
|
||||
contactViewModel.selectedFriend!.starred.toggle()
|
||||
}
|
||||
self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
||||
MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
|
|
|
|||
|
|
@ -21,122 +21,83 @@ import SwiftUI
|
|||
import linphonesw
|
||||
|
||||
struct ContactsListFragment: View {
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var contactsListViewModel: ContactsListViewModel
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
ForEach(0..<magicSearch.lastSearch.count, id: \.self) { index in
|
||||
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)!))
|
||||
.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: ContactsManager.shared.getImagePath(friendPhotoPath: magicSearch.lastSearch[index].friend!.photo!)) { image in
|
||||
switch image {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
.frame(width: 45, height: 45)
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
case .failure:
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
Text((magicSearch.lastSearch[index].friend?.name)!)
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.selectedFriend = magicSearch.lastSearch[index].friend
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
contactViewModel.indexDisplayedFriend = index
|
||||
}
|
||||
}
|
||||
)
|
||||
.buttonStyle(.borderless)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.overlay(
|
||||
VStack {
|
||||
if magicSearch.lastSearch.isEmpty {
|
||||
Spacer()
|
||||
Image("illus-belledonne")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipped()
|
||||
.padding(.all)
|
||||
Text("No contacts for the moment...")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.all)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var contactsListViewModel: ContactsListViewModel
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
ForEach(0..<contactsManager.lastSearch.count, id: \.self) { index in
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
if index == 0
|
||||
|| contactsManager.lastSearch[index].friend?.name!.lowercased().folding(
|
||||
options: .diacriticInsensitive,
|
||||
locale: .current
|
||||
).first
|
||||
!= contactsManager.lastSearch[index-1].friend?.name!.lowercased().folding(
|
||||
options: .diacriticInsensitive,
|
||||
locale: .current
|
||||
).first {
|
||||
Text(
|
||||
String(
|
||||
(contactsManager.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 index < contactsManager.avatarListModel.count
|
||||
&& contactsManager.avatarListModel[index].friend!.photo != nil
|
||||
&& !contactsManager.avatarListModel[index].friend!.photo!.isEmpty {
|
||||
Avatar(contactAvatarModel: contactsManager.avatarListModel[index], avatarSize: 45)
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
Text((contactsManager.lastSearch[index].friend?.name)!)
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.selectedFriend = contactsManager.lastSearch[index].friend
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
contactViewModel.indexDisplayedFriend = index
|
||||
}
|
||||
}
|
||||
)
|
||||
.buttonStyle(.borderless)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel(), showingSheet: .constant(false))
|
||||
ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel(), showingSheet: .constant(false))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,12 @@ import linphonesw
|
|||
struct EditContactFragment: View {
|
||||
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel()
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var contactViewModel: ContactViewModel
|
||||
|
||||
@Binding var isShowEditContactFragment: Bool
|
||||
@Binding var isShowDismissPopup: Bool
|
||||
|
||||
|
|
@ -129,26 +131,9 @@ struct EditContactFragment: View {
|
|||
if editContactViewModel.selectedEditFriend != nil
|
||||
&& editContactViewModel.selectedEditFriend!.photo != nil
|
||||
&& !editContactViewModel.selectedEditFriend!.photo!.isEmpty && selectedImage == nil && !removedImage {
|
||||
AsyncImage(url: ContactsManager.shared.getImagePath(friendPhotoPath: editContactViewModel.selectedEditFriend!.photo!)) { image in
|
||||
switch image {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
.frame(width: 100, height: 100)
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
case .failure:
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
Avatar(contactAvatarModel: ContactAvatarModel(friend: editContactViewModel.selectedEditFriend!, withPresence: false), avatarSize: 100)
|
||||
|
||||
} else if selectedImage == nil {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
|
|
@ -494,7 +479,7 @@ struct EditContactFragment: View {
|
|||
)
|
||||
|
||||
if editContactViewModel.selectedEditFriend != nil && selectedImage == nil &&
|
||||
!removedImage {
|
||||
!removedImage && editContactViewModel.selectedEditFriend!.photo!.suffix(11) != "default.png" {
|
||||
ContactsManager.shared.saveFriend(
|
||||
result: String(editContactViewModel.selectedEditFriend!.photo!.dropFirst(6)),
|
||||
contact: newContact,
|
||||
|
|
@ -505,15 +490,23 @@ struct EditContactFragment: View {
|
|||
image: selectedImage
|
||||
?? ContactsManager.shared.textToImage(
|
||||
firstName: editContactViewModel.firstName, lastName: editContactViewModel.lastName),
|
||||
name: editContactViewModel.firstName
|
||||
+ editContactViewModel.lastName
|
||||
+ String(Int.random(in: 1...1000))
|
||||
+ ((selectedImage == nil) ? "-default" : ""),
|
||||
name: editContactViewModel.firstName
|
||||
+ editContactViewModel.lastName,
|
||||
prefix: ((selectedImage == nil) ? "-default" : ""),
|
||||
contact: newContact, linphoneFriend: true, existingFriend: editContactViewModel.selectedEditFriend)
|
||||
}
|
||||
|
||||
MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
||||
if editContactViewModel.selectedEditFriend != nil && editContactViewModel.selectedEditFriend!.name != editContactViewModel.firstName + " " + editContactViewModel.lastName {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
let result = ContactsManager.shared.lastSearch.firstIndex(where: {
|
||||
$0.friend!.name == newContact.firstName + " " + newContact.lastName
|
||||
})
|
||||
contactViewModel.indexDisplayedFriend = result
|
||||
}
|
||||
}
|
||||
|
||||
delayColorDismiss()
|
||||
if editContactViewModel.selectedEditFriend == nil {
|
||||
withAnimation {
|
||||
|
|
@ -527,5 +520,10 @@ struct EditContactFragment: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
EditContactFragment(editContactViewModel: EditContactViewModel(), isShowEditContactFragment: .constant(false), isShowDismissPopup: .constant(false))
|
||||
EditContactFragment(
|
||||
editContactViewModel: EditContactViewModel(),
|
||||
contactViewModel: ContactViewModel(),
|
||||
isShowEditContactFragment: .constant(false),
|
||||
isShowDismissPopup: .constant(false)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ import SwiftUI
|
|||
import linphonesw
|
||||
|
||||
struct FavoriteContactsListFragment: View {
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var favoriteContactsListViewModel: FavoriteContactsListViewModel
|
||||
|
||||
|
|
@ -32,41 +32,21 @@ struct FavoriteContactsListFragment: View {
|
|||
var body: some View {
|
||||
ScrollView(.horizontal) {
|
||||
HStack {
|
||||
ForEach(0..<magicSearch.lastSearch.count, id: \.self) { index in
|
||||
if magicSearch.lastSearch[index].friend != nil && magicSearch.lastSearch[index].friend!.starred == true {
|
||||
ForEach(0..<contactsManager.lastSearch.count, id: \.self) { index in
|
||||
if contactsManager.lastSearch[index].friend != nil && contactsManager.lastSearch[index].friend!.starred == true {
|
||||
Button {
|
||||
} label: {
|
||||
VStack {
|
||||
if magicSearch.lastSearch[index].friend!.photo != nil
|
||||
&& !magicSearch.lastSearch[index].friend!.photo!.isEmpty {
|
||||
AsyncImage(url: ContactsManager.shared.getImagePath(friendPhotoPath: magicSearch.lastSearch[index].friend!.photo!)
|
||||
) { image in
|
||||
switch image {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
.frame(width: 45, height: 45)
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
case .failure:
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
if contactsManager.lastSearch[index].friend!.photo != nil
|
||||
&& !contactsManager.lastSearch[index].friend!.photo!.isEmpty {
|
||||
Avatar(contactAvatarModel: contactsManager.avatarListModel[index], avatarSize: 45)
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
Text((magicSearch.lastSearch[index].friend?.name)!)
|
||||
Text((contactsManager.lastSearch[index].friend?.name)!)
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame( maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
|
|
@ -74,7 +54,7 @@ struct FavoriteContactsListFragment: View {
|
|||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.selectedFriend = magicSearch.lastSearch[index].friend
|
||||
contactViewModel.selectedFriend = contactsManager.lastSearch[index].friend
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
|
|
|
|||
121
Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift
Normal file
121
Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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 Foundation
|
||||
import linphonesw
|
||||
|
||||
class ContactAvatarModel: ObservableObject {
|
||||
|
||||
let friend: Friend?
|
||||
|
||||
let withPresence: Bool?
|
||||
|
||||
@Published var lastPresenceInfo: String
|
||||
|
||||
@Published var presenceStatus: ConsolidatedPresence
|
||||
|
||||
private var friendDelegate: FriendDelegate?
|
||||
|
||||
init(friend: Friend?, withPresence: Bool?) {
|
||||
self.friend = friend
|
||||
self.withPresence = withPresence
|
||||
if friend != nil &&
|
||||
withPresence == true {
|
||||
self.lastPresenceInfo = ""
|
||||
|
||||
self.presenceStatus = friend!.consolidatedPresence
|
||||
|
||||
if friend!.consolidatedPresence == .Online || friend!.consolidatedPresence == .Busy {
|
||||
if friend!.consolidatedPresence == .Online || friend!.presenceModel!.latestActivityTimestamp != -1 {
|
||||
self.lastPresenceInfo = friend!.consolidatedPresence == .Online ? "Online" : getCallTime(startDate: friend!.presenceModel!.latestActivityTimestamp)
|
||||
} else {
|
||||
self.lastPresenceInfo = "Away"
|
||||
}
|
||||
} else {
|
||||
self.lastPresenceInfo = ""
|
||||
}
|
||||
|
||||
if self.friendDelegate != nil {
|
||||
self.friend!.removeDelegate(delegate: self.friendDelegate!)
|
||||
self.friendDelegate = nil
|
||||
}
|
||||
|
||||
addDelegate()
|
||||
} else {
|
||||
self.lastPresenceInfo = ""
|
||||
self.presenceStatus = .Offline
|
||||
}
|
||||
}
|
||||
|
||||
func addDelegate() {
|
||||
let newFriendDelegate = FriendDelegateStub(
|
||||
onPresenceReceived: { (linphoneFriend: Friend) -> Void in
|
||||
DispatchQueue.main.sync {
|
||||
self.presenceStatus = linphoneFriend.consolidatedPresence
|
||||
if linphoneFriend.consolidatedPresence == .Online || linphoneFriend.consolidatedPresence == .Busy {
|
||||
if linphoneFriend.consolidatedPresence == .Online || linphoneFriend.presenceModel!.latestActivityTimestamp != -1 {
|
||||
self.lastPresenceInfo = linphoneFriend.consolidatedPresence == .Online ? "Online" : self.getCallTime(startDate: linphoneFriend.presenceModel!.latestActivityTimestamp)
|
||||
} else {
|
||||
self.lastPresenceInfo = "Away"
|
||||
}
|
||||
} else {
|
||||
self.lastPresenceInfo = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
friendDelegate = newFriendDelegate
|
||||
if friendDelegate != nil {
|
||||
friend!.addDelegate(delegate: friendDelegate!)
|
||||
}
|
||||
}
|
||||
|
||||
func removeAllDelegate() {
|
||||
if friendDelegate != nil {
|
||||
presenceStatus = .Offline
|
||||
friend!.removeDelegate(delegate: friendDelegate!)
|
||||
friendDelegate = nil
|
||||
}
|
||||
}
|
||||
|
||||
func getCallTime(startDate: time_t) -> String {
|
||||
let timeInterval = TimeInterval(startDate)
|
||||
|
||||
let myNSDate = Date(timeIntervalSince1970: timeInterval)
|
||||
|
||||
if Calendar.current.isDateInToday(myNSDate) {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = Locale.current.identifier == "fr_FR" ? "HH:mm" : "h:mm a"
|
||||
return "Online today at " + formatter.string(from: myNSDate)
|
||||
} else if Calendar.current.isDateInYesterday(myNSDate) {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = Locale.current.identifier == "fr_FR" ? "HH:mm" : "h:mm a"
|
||||
return "Online yesterday at " + formatter.string(from: myNSDate)
|
||||
} else if Calendar.current.isDate(myNSDate, equalTo: .now, toGranularity: .year) {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = Locale.current.identifier == "fr_FR" ? "dd/MM | HH:mm" : "MM/dd | h:mm a"
|
||||
return "Online on " + formatter.string(from: myNSDate)
|
||||
} else {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = Locale.current.identifier == "fr_FR" ? "dd/MM/yy | HH:mm" : "MM/dd/yy | h:mm a"
|
||||
return "Online on " + formatter.string(from: myNSDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -29,9 +29,5 @@ class ContactViewModel: ObservableObject {
|
|||
var selectedFriendToShare: Friend?
|
||||
var selectedFriendToDelete: Friend?
|
||||
|
||||
private var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
init() {
|
||||
magicSearch.searchForContacts(
|
||||
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)}
|
||||
init() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,6 @@
|
|||
import linphonesw
|
||||
|
||||
class ContactsListViewModel: ObservableObject {
|
||||
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,18 +17,26 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// swiftlint:disable type_body_length
|
||||
import SwiftUI
|
||||
import linphonesw
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
var contactManager = ContactsManager.shared
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
@ObservedObject var historyViewModel: HistoryViewModel
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
@ObservedObject var historyListViewModel: HistoryListViewModel
|
||||
@ObservedObject var startCallViewModel: StartCallViewModel
|
||||
|
||||
@State var index = 0
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
|
|
@ -37,9 +45,12 @@ struct ContentView: View {
|
|||
@State private var searchIsActive = false
|
||||
@State private var text = ""
|
||||
@FocusState private var focusedField: Bool
|
||||
@State private var showingDialer = false
|
||||
@State var isMenuOpen = false
|
||||
@State var isShowDeletePopup = false
|
||||
@State var isShowDeleteContactPopup = false
|
||||
@State var isShowDeleteAllHistoryPopup = false
|
||||
@State var isShowEditContactFragment = false
|
||||
@State var isShowStartCallFragment = false
|
||||
@State var isShowDismissPopup = false
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -55,6 +66,7 @@ struct ContentView: View {
|
|||
Spacer()
|
||||
Button(action: {
|
||||
self.index = 0
|
||||
historyViewModel.displayedCall = nil
|
||||
}, label: {
|
||||
VStack {
|
||||
Image("address-book")
|
||||
|
|
@ -134,34 +146,49 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
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")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
if index == 0 {
|
||||
Button {
|
||||
isMenuOpen = false
|
||||
magicSearch.allContact = true
|
||||
MagicSearchSingleton.shared.searchForContacts(
|
||||
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
} label: {
|
||||
HStack {
|
||||
Text("See all")
|
||||
Spacer()
|
||||
if magicSearch.allContact {
|
||||
Image("green-check")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
Button {
|
||||
isMenuOpen = false
|
||||
magicSearch.allContact = false
|
||||
MagicSearchSingleton.shared.searchForContacts(
|
||||
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
} label: {
|
||||
HStack {
|
||||
Text("See Linphone contact")
|
||||
Spacer()
|
||||
if !magicSearch.allContact {
|
||||
Image("green-check")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Button(role: .destructive) {
|
||||
isMenuOpen = false
|
||||
isShowDeleteAllHistoryPopup.toggle()
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Delete all history")
|
||||
Spacer()
|
||||
Image("trash-simple-red")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
|
|
@ -193,9 +220,14 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
text = ""
|
||||
magicSearch.currentFilter = ""
|
||||
magicSearch.searchForContacts(
|
||||
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
||||
if index == 0 {
|
||||
magicSearch.currentFilter = ""
|
||||
MagicSearchSingleton.shared.searchForContacts(
|
||||
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
} else {
|
||||
historyListViewModel.resetFilterCallLogs()
|
||||
}
|
||||
} label: {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
|
|
@ -226,9 +258,13 @@ struct ContentView: View {
|
|||
self.focusedField = true
|
||||
}
|
||||
.onChange(of: text) { newValue in
|
||||
magicSearch.currentFilter = newValue
|
||||
magicSearch.searchForContacts(
|
||||
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
if index == 0 {
|
||||
magicSearch.currentFilter = newValue
|
||||
MagicSearchSingleton.shared.searchForContacts(
|
||||
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
} else {
|
||||
historyListViewModel.filterCallLogs(filter: text)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TextEditor(text: Binding(
|
||||
|
|
@ -252,7 +288,7 @@ struct ContentView: View {
|
|||
}
|
||||
.onChange(of: text) { newValue in
|
||||
magicSearch.currentFilter = newValue
|
||||
magicSearch.searchForContacts(
|
||||
MagicSearchSingleton.shared.searchForContacts(
|
||||
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -281,10 +317,18 @@ struct ContentView: View {
|
|||
historyViewModel: historyViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
isShowEditContactFragment: $isShowEditContactFragment,
|
||||
isShowDeletePopup: $isShowDeletePopup
|
||||
isShowDeletePopup: $isShowDeleteContactPopup
|
||||
)
|
||||
} else if self.index == 1 {
|
||||
HistoryView()
|
||||
HistoryView(
|
||||
historyListViewModel: historyListViewModel,
|
||||
historyViewModel: historyViewModel,
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
index: $index,
|
||||
isShowStartCallFragment: $isShowStartCallFragment,
|
||||
isShowEditContactFragment: $isShowEditContactFragment
|
||||
)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth:
|
||||
|
|
@ -315,6 +359,7 @@ struct ContentView: View {
|
|||
Spacer()
|
||||
Button(action: {
|
||||
self.index = 0
|
||||
historyViewModel.displayedCall = nil
|
||||
}, label: {
|
||||
VStack {
|
||||
Image("address-book")
|
||||
|
|
@ -367,7 +412,7 @@ struct ContentView: View {
|
|||
}
|
||||
}
|
||||
|
||||
if contactViewModel.indexDisplayedFriend != nil || !historyViewModel.historyTitle.isEmpty {
|
||||
if contactViewModel.indexDisplayedFriend != nil || historyViewModel.displayedCall != nil {
|
||||
HStack(spacing: 0) {
|
||||
Spacer()
|
||||
.frame(maxWidth:
|
||||
|
|
@ -381,17 +426,38 @@ struct ContentView: View {
|
|||
ContactFragment(
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
isShowDeletePopup: $isShowDeletePopup,
|
||||
isShowDeletePopup: $isShowDeleteContactPopup,
|
||||
isShowDismissPopup: $isShowDismissPopup
|
||||
)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.gray100)
|
||||
.ignoresSafeArea(.keyboard)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.gray100)
|
||||
.ignoresSafeArea(.keyboard)
|
||||
} else if self.index == 1 {
|
||||
HistoryContactFragment()
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.gray100)
|
||||
.ignoresSafeArea(.keyboard)
|
||||
let fromAddressFriend = historyViewModel.displayedCall != nil ? contactsManager.getFriendWithAddress(address: historyViewModel.displayedCall!.fromAddress!) : nil
|
||||
let toAddressFriend = historyViewModel.displayedCall != nil ? contactsManager.getFriendWithAddress(address: historyViewModel.displayedCall!.toAddress!) : nil
|
||||
let addressFriend = historyViewModel.displayedCall != nil ? (historyViewModel.displayedCall!.dir == .Incoming ? fromAddressFriend : toAddressFriend) : nil
|
||||
|
||||
let contactAvatarModel = addressFriend != nil
|
||||
? ContactsManager.shared.avatarListModel.first(where: {
|
||||
($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy)
|
||||
&& $0.friend!.name == addressFriend!.name
|
||||
&& $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly()
|
||||
})
|
||||
: ContactAvatarModel(friend: nil, withPresence: false)
|
||||
|
||||
HistoryContactFragment(
|
||||
contactAvatarModel: contactAvatarModel!,
|
||||
historyViewModel: historyViewModel,
|
||||
historyListViewModel: historyListViewModel,
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
isShowDeleteAllHistoryPopup: $isShowDeleteAllHistoryPopup,
|
||||
isShowEditContactFragment: $isShowEditContactFragment,
|
||||
indexPage: $index
|
||||
)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.gray100)
|
||||
.ignoresSafeArea(.keyboard)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
|
@ -430,28 +496,45 @@ struct ContentView: View {
|
|||
if isShowEditContactFragment {
|
||||
EditContactFragment(
|
||||
editContactViewModel: editContactViewModel,
|
||||
contactViewModel: contactViewModel,
|
||||
isShowEditContactFragment: $isShowEditContactFragment,
|
||||
isShowDismissPopup: $isShowDismissPopup
|
||||
)
|
||||
.zIndex(3)
|
||||
.transition(.move(edge: .bottom))
|
||||
.onAppear {
|
||||
contactViewModel.indexDisplayedFriend = nil
|
||||
}
|
||||
.zIndex(3)
|
||||
.transition(.move(edge: .bottom))
|
||||
.onAppear {
|
||||
contactViewModel.indexDisplayedFriend = nil
|
||||
}
|
||||
}
|
||||
|
||||
if isShowDeletePopup {
|
||||
PopupView(sharedMainViewModel: SharedMainViewModel(), isShowPopup: $isShowDeletePopup,
|
||||
if isShowStartCallFragment {
|
||||
StartCallFragment(
|
||||
startCallViewModel: startCallViewModel,
|
||||
isShowStartCallFragment: $isShowStartCallFragment,
|
||||
showingDialer: $showingDialer
|
||||
)
|
||||
.zIndex(3)
|
||||
.transition(.move(edge: .bottom))
|
||||
.halfSheet(showSheet: $showingDialer) {
|
||||
DialerBottomSheet(
|
||||
startCallViewModel: startCallViewModel,
|
||||
showingDialer: $showingDialer
|
||||
)
|
||||
} onDismiss: {}
|
||||
}
|
||||
|
||||
if isShowDeleteContactPopup {
|
||||
PopupView(isShowPopup: $isShowDeleteContactPopup,
|
||||
title: Text(
|
||||
contactViewModel.selectedFriend != nil
|
||||
? "Delete \(contactViewModel.selectedFriend!.name!)?"
|
||||
: (contactViewModel.indexDisplayedFriend != nil
|
||||
? "Delete \(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.name!)?"
|
||||
: "Error Name")),
|
||||
contactViewModel.selectedFriend != nil
|
||||
? "Delete \(contactViewModel.selectedFriend!.name!)?"
|
||||
: (contactViewModel.indexDisplayedFriend != nil
|
||||
? "Delete \(contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.name!)?"
|
||||
: "Error Name")),
|
||||
content: Text("This contact will be deleted definitively."),
|
||||
titleFirstButton: Text("Cancel"),
|
||||
actionFirstButton: {
|
||||
self.isShowDeletePopup.toggle()},
|
||||
self.isShowDeleteContactPopup.toggle()},
|
||||
titleSecondButton: Text("Ok"),
|
||||
actionSecondButton: {
|
||||
if contactViewModel.selectedFriendToDelete != nil {
|
||||
|
|
@ -466,24 +549,49 @@ struct ContentView: View {
|
|||
withAnimation {
|
||||
contactViewModel.indexDisplayedFriend = nil
|
||||
}
|
||||
magicSearch.lastSearch[tmpIndex!].friend!.remove()
|
||||
contactsManager.lastSearch[tmpIndex!].friend!.remove()
|
||||
}
|
||||
magicSearch.searchForContacts(
|
||||
MagicSearchSingleton.shared.searchForContacts(
|
||||
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
self.isShowDeletePopup.toggle()
|
||||
self.isShowDeleteContactPopup.toggle()
|
||||
})
|
||||
.background(.black.opacity(0.65))
|
||||
.zIndex(3)
|
||||
.onTapGesture {
|
||||
self.isShowDeletePopup.toggle()
|
||||
self.isShowDeleteContactPopup.toggle()
|
||||
}
|
||||
.onAppear {
|
||||
contactViewModel.selectedFriendToDelete = contactViewModel.selectedFriend
|
||||
}
|
||||
}
|
||||
|
||||
if isShowDeleteAllHistoryPopup {
|
||||
PopupView(isShowPopup: $isShowDeleteContactPopup,
|
||||
title: Text("Do you really want to delete all calls history?"),
|
||||
content: Text("All calls will be removed from the history."),
|
||||
titleFirstButton: Text("Cancel"),
|
||||
actionFirstButton: {
|
||||
self.isShowDeleteAllHistoryPopup.toggle()
|
||||
historyListViewModel.callLogsAddressToDelete = ""
|
||||
},
|
||||
titleSecondButton: Text("Ok"),
|
||||
actionSecondButton: {
|
||||
historyListViewModel.removeCallLogs()
|
||||
self.isShowDeleteAllHistoryPopup.toggle()
|
||||
historyViewModel.displayedCall = nil
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Success_remove_call_logs"
|
||||
ToastViewModel.shared.displayToast.toggle()
|
||||
})
|
||||
.background(.black.opacity(0.65))
|
||||
.zIndex(3)
|
||||
.onTapGesture {
|
||||
self.isShowDeleteAllHistoryPopup.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if isShowDismissPopup {
|
||||
PopupView(sharedMainViewModel: SharedMainViewModel(), isShowPopup: $isShowDismissPopup,
|
||||
PopupView(isShowPopup: $isShowDismissPopup,
|
||||
title: Text("Don’t save modifications?"),
|
||||
content: Text("All modifications will be canceled."),
|
||||
titleFirstButton: Text("Cancel"),
|
||||
|
|
@ -511,6 +619,11 @@ struct ContentView: View {
|
|||
self.isShowDismissPopup.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
//if sharedMainViewModel.displayToast {
|
||||
ToastView()
|
||||
.zIndex(3)
|
||||
//}
|
||||
}
|
||||
}
|
||||
.overlay {
|
||||
|
|
@ -524,13 +637,27 @@ struct ContentView: View {
|
|||
}
|
||||
}
|
||||
.onRotate { newOrientation in
|
||||
if (contactViewModel.indexDisplayedFriend != nil || !historyViewModel.historyTitle.isEmpty) && searchIsActive {
|
||||
if (contactViewModel.indexDisplayedFriend != nil || historyViewModel.displayedCall != nil) && searchIsActive {
|
||||
self.focusedField = false
|
||||
} else if searchIsActive {
|
||||
self.focusedField = true
|
||||
}
|
||||
orientation = newOrientation
|
||||
}
|
||||
.onChange(of: scenePhase) { newPhase in
|
||||
if newPhase == .active {
|
||||
coreContext.onForeground()
|
||||
if !isShowStartCallFragment {
|
||||
contactsManager.fetchContacts()
|
||||
}
|
||||
print("Active")
|
||||
} else if newPhase == .inactive {
|
||||
print("Inactive")
|
||||
} else if newPhase == .background {
|
||||
coreContext.onBackground()
|
||||
print("Background")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openMenu() {
|
||||
|
|
@ -541,5 +668,12 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
ContentView(contactViewModel: ContactViewModel(), editContactViewModel: EditContactViewModel(), historyViewModel: HistoryViewModel())
|
||||
ContentView(
|
||||
contactViewModel: ContactViewModel(),
|
||||
editContactViewModel: EditContactViewModel(),
|
||||
historyViewModel: HistoryViewModel(),
|
||||
historyListViewModel: HistoryListViewModel(),
|
||||
startCallViewModel: StartCallViewModel()
|
||||
)
|
||||
}
|
||||
// swiftlint:enable type_body_length
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import SwiftUI
|
|||
|
||||
struct PopupLoadingView: View {
|
||||
|
||||
@ObservedObject var sharedMainViewModel: SharedMainViewModel
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
|
|
@ -54,6 +54,6 @@ struct PopupLoadingView: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
PopupLoadingView(sharedMainViewModel: SharedMainViewModel())
|
||||
PopupLoadingView()
|
||||
.background(.black.opacity(0.65))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import Photos
|
|||
|
||||
struct PopupView: View {
|
||||
|
||||
@ObservedObject var sharedMainViewModel: SharedMainViewModel
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
var permissionManager = PermissionManager.shared
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ struct PopupView: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
PopupView(sharedMainViewModel: SharedMainViewModel(), isShowPopup: .constant(true),
|
||||
PopupView(isShowPopup: .constant(true),
|
||||
title: Text("Title"),
|
||||
content: Text("Content"),
|
||||
titleFirstButton: Text("Deny all"),
|
||||
|
|
|
|||
|
|
@ -18,8 +18,15 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import linphonesw
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct SideMenu: View {
|
||||
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
|
||||
@State private var coreDelegate: CoreDelegate?
|
||||
|
||||
let width: CGFloat
|
||||
let isOpen: Bool
|
||||
let menuClose: () -> Void
|
||||
|
|
@ -41,9 +48,13 @@ struct SideMenu: View {
|
|||
Text("My Profile").onTapGesture {
|
||||
print("My Profile")
|
||||
}
|
||||
Text("Posts").onTapGesture {
|
||||
print("Posts")
|
||||
Text("Send logs").onTapGesture {
|
||||
sendLogs()
|
||||
}
|
||||
Text("Clear logs").onTapGesture {
|
||||
print("Clear logs")
|
||||
Core.resetLogCollection()
|
||||
}
|
||||
Text("Logout").onTapGesture {
|
||||
print("Logout")
|
||||
}
|
||||
|
|
@ -60,4 +71,41 @@ struct SideMenu: View {
|
|||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
func sendLogs() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.uploadLogCollection()
|
||||
|
||||
let newCoreDelegate = CoreDelegateStub(
|
||||
onLogCollectionUploadStateChanged: { core, logCollectionUploadState, logString in
|
||||
|
||||
if logString.starts(with: "https") {
|
||||
UIPasteboard.general.setValue(
|
||||
logString,
|
||||
forPasteboardType: UTType.plainText.identifier
|
||||
)
|
||||
|
||||
removeAllDelegate()
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Success_copied_into_clipboard"
|
||||
ToastViewModel.shared.displayToast.toggle()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
coreDelegate = newCoreDelegate
|
||||
if coreDelegate != nil {
|
||||
core.addDelegate(delegate: coreDelegate!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeAllDelegate() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
if coreDelegate != nil {
|
||||
core.removeDelegate(delegate: coreDelegate!)
|
||||
coreDelegate = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,30 +19,21 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct ToastView: ViewModifier {
|
||||
struct ToastView: View {
|
||||
|
||||
@ObservedObject var sharedMainViewModel: SharedMainViewModel
|
||||
@ObservedObject private var toastViewModel = ToastViewModel.shared
|
||||
|
||||
@Binding var isShowing: String
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
ZStack {
|
||||
content
|
||||
toastView
|
||||
}
|
||||
}
|
||||
|
||||
private var toastView: some View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
if !isShowing.isEmpty {
|
||||
if toastViewModel.displayToast {
|
||||
HStack {
|
||||
Image(isShowing == "Successful" ? "smiley" : "warning-circle")
|
||||
Image(toastViewModel.toastMessage.contains("Success") ? "check" : "warning-circle")
|
||||
.resizable()
|
||||
.renderingMode(.template)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.foregroundStyle(isShowing == "Successful" ? Color.greenSuccess500 : Color.redDanger500)
|
||||
.foregroundStyle(toastViewModel.toastMessage.contains("Success") ? Color.greenSuccess500 : Color.redDanger500)
|
||||
|
||||
switch isShowing {
|
||||
switch toastViewModel.toastMessage {
|
||||
case "Successful":
|
||||
Text("QR code validated!")
|
||||
.multilineTextAlignment(.center)
|
||||
|
|
@ -50,6 +41,20 @@ struct ToastView: ViewModifier {
|
|||
.default_text_style(styleSize: 15)
|
||||
.padding(8)
|
||||
|
||||
case "Success_remove_call_logs":
|
||||
Text("History has been deleted")
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(Color.greenSuccess500)
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(8)
|
||||
|
||||
case "Success_copied_into_clipboard":
|
||||
Text("SIP address copied into clipboard")
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(Color.greenSuccess500)
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(8)
|
||||
|
||||
case "Failed":
|
||||
Text("Invalid QR code!")
|
||||
.multilineTextAlignment(.center)
|
||||
|
|
@ -85,29 +90,30 @@ struct ToastView: ViewModifier {
|
|||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 50)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isShowing == "Successful" ? Color.greenSuccess500 : Color.redDanger500, lineWidth: 1)
|
||||
.stroke(toastViewModel.toastMessage.contains("Success") ? Color.greenSuccess500 : Color.redDanger500, lineWidth: 1)
|
||||
)
|
||||
.onTapGesture {
|
||||
isShowing = ""
|
||||
withAnimation {
|
||||
toastViewModel.toastMessage = ""
|
||||
toastViewModel.displayToast = false
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
isShowing = ""
|
||||
withAnimation {
|
||||
toastViewModel.toastMessage = ""
|
||||
toastViewModel.displayToast = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.frame(maxWidth: SharedMainViewModel.shared.maxWidth)
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.bottom, 18)
|
||||
.animation(.linear(duration: 0.3), value: isShowing)
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func toast(isShowing: Binding<String>) -> some View {
|
||||
self.modifier(ToastView(sharedMainViewModel: SharedMainViewModel(), isShowing: isShowing))
|
||||
.transition(.move(edge: .top))
|
||||
.padding(.top, 60)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
320
Linphone/UI/Main/History/Fragments/DialerBottomSheet.swift
Normal file
320
Linphone/UI/Main/History/Fragments/DialerBottomSheet.swift
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
* 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 DialerBottomSheet: View {
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@ObservedObject private var magicSearch = MagicSearchSingleton.shared
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
|
||||
@ObservedObject var startCallViewModel: StartCallViewModel
|
||||
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
|
||||
@Binding var showingDialer: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .center, spacing: 0) {
|
||||
VStack(alignment: .center, spacing: 0) {
|
||||
if idiom != .pad && (orientation == .landscapeLeft
|
||||
|| orientation == .landscapeRight
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Close") {
|
||||
showingDialer.toggle()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
.padding(.trailing)
|
||||
} else {
|
||||
Capsule()
|
||||
.fill(Color.grayMain2c300)
|
||||
.frame(width: 75, height: 5)
|
||||
.padding(15)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
Button {
|
||||
startCallViewModel.searchField += "1"
|
||||
} label: {
|
||||
Text("1")
|
||||
.default_text_style(styleSize: 32)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 60)
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
startCallViewModel.searchField += "2"
|
||||
} label: {
|
||||
Text("2")
|
||||
.default_text_style(styleSize: 32)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 60)
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
startCallViewModel.searchField += "3"
|
||||
} label: {
|
||||
Text("3")
|
||||
.default_text_style(styleSize: 32)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 60)
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 60)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
|
||||
HStack {
|
||||
Button {
|
||||
startCallViewModel.searchField += "4"
|
||||
} label: {
|
||||
Text("4")
|
||||
.default_text_style(styleSize: 32)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 60)
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
startCallViewModel.searchField += "5"
|
||||
} label: {
|
||||
Text("5")
|
||||
.default_text_style(styleSize: 32)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 60)
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
startCallViewModel.searchField += "6"
|
||||
} label: {
|
||||
Text("6")
|
||||
.default_text_style(styleSize: 32)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 60)
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 60)
|
||||
.padding(.top, 10)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
|
||||
HStack {
|
||||
Button {
|
||||
startCallViewModel.searchField += "7"
|
||||
} label: {
|
||||
Text("7")
|
||||
.default_text_style(styleSize: 32)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 60)
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
startCallViewModel.searchField += "8"
|
||||
} label: {
|
||||
Text("8")
|
||||
.default_text_style(styleSize: 32)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 60)
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
startCallViewModel.searchField += "9"
|
||||
} label: {
|
||||
Text("9")
|
||||
.default_text_style(styleSize: 32)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 60)
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 60)
|
||||
.padding(.top, 10)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
|
||||
HStack {
|
||||
Button {
|
||||
startCallViewModel.searchField += "*"
|
||||
} label: {
|
||||
Text("*")
|
||||
.default_text_style(styleSize: 32)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 60)
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
} label: {
|
||||
ZStack {
|
||||
Text("0")
|
||||
.default_text_style(styleSize: 32)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 75)
|
||||
.padding(.top, -15)
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
Text("+")
|
||||
.default_text_style(styleSize: 20)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 85)
|
||||
.padding(.bottom, -25)
|
||||
.background(.clear)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
}
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
startCallViewModel.searchField += "+"
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
startCallViewModel.searchField += "0"
|
||||
}
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
startCallViewModel.searchField += "#"
|
||||
} label: {
|
||||
Text("#")
|
||||
.default_text_style(styleSize: 32)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 60, height: 60)
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 60)
|
||||
.padding(.top, 10)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
|
||||
HStack {
|
||||
|
||||
HStack {
|
||||
|
||||
}
|
||||
.frame(width: 60, height: 60)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
} label: {
|
||||
Image("phone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
|
||||
}
|
||||
.frame(width: 90, height: 60)
|
||||
.background(Color.greenSuccess500)
|
||||
.cornerRadius(40)
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
startCallViewModel.searchField = String(startCallViewModel.searchField.dropLast())
|
||||
} label: {
|
||||
Image("backspace-fill")
|
||||
.resizable()
|
||||
.frame(width: 32, height: 32)
|
||||
|
||||
}
|
||||
.frame(width: 60, height: 60)
|
||||
}
|
||||
.padding(.horizontal, 60)
|
||||
.padding(.top, 20)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.background(Color.gray100)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(maxHeight: .infinity)
|
||||
.onRotate { newOrientation in
|
||||
orientation = newOrientation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
DialerBottomSheet(
|
||||
startCallViewModel: StartCallViewModel(), showingDialer: .constant(false)
|
||||
)
|
||||
}
|
||||
|
|
@ -18,17 +18,529 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct HistoryContactFragment: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
Spacer()
|
||||
Text("History Contact fragment")
|
||||
Spacer()
|
||||
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
|
||||
@ObservedObject var contactAvatarModel: ContactAvatarModel
|
||||
@ObservedObject var historyViewModel: HistoryViewModel
|
||||
@ObservedObject var historyListViewModel: HistoryListViewModel
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
|
||||
@State var isMenuOpen = false
|
||||
|
||||
@Binding var isShowDeleteAllHistoryPopup: Bool
|
||||
@Binding var isShowEditContactFragment: Bool
|
||||
@Binding var indexPage: Int
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack(spacing: 1) {
|
||||
Rectangle()
|
||||
.foregroundColor(Color.orangeMain500)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.frame(height: 0)
|
||||
|
||||
HStack {
|
||||
if !(orientation == .landscapeLeft || orientation == .landscapeRight
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.top, 2)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
historyViewModel.displayedCall = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text("Call history")
|
||||
.default_text_style_orange_800(styleSize: 20)
|
||||
|
||||
Spacer()
|
||||
|
||||
Menu {
|
||||
let fromAddressFriend = historyViewModel.displayedCall != nil ? contactsManager.getFriendWithAddress(address: historyViewModel.displayedCall!.fromAddress!) : nil
|
||||
let toAddressFriend = historyViewModel.displayedCall != nil ? contactsManager.getFriendWithAddress(address: historyViewModel.displayedCall!.toAddress!) : nil
|
||||
let addressFriend = historyViewModel.displayedCall != nil ? (historyViewModel.displayedCall!.dir == .Incoming ? fromAddressFriend : toAddressFriend) : nil
|
||||
|
||||
Button {
|
||||
isMenuOpen = false
|
||||
|
||||
if contactsManager.getFriendWithAddress(
|
||||
address: historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing
|
||||
? historyViewModel.displayedCall!.toAddress!
|
||||
: historyViewModel.displayedCall!.fromAddress!
|
||||
) != nil {
|
||||
let addressCall = historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing
|
||||
? historyViewModel.displayedCall!.toAddress!
|
||||
: historyViewModel.displayedCall!.fromAddress!
|
||||
|
||||
let friendIndex = contactsManager.lastSearch.firstIndex(
|
||||
where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressCall.asStringUriOnly()})})
|
||||
if friendIndex != nil {
|
||||
|
||||
withAnimation {
|
||||
historyViewModel.displayedCall = nil
|
||||
indexPage = 0
|
||||
|
||||
contactViewModel.indexDisplayedFriend = friendIndex
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let addressCall = historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing
|
||||
? historyViewModel.displayedCall!.toAddress!
|
||||
: historyViewModel.displayedCall!.fromAddress!
|
||||
|
||||
withAnimation {
|
||||
historyViewModel.displayedCall = nil
|
||||
indexPage = 0
|
||||
|
||||
isShowEditContactFragment.toggle()
|
||||
editContactViewModel.sipAddresses.removeAll()
|
||||
editContactViewModel.sipAddresses.append(String(addressCall.asStringUriOnly().dropFirst(4)))
|
||||
editContactViewModel.sipAddresses.append("")
|
||||
}
|
||||
}
|
||||
|
||||
} label: {
|
||||
HStack {
|
||||
Text(addressFriend != nil ? "See contact" : "Add to contacts")
|
||||
Spacer()
|
||||
Image(addressFriend != nil ? "user-circle" : "plus-circle")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
isMenuOpen = false
|
||||
|
||||
if historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing {
|
||||
UIPasteboard.general.setValue(
|
||||
historyViewModel.displayedCall!.toAddress!.asStringUriOnly().dropFirst(4),
|
||||
forPasteboardType: UTType.plainText.identifier
|
||||
)
|
||||
} else {
|
||||
UIPasteboard.general.setValue(
|
||||
historyViewModel.displayedCall!.fromAddress!.asStringUriOnly().dropFirst(4),
|
||||
forPasteboardType: UTType.plainText.identifier
|
||||
)
|
||||
}
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Success_copied_into_clipboard"
|
||||
ToastViewModel.shared.displayToast.toggle()
|
||||
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Copy SIP address")
|
||||
Spacer()
|
||||
Image("copy")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
isMenuOpen = false
|
||||
|
||||
if historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing {
|
||||
historyListViewModel.callLogsAddressToDelete = historyViewModel.displayedCall!.toAddress!.asStringUriOnly()
|
||||
} else {
|
||||
historyListViewModel.callLogsAddressToDelete = historyViewModel.displayedCall!.fromAddress!.asStringUriOnly()
|
||||
}
|
||||
|
||||
isShowDeleteAllHistoryPopup.toggle()
|
||||
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Delete history")
|
||||
Spacer()
|
||||
Image("trash-simple-red")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Image("dots-three-vertical")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
.padding(.leading)
|
||||
.onTapGesture {
|
||||
isMenuOpen = true
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 50)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 4)
|
||||
.background(.white)
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
|
||||
let fromAddressFriend = historyViewModel.displayedCall != nil ? contactsManager.getFriendWithAddress(address: historyViewModel.displayedCall!.fromAddress!) : nil
|
||||
let toAddressFriend = historyViewModel.displayedCall != nil ? contactsManager.getFriendWithAddress(address: historyViewModel.displayedCall!.toAddress!) : nil
|
||||
let addressFriend = historyViewModel.displayedCall != nil ? (historyViewModel.displayedCall!.dir == .Incoming ? fromAddressFriend : toAddressFriend) : nil
|
||||
|
||||
if historyViewModel.displayedCall != nil
|
||||
&& addressFriend != nil
|
||||
&& addressFriend!.photo != nil
|
||||
&& !addressFriend!.photo!.isEmpty {
|
||||
Avatar(contactAvatarModel: contactAvatarModel, avatarSize: 100)
|
||||
} else if historyViewModel.displayedCall != nil {
|
||||
if historyViewModel.displayedCall!.dir == .Outgoing && historyViewModel.displayedCall!.toAddress != nil {
|
||||
if historyViewModel.displayedCall!.toAddress!.displayName != nil {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: historyViewModel.displayedCall!.toAddress!.displayName!,
|
||||
lastName: historyViewModel.displayedCall!.toAddress!.displayName!.components(separatedBy: " ").count > 1
|
||||
? historyViewModel.displayedCall!.toAddress!.displayName!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(historyViewModel.displayedCall!.toAddress!.displayName!)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
|
||||
Text(historyViewModel.displayedCall!.toAddress!.asStringUriOnly())
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 5)
|
||||
|
||||
Text("")
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 20)
|
||||
} else {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: historyViewModel.displayedCall!.toAddress!.username ?? "Username Error",
|
||||
lastName: historyViewModel.displayedCall!.toAddress!.username!.components(separatedBy: " ").count > 1
|
||||
? historyViewModel.displayedCall!.toAddress!.username!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(historyViewModel.displayedCall!.toAddress!.username ?? "Username Error")
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
|
||||
Text(historyViewModel.displayedCall!.toAddress!.asStringUriOnly())
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 5)
|
||||
|
||||
Text("")
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 20)
|
||||
}
|
||||
|
||||
} else if historyViewModel.displayedCall!.fromAddress != nil {
|
||||
if historyViewModel.displayedCall!.fromAddress!.displayName != nil {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: historyViewModel.displayedCall!.fromAddress!.displayName!,
|
||||
lastName: historyViewModel.displayedCall!.fromAddress!.displayName!.components(separatedBy: " ").count > 1
|
||||
? historyViewModel.displayedCall!.fromAddress!.displayName!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(historyViewModel.displayedCall!.fromAddress!.displayName!)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
|
||||
Text(historyViewModel.displayedCall!.fromAddress!.asStringUriOnly())
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 5)
|
||||
|
||||
Text("")
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 20)
|
||||
} else {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: historyViewModel.displayedCall!.fromAddress!.username ?? "Username Error",
|
||||
lastName: historyViewModel.displayedCall!.fromAddress!.username!.components(separatedBy: " ").count > 1
|
||||
? historyViewModel.displayedCall!.fromAddress!.username!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(historyViewModel.displayedCall!.fromAddress!.username ?? "Username Error")
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
|
||||
Text(historyViewModel.displayedCall!.fromAddress!.asStringUriOnly())
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 5)
|
||||
|
||||
Text("")
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
if historyViewModel.displayedCall != nil
|
||||
&& addressFriend != nil
|
||||
&& addressFriend!.name != nil {
|
||||
Text((addressFriend!.name)!)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
|
||||
if historyViewModel.displayedCall!.dir == .Outgoing && historyViewModel.displayedCall!.toAddress != nil {
|
||||
Text(historyViewModel.displayedCall!.toAddress!.asStringUriOnly())
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 5)
|
||||
} else if historyViewModel.displayedCall!.fromAddress != nil {
|
||||
Text(historyViewModel.displayedCall!.fromAddress!.asStringUriOnly())
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 5)
|
||||
}
|
||||
|
||||
Text(contactAvatarModel.lastPresenceInfo)
|
||||
.foregroundStyle(contactAvatarModel.lastPresenceInfo == "Online"
|
||||
? Color.greenSuccess500
|
||||
: Color.orangeWarning600)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 20)
|
||||
.padding(.top, 5)
|
||||
}
|
||||
}
|
||||
.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)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
|
||||
let addressFriend = historyViewModel.displayedCall != nil
|
||||
? (historyViewModel.displayedCall!.dir == .Incoming ? historyViewModel.displayedCall!.fromAddress!.asStringUriOnly()
|
||||
: historyViewModel.displayedCall!.toAddress!.asStringUriOnly()) : nil
|
||||
|
||||
let callLogsFilter = historyListViewModel.callLogs.filter({ $0.dir == .Incoming
|
||||
? $0.fromAddress!.asStringUriOnly() == addressFriend
|
||||
: $0.toAddress!.asStringUriOnly() == addressFriend })
|
||||
|
||||
ForEach(0..<callLogsFilter.count, id: \.self) { index in
|
||||
HStack {
|
||||
VStack {
|
||||
Image(historyListViewModel.getCallIconResId(callStatus: callLogsFilter[index].status, callDir: callLogsFilter[index].dir))
|
||||
.resizable()
|
||||
.frame(
|
||||
width: historyListViewModel.getCallIconResId(
|
||||
callStatus: callLogsFilter[index].status,
|
||||
callDir: callLogsFilter[index].dir
|
||||
).contains("rejected") ? 12 : 8,
|
||||
height: historyListViewModel.getCallIconResId(
|
||||
callStatus: callLogsFilter[index].status,
|
||||
callDir: callLogsFilter[index].dir
|
||||
).contains("rejected") ? 6 : 8)
|
||||
.padding(.top, 5)
|
||||
Spacer()
|
||||
}
|
||||
|
||||
VStack {
|
||||
Text(historyListViewModel.getCallText(
|
||||
callStatus: callLogsFilter[index].status,
|
||||
callDir: callLogsFilter[index].dir)
|
||||
)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Text(historyListViewModel.getCallTime(startDate: callLogsFilter[index].startDate))
|
||||
.foregroundStyle(
|
||||
callLogsFilter[index].status != .Success
|
||||
? Color.redDanger500
|
||||
: Color.grayMain2c600
|
||||
)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
Text(callLogsFilter[index].duration.convertDurationToString())
|
||||
.default_text_style_300(styleSize: 12)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
}
|
||||
.background(.white)
|
||||
.cornerRadius(15)
|
||||
.padding(.all)
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.background(Color.gray100)
|
||||
}
|
||||
.background(.white)
|
||||
.navigationBarHidden(true)
|
||||
.onRotate { newOrientation in
|
||||
orientation = newOrientation
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
HistoryContactFragment()
|
||||
HistoryContactFragment(
|
||||
contactAvatarModel: ContactAvatarModel(friend: nil, withPresence: false),
|
||||
historyViewModel: HistoryViewModel(),
|
||||
historyListViewModel: HistoryListViewModel(),
|
||||
contactViewModel: ContactViewModel(),
|
||||
editContactViewModel: EditContactViewModel(),
|
||||
isShowDeleteAllHistoryPopup: .constant(false),
|
||||
isShowEditContactFragment: .constant(false),
|
||||
indexPage: .constant(1)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
92
Linphone/UI/Main/History/Fragments/HistoryFragment.swift
Normal file
92
Linphone/UI/Main/History/Fragments/HistoryFragment.swift
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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 HistoryFragment: View {
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@ObservedObject var historyListViewModel: HistoryListViewModel
|
||||
@ObservedObject var historyViewModel: HistoryViewModel
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
|
||||
@State private var showingSheet = false
|
||||
@Binding var index: Int
|
||||
@Binding var isShowEditContactFragment: Bool
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
HistoryListFragment(historyListViewModel: historyListViewModel, historyViewModel: historyViewModel, showingSheet: $showingSheet)
|
||||
.sheet(isPresented: $showingSheet) {
|
||||
HistoryListBottomSheet(
|
||||
historyViewModel: historyViewModel,
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
historyListViewModel: historyListViewModel,
|
||||
showingSheet: $showingSheet,
|
||||
index: $index,
|
||||
isShowEditContactFragment: $isShowEditContactFragment
|
||||
)
|
||||
.presentationDetents([.fraction(0.2)])
|
||||
}
|
||||
} else {
|
||||
HistoryListFragment(historyListViewModel: historyListViewModel, historyViewModel: historyViewModel, showingSheet: $showingSheet)
|
||||
.halfSheet(showSheet: $showingSheet) {
|
||||
HistoryListBottomSheet(
|
||||
historyViewModel: historyViewModel,
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
historyListViewModel: historyListViewModel,
|
||||
showingSheet: $showingSheet,
|
||||
index: $index,
|
||||
isShowEditContactFragment: $isShowEditContactFragment
|
||||
)
|
||||
} onDismiss: {}
|
||||
}
|
||||
} else {
|
||||
HistoryListFragment(historyListViewModel: historyListViewModel, historyViewModel: historyViewModel, showingSheet: $showingSheet)
|
||||
.halfSheet(showSheet: $showingSheet) {
|
||||
HistoryListBottomSheet(
|
||||
historyViewModel: historyViewModel,
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
historyListViewModel: historyListViewModel,
|
||||
showingSheet: $showingSheet,
|
||||
index: $index,
|
||||
isShowEditContactFragment: $isShowEditContactFragment
|
||||
)
|
||||
} onDismiss: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
HistoryFragment(
|
||||
historyListViewModel: HistoryListViewModel(),
|
||||
historyViewModel: HistoryViewModel(),
|
||||
contactViewModel: ContactViewModel(),
|
||||
editContactViewModel: EditContactViewModel(),
|
||||
index: .constant(1),
|
||||
isShowEditContactFragment: .constant(false)
|
||||
)
|
||||
}
|
||||
247
Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift
Normal file
247
Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* 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 HistoryListBottomSheet: View {
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
|
||||
@ObservedObject var historyViewModel: HistoryViewModel
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
@ObservedObject var historyListViewModel: HistoryListViewModel
|
||||
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
@Binding var index: Int
|
||||
@Binding var isShowEditContactFragment: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if idiom != .pad && (orientation == .landscapeLeft
|
||||
|| orientation == .landscapeRight
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Close") {
|
||||
if #available(iOS 16.0, *) {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.trailing)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
Button {
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
index = 0
|
||||
|
||||
if contactsManager.getFriendWithAddress(
|
||||
address: historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.dir == .Outgoing
|
||||
? historyViewModel.selectedCall!.toAddress!
|
||||
: historyViewModel.selectedCall!.fromAddress!
|
||||
) != nil {
|
||||
let addressCall = historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.dir == .Outgoing
|
||||
? historyViewModel.selectedCall!.toAddress!
|
||||
: historyViewModel.selectedCall!.fromAddress!
|
||||
|
||||
let friendIndex = contactsManager.lastSearch.firstIndex(where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressCall.asStringUriOnly()})})
|
||||
if friendIndex != nil {
|
||||
withAnimation {
|
||||
contactViewModel.indexDisplayedFriend = friendIndex
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let addressCall = historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.dir == .Outgoing
|
||||
? historyViewModel.selectedCall!.toAddress!
|
||||
: historyViewModel.selectedCall!.fromAddress!
|
||||
|
||||
withAnimation {
|
||||
isShowEditContactFragment.toggle()
|
||||
editContactViewModel.sipAddresses.removeAll()
|
||||
editContactViewModel.sipAddresses.append(String(addressCall.asStringUriOnly().dropFirst(4)))
|
||||
editContactViewModel.sipAddresses.append("")
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
if contactsManager.getFriendWithAddress(
|
||||
address: historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.dir == .Outgoing
|
||||
? historyViewModel.selectedCall!.toAddress!
|
||||
: historyViewModel.selectedCall!.fromAddress!
|
||||
) != nil {
|
||||
Image("user-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text("See contact")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
} else {
|
||||
Image("plus-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text("Add the contact")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Button {
|
||||
if historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.dir == .Outgoing {
|
||||
UIPasteboard.general.setValue(
|
||||
historyViewModel.selectedCall!.toAddress!.asStringUriOnly().dropFirst(4),
|
||||
forPasteboardType: UTType.plainText.identifier
|
||||
)
|
||||
} else {
|
||||
UIPasteboard.general.setValue(
|
||||
historyViewModel.selectedCall!.fromAddress!.asStringUriOnly().dropFirst(4),
|
||||
forPasteboardType: UTType.plainText.identifier
|
||||
)
|
||||
}
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Success_copied_into_clipboard"
|
||||
ToastViewModel.shared.displayToast.toggle()
|
||||
|
||||
} label: {
|
||||
HStack {
|
||||
Image("copy")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text("Copy SIP address")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Button {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
if historyViewModel.selectedCall != nil {
|
||||
core.removeCallLog(callLog: historyViewModel.selectedCall!)
|
||||
historyListViewModel.removeCallLog(callLog: historyViewModel.selectedCall!)
|
||||
}
|
||||
}
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text("Delete")
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
}
|
||||
.background(Color.gray100)
|
||||
.frame(maxWidth: .infinity)
|
||||
.onRotate { newOrientation in
|
||||
orientation = newOrientation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
HistoryListBottomSheet(
|
||||
historyViewModel: HistoryViewModel(),
|
||||
contactViewModel: ContactViewModel(),
|
||||
editContactViewModel: EditContactViewModel(),
|
||||
historyListViewModel: HistoryListViewModel(),
|
||||
showingSheet: .constant(false),
|
||||
index: .constant(1),
|
||||
isShowEditContactFragment: .constant(false)
|
||||
)
|
||||
}
|
||||
197
Linphone/UI/Main/History/Fragments/HistoryListFragment.swift
Normal file
197
Linphone/UI/Main/History/Fragments/HistoryListFragment.swift
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* 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 HistoryListFragment: View {
|
||||
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
|
||||
@ObservedObject var historyListViewModel: HistoryListViewModel
|
||||
@ObservedObject var historyViewModel: HistoryViewModel
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
ForEach(0..<historyListViewModel.callLogs.count, id: \.self) { index in
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
let fromAddressFriend = contactsManager.getFriendWithAddress(address: historyListViewModel.callLogs[index].fromAddress!)
|
||||
let toAddressFriend = contactsManager.getFriendWithAddress(address: historyListViewModel.callLogs[index].toAddress!)
|
||||
let addressFriend = historyListViewModel.callLogs[index].dir == .Incoming ? fromAddressFriend : toAddressFriend
|
||||
|
||||
let contactAvatarModel = addressFriend != nil
|
||||
? ContactsManager.shared.avatarListModel.first(where: {
|
||||
($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy)
|
||||
&& $0.friend!.name == addressFriend!.name
|
||||
&& $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly()
|
||||
})
|
||||
: ContactAvatarModel(friend: nil, withPresence: false)
|
||||
|
||||
if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty {
|
||||
if contactAvatarModel != nil {
|
||||
Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 45)
|
||||
}
|
||||
} else {
|
||||
if historyListViewModel.callLogs[index].dir == .Outgoing && historyListViewModel.callLogs[index].toAddress != nil {
|
||||
if historyListViewModel.callLogs[index].toAddress!.displayName != nil {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: historyListViewModel.callLogs[index].toAddress!.displayName!,
|
||||
lastName: historyListViewModel.callLogs[index].toAddress!.displayName!.components(separatedBy: " ").count > 1
|
||||
? historyListViewModel.callLogs[index].toAddress!.displayName!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
} else {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: historyListViewModel.callLogs[index].toAddress!.username ?? "Username Error",
|
||||
lastName: historyListViewModel.callLogs[index].toAddress!.username!.components(separatedBy: " ").count > 1
|
||||
? historyListViewModel.callLogs[index].toAddress!.username!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
} else if historyListViewModel.callLogs[index].fromAddress != nil {
|
||||
if historyListViewModel.callLogs[index].fromAddress!.displayName != nil {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: historyListViewModel.callLogs[index].fromAddress!.displayName!,
|
||||
lastName: historyListViewModel.callLogs[index].fromAddress!.displayName!.components(separatedBy: " ").count > 1
|
||||
? historyListViewModel.callLogs[index].fromAddress!.displayName!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
} else {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: historyListViewModel.callLogs[index].fromAddress!.username ?? "Username Error",
|
||||
lastName: historyListViewModel.callLogs[index].fromAddress!.username!.components(separatedBy: " ").count > 1
|
||||
? historyListViewModel.callLogs[index].fromAddress!.username!.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
let fromAddressFriend = contactsManager.getFriendWithAddress(address: historyListViewModel.callLogs[index].fromAddress!)
|
||||
let toAddressFriend = contactsManager.getFriendWithAddress(address: historyListViewModel.callLogs[index].toAddress!)
|
||||
let addressFriend = historyListViewModel.callLogs[index].dir == .Incoming ? fromAddressFriend : toAddressFriend
|
||||
|
||||
if addressFriend != nil {
|
||||
Text(addressFriend!.name!)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
} else {
|
||||
if historyListViewModel.callLogs[index].dir == .Outgoing && historyListViewModel.callLogs[index].toAddress != nil {
|
||||
Text(historyListViewModel.callLogs[index].toAddress!.displayName != nil
|
||||
? historyListViewModel.callLogs[index].toAddress!.displayName!
|
||||
: historyListViewModel.callLogs[index].toAddress!.username!)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
} else if historyListViewModel.callLogs[index].fromAddress != nil {
|
||||
Text(historyListViewModel.callLogs[index].fromAddress!.displayName != nil
|
||||
? historyListViewModel.callLogs[index].fromAddress!.displayName!
|
||||
: historyListViewModel.callLogs[index].fromAddress!.username!)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Image(historyListViewModel.getCallIconResId(callStatus: historyListViewModel.callLogs[index].status, callDir: historyListViewModel.callLogs[index].dir))
|
||||
.resizable()
|
||||
.frame(
|
||||
width: historyListViewModel.getCallIconResId(callStatus: historyListViewModel.callLogs[index].status, callDir: historyListViewModel.callLogs[index].dir).contains("rejected") ? 12 : 8,
|
||||
height: historyListViewModel.getCallIconResId(callStatus: historyListViewModel.callLogs[index].status, callDir: historyListViewModel.callLogs[index].dir).contains("rejected") ? 6 : 8)
|
||||
|
||||
Text(historyListViewModel.getCallTime(startDate: historyListViewModel.callLogs[index].startDate))
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Image("phone")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.trailing, 5)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.listRowInsets(EdgeInsets(top: 5, leading: 20, bottom: 5, trailing: 20))
|
||||
.listRowSeparator(.hidden)
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
historyViewModel.selectedCall = historyListViewModel.callLogs[index]
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
historyViewModel.displayedCall = historyListViewModel.callLogs[index]
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.overlay(
|
||||
VStack {
|
||||
if historyListViewModel.callLogs.isEmpty {
|
||||
Spacer()
|
||||
Image("illus-belledonne")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipped()
|
||||
.padding(.all)
|
||||
Text("No call for the moment...")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.all)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
HistoryListFragment(historyListViewModel: HistoryListViewModel(), historyViewModel: HistoryViewModel(), showingSheet: .constant(false))
|
||||
}
|
||||
245
Linphone/UI/Main/History/Fragments/StartCallFragment.swift
Normal file
245
Linphone/UI/Main/History/Fragments/StartCallFragment.swift
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* 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 StartCallFragment: View {
|
||||
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
@ObservedObject var startCallViewModel: StartCallViewModel
|
||||
|
||||
@Binding var isShowStartCallFragment: Bool
|
||||
@Binding var showingDialer: Bool
|
||||
|
||||
@FocusState var isSearchFieldFocused: Bool
|
||||
@State private var hasTimeElapsed = false
|
||||
@State private var delayedColor = Color.white
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
VStack(spacing: 1) {
|
||||
|
||||
Rectangle()
|
||||
.foregroundColor(delayedColor)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.frame(height: 0)
|
||||
.task(delayColor)
|
||||
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.top, 2)
|
||||
.onTapGesture {
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
|
||||
magicSearch.searchForContacts(
|
||||
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
}
|
||||
|
||||
startCallViewModel.searchField = ""
|
||||
magicSearch.currentFilterSuggestions = ""
|
||||
delayColorDismiss()
|
||||
withAnimation {
|
||||
isShowStartCallFragment.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
Text("New call")
|
||||
.multilineTextAlignment(.leading)
|
||||
.default_text_style_orange_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 50)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 4)
|
||||
.background(.white)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
ZStack(alignment: .trailing) {
|
||||
TextField("Search contact or history call", text: $startCallViewModel.searchField)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.focused($isSearchFieldFocused)
|
||||
.padding(.horizontal, 30)
|
||||
.onChange(of: startCallViewModel.searchField) { newValue in
|
||||
magicSearch.currentFilterSuggestions = newValue
|
||||
magicSearch.searchForSuggestions()
|
||||
}
|
||||
|
||||
HStack {
|
||||
Button(action: {
|
||||
}, label: {
|
||||
Image("magnifying-glass")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
})
|
||||
|
||||
Spacer()
|
||||
|
||||
if startCallViewModel.searchField.isEmpty {
|
||||
Button(action: {
|
||||
isSearchFieldFocused = false
|
||||
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
|
||||
showingDialer.toggle()
|
||||
}
|
||||
}, label: {
|
||||
Image(!showingDialer ? "dialer" : "keyboard")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
})
|
||||
} else {
|
||||
Button(action: {
|
||||
startCallViewModel.searchField = ""
|
||||
magicSearch.currentFilterSuggestions = ""
|
||||
magicSearch.searchForSuggestions()
|
||||
}, label: {
|
||||
Image("x")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 15)
|
||||
.padding(.vertical, 10)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isSearchFieldFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.vertical)
|
||||
.padding(.horizontal)
|
||||
|
||||
ScrollView {
|
||||
if !ContactsManager.shared.lastSearch.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("All contacts")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
|
||||
ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel(), showingSheet: .constant(false))
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("Suggestions")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
suggestionsList
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.background(.white)
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
|
||||
@Sendable private func delayColor() async {
|
||||
try? await Task.sleep(nanoseconds: 250_000_000)
|
||||
delayedColor = Color.orangeMain500
|
||||
}
|
||||
|
||||
func delayColorDismiss() {
|
||||
Task {
|
||||
try? await Task.sleep(nanoseconds: 80_000_000)
|
||||
delayedColor = .white
|
||||
}
|
||||
}
|
||||
|
||||
var suggestionsList: some View {
|
||||
ForEach(0..<contactsManager.lastSearchSuggestions.count, id: \.self) { index in
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
if index < contactsManager.lastSearchSuggestions.count
|
||||
&& contactsManager.lastSearchSuggestions[index].address != nil
|
||||
&& contactsManager.lastSearchSuggestions[index].address!.username != nil {
|
||||
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: contactsManager.lastSearchSuggestions[index].address!.username!,
|
||||
lastName: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(contactsManager.lastSearchSuggestions[index].address?.username ?? "")
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text("Username error")
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
|
||||
}
|
||||
)
|
||||
.buttonStyle(.borderless)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
StartCallFragment(startCallViewModel: StartCallViewModel(), isShowStartCallFragment: .constant(true), showingDialer: .constant(false))
|
||||
}
|
||||
|
|
@ -18,25 +18,45 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import linphonesw
|
||||
|
||||
struct HistoryView: View {
|
||||
|
||||
@ObservedObject var historyListViewModel: HistoryListViewModel
|
||||
@ObservedObject var historyViewModel: HistoryViewModel
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
|
||||
@Binding var index: Int
|
||||
@Binding var isShowStartCallFragment: Bool
|
||||
@Binding var isShowEditContactFragment: Bool
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack(spacing: 0) {
|
||||
VStack {
|
||||
Spacer()
|
||||
Image("illus-belledonne")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipped()
|
||||
.padding(.all)
|
||||
Text("No calls for the moment...")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
Spacer()
|
||||
Spacer()
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
HistoryFragment(
|
||||
historyListViewModel: historyListViewModel,
|
||||
historyViewModel: historyViewModel,
|
||||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
index: $index,
|
||||
isShowEditContactFragment: $isShowEditContactFragment
|
||||
)
|
||||
|
||||
Button {
|
||||
withAnimation {
|
||||
MagicSearchSingleton.shared.searchForSuggestions()
|
||||
isShowStartCallFragment.toggle()
|
||||
}
|
||||
} label: {
|
||||
Image("phone-plus")
|
||||
.padding()
|
||||
.background(.white)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
|
||||
}
|
||||
.padding(.all)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
|
|
@ -44,5 +64,12 @@ struct HistoryView: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
HistoryView()
|
||||
HistoryFragment(
|
||||
historyListViewModel: HistoryListViewModel(),
|
||||
historyViewModel: HistoryViewModel(),
|
||||
contactViewModel: ContactViewModel(),
|
||||
editContactViewModel: EditContactViewModel(),
|
||||
index: .constant(1),
|
||||
isShowEditContactFragment: .constant(false)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
222
Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift
Normal file
222
Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* 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 HistoryListViewModel: ObservableObject {
|
||||
|
||||
private var coreContext = CoreContext.shared
|
||||
|
||||
@Published var callLogs: [CallLog] = []
|
||||
var callLogsTmp: [CallLog] = []
|
||||
|
||||
@Published private var coreDelegate: CoreDelegate?
|
||||
|
||||
var callLogsAddressToDelete = ""
|
||||
|
||||
init() {
|
||||
removeAllDelegate()
|
||||
computeCallLogsList()
|
||||
}
|
||||
|
||||
func computeCallLogsList() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
let account = core.defaultAccount
|
||||
let logs = account?.callLogs != nil ? account!.callLogs : core.callLogs
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.callLogs.removeAll()
|
||||
self.callLogsTmp.removeAll()
|
||||
|
||||
logs.forEach { log in
|
||||
self.callLogs.append(log)
|
||||
self.callLogsTmp.append(log)
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.coreDelegate = CoreDelegateStub(
|
||||
onCallLogUpdated: { (_: Core, _: CallLog) -> Void in
|
||||
DispatchQueue.main.sync {
|
||||
let account = core.defaultAccount
|
||||
let logs = account != nil ? account!.callLogs : core.callLogs
|
||||
|
||||
self.callLogs.removeAll()
|
||||
self.callLogsTmp.removeAll()
|
||||
|
||||
logs.forEach { log in
|
||||
self.callLogs.append(log)
|
||||
self.callLogsTmp.append(log)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
if self.coreDelegate != nil {
|
||||
core.addDelegate(delegate: self.coreDelegate!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getCallIconResId(callStatus: Call.Status, callDir: Call.Dir) -> String {
|
||||
switch callStatus {
|
||||
case Call.Status.Missed:
|
||||
if callDir == .Outgoing {
|
||||
"outgoing-call-missed"
|
||||
} else {
|
||||
"incoming-call-missed"
|
||||
}
|
||||
|
||||
case Call.Status.Success:
|
||||
if callDir == .Outgoing {
|
||||
"outgoing-call"
|
||||
} else {
|
||||
"incoming-call"
|
||||
}
|
||||
|
||||
default:
|
||||
if callDir == .Outgoing {
|
||||
"outgoing-call-rejected"
|
||||
} else {
|
||||
"incoming-call-rejected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getCallText(callStatus: Call.Status, callDir: Call.Dir) -> String {
|
||||
switch callStatus {
|
||||
case Call.Status.Missed:
|
||||
if callDir == .Outgoing {
|
||||
"Outgoing Call"
|
||||
} else {
|
||||
"Missed Call"
|
||||
}
|
||||
|
||||
case Call.Status.Success:
|
||||
if callDir == .Outgoing {
|
||||
"Outgoing Call"
|
||||
} else {
|
||||
"Incoming Call"
|
||||
}
|
||||
|
||||
default:
|
||||
if callDir == .Outgoing {
|
||||
"Outgoing Call"
|
||||
} else {
|
||||
"Incoming Call"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getCallTime(startDate: time_t) -> String {
|
||||
let timeInterval = TimeInterval(startDate)
|
||||
|
||||
let myNSDate = Date(timeIntervalSince1970: timeInterval)
|
||||
|
||||
if Calendar.current.isDateInToday(myNSDate) {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = Locale.current.identifier == "fr_FR" ? "HH:mm" : "h:mm a"
|
||||
return "Today | " + formatter.string(from: myNSDate)
|
||||
} else if Calendar.current.isDateInYesterday(myNSDate) {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = Locale.current.identifier == "fr_FR" ? "HH:mm" : "h:mm a"
|
||||
return "Yesterday | " + formatter.string(from: myNSDate)
|
||||
} else if Calendar.current.isDate(myNSDate, equalTo: .now, toGranularity: .year) {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = Locale.current.identifier == "fr_FR" ? "dd/MM | HH:mm" : "MM/dd | h:mm a"
|
||||
return formatter.string(from: myNSDate)
|
||||
} else {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = Locale.current.identifier == "fr_FR" ? "dd/MM/yy | HH:mm" : "MM/dd/yy | h:mm a"
|
||||
return formatter.string(from: myNSDate)
|
||||
}
|
||||
}
|
||||
|
||||
func filterCallLogs(filter: String) {
|
||||
callLogs.removeAll()
|
||||
callLogsTmp.forEach { callLog in
|
||||
if callLog.dir == .Outgoing && callLog.toAddress != nil {
|
||||
if callLog.toAddress!.username != nil && callLog.toAddress!.username!.contains(filter) {
|
||||
callLogs.append(callLog)
|
||||
} else if callLog.toAddress!.displayName != nil && callLog.toAddress!.displayName!.contains(filter) {
|
||||
callLogs.append(callLog)
|
||||
}
|
||||
} else if callLog.fromAddress != nil {
|
||||
if callLog.fromAddress!.username != nil && callLog.fromAddress!.username!.contains(filter) {
|
||||
callLogs.append(callLog)
|
||||
} else if callLog.fromAddress!.displayName != nil && callLog.fromAddress!.displayName!.contains(filter) {
|
||||
callLogs.append(callLog)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resetFilterCallLogs() {
|
||||
callLogs = callLogsTmp
|
||||
}
|
||||
|
||||
func removeCallLogs() {
|
||||
if callLogsAddressToDelete.isEmpty {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
let account = core.defaultAccount
|
||||
if account != nil {
|
||||
account!.clearCallLogs()
|
||||
} else {
|
||||
core.clearCallLogs()
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.callLogs.removeAll()
|
||||
self.callLogsTmp.removeAll()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
removeCallLogsWithAddress()
|
||||
callLogsAddressToDelete = ""
|
||||
}
|
||||
}
|
||||
|
||||
func removeCallLogsWithAddress() {
|
||||
self.callLogs.filter { $0.toAddress!.asStringUriOnly() == callLogsAddressToDelete || $0.fromAddress!.asStringUriOnly() == callLogsAddressToDelete }.forEach { callLog in
|
||||
removeCallLog(callLog: callLog)
|
||||
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.removeCallLog(callLog: callLog)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeCallLog(callLog: CallLog) {
|
||||
let index = self.callLogs.firstIndex(where: {$0.callId == callLog.callId})
|
||||
self.callLogs.remove(at: index!)
|
||||
|
||||
let indexTmp = self.callLogsTmp.firstIndex(where: {$0.callId == callLog.callId})
|
||||
self.callLogsTmp.remove(at: indexTmp!)
|
||||
}
|
||||
|
||||
func removeAllDelegate() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
if self.coreDelegate != nil {
|
||||
core.removeDelegate(delegate: self.coreDelegate!)
|
||||
self.coreDelegate = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -18,10 +18,13 @@
|
|||
*/
|
||||
|
||||
import Foundation
|
||||
import linphonesw
|
||||
|
||||
class HistoryViewModel: ObservableObject {
|
||||
|
||||
@Published var historyTitle: String = ""
|
||||
@Published var displayedCall: CallLog?
|
||||
|
||||
var selectedCall: CallLog?
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
|
|
|||
27
Linphone/UI/Main/History/ViewModel/StartCallViewModel.swift
Normal file
27
Linphone/UI/Main/History/ViewModel/StartCallViewModel.swift
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 StartCallViewModel: ObservableObject {
|
||||
|
||||
@Published var searchField: String = ""
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
|
@ -21,6 +21,8 @@ import linphonesw
|
|||
|
||||
class SharedMainViewModel: ObservableObject {
|
||||
|
||||
static let shared = SharedMainViewModel()
|
||||
|
||||
@Published var welcomeViewDisplayed = false
|
||||
@Published var generalTermsAccepted = false
|
||||
@Published var displayProfileMode = false
|
||||
|
|
@ -31,7 +33,7 @@ class SharedMainViewModel: ObservableObject {
|
|||
|
||||
var maxWidth = 400.0
|
||||
|
||||
init() {
|
||||
private init() {
|
||||
let preferences = UserDefaults.standard
|
||||
|
||||
if preferences.object(forKey: welcomeViewKey) == nil {
|
||||
|
|
|
|||
31
Linphone/UI/Main/Viewmodel/ToastViewModel.swift
Normal file
31
Linphone/UI/Main/Viewmodel/ToastViewModel.swift
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 Foundation
|
||||
|
||||
class ToastViewModel: ObservableObject {
|
||||
|
||||
static let shared = ToastViewModel()
|
||||
|
||||
var toastMessage: String = ""
|
||||
@Published var displayToast = false
|
||||
|
||||
private init() {
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ import SwiftUI
|
|||
|
||||
struct WelcomeView: View {
|
||||
|
||||
@ObservedObject var sharedMainViewModel: SharedMainViewModel
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
@State private var index = 0
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ struct WelcomeView: View {
|
|||
|
||||
VStack(alignment: .trailing) {
|
||||
NavigationLink(destination: {
|
||||
PermissionsFragment(sharedMainViewModel: sharedMainViewModel)
|
||||
PermissionsFragment()
|
||||
}, label: {
|
||||
Text("Skip")
|
||||
.underline()
|
||||
|
|
@ -96,7 +96,7 @@ struct WelcomeView: View {
|
|||
|
||||
if index == 2 {
|
||||
NavigationLink(destination: {
|
||||
PermissionsFragment(sharedMainViewModel: sharedMainViewModel)
|
||||
PermissionsFragment()
|
||||
}, label: {
|
||||
Text("Start")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
|
|
@ -158,5 +158,5 @@ struct WelcomeView: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
WelcomeView(sharedMainViewModel: SharedMainViewModel())
|
||||
WelcomeView()
|
||||
}
|
||||
|
|
|
|||
66
Linphone/Utils/Avatar.swift
Normal file
66
Linphone/Utils/Avatar.swift
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 Avatar: View {
|
||||
|
||||
@ObservedObject var contactAvatarModel: ContactAvatarModel
|
||||
let avatarSize: CGFloat
|
||||
|
||||
var body: some View {
|
||||
AsyncImage(url: ContactsManager.shared.getImagePath(friendPhotoPath: contactAvatarModel.friend!.photo!)) { image in
|
||||
switch image {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
.frame(width: avatarSize, height: avatarSize)
|
||||
case .success(let image):
|
||||
ZStack {
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: avatarSize, height: avatarSize)
|
||||
.clipShape(Circle())
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
Spacer()
|
||||
if contactAvatarModel.presenceStatus == .Online || contactAvatarModel.presenceStatus == .Busy {
|
||||
Image(contactAvatarModel.presenceStatus == .Online ? "presence-online" : "presence-busy")
|
||||
.resizable()
|
||||
.frame(width: avatarSize/4, height: avatarSize/4)
|
||||
.padding(.trailing, avatarSize == 45 ? 1 : 3)
|
||||
.padding(.bottom, avatarSize == 45 ? 1 : 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: avatarSize, height: avatarSize)
|
||||
}
|
||||
case .failure:
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: avatarSize, height: avatarSize)
|
||||
.clipShape(Circle())
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -50,10 +50,11 @@ struct EditContactView: UIViewControllerRepresentable {
|
|||
&& cnc.phoneNumbers.first?.value.stringValue != nil
|
||||
? cnc.phoneNumbers.first!.value.stringValue
|
||||
: cnc.givenName, lastName: cnc.familyName),
|
||||
name: cnc.givenName + cnc.familyName + String(Int.random(in: 1...1000)) + ((imageThumbnail == nil) ? "-default" : ""),
|
||||
name: cnc.givenName + cnc.familyName,
|
||||
prefix: ((imageThumbnail == nil) ? "-default" : ""),
|
||||
contact: newContact,
|
||||
linphoneFriend: false,
|
||||
existingFriend: ContactsManager.shared.getFriend(contact: newContact))
|
||||
existingFriend: ContactsManager.shared.getFriendWithContact(contact: newContact))
|
||||
|
||||
MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
}
|
||||
|
|
|
|||
67
Linphone/Utils/IntExtension.swift
Normal file
67
Linphone/Utils/IntExtension.swift
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 Foundation
|
||||
|
||||
extension Int {
|
||||
|
||||
public func hmsFrom() -> (Int, Int, Int) {
|
||||
return (self / 3600, (self % 3600) / 60, (self % 3600) % 60)
|
||||
}
|
||||
|
||||
public func convertDurationToString() -> String {
|
||||
var duration = ""
|
||||
let (hour, minute, second) = self.hmsFrom()
|
||||
if hour > 0 {
|
||||
duration = self.getHour(hour: hour)
|
||||
}
|
||||
return "\(duration)\(self.getMinute(minute: minute))\(self.getSecond(second: second))"
|
||||
}
|
||||
|
||||
private func getHour(hour: Int) -> String {
|
||||
var duration = "\(hour):"
|
||||
if hour < 10 {
|
||||
duration = "0\(hour):"
|
||||
}
|
||||
return duration
|
||||
}
|
||||
|
||||
private func getMinute(minute: Int) -> String {
|
||||
if minute == 0 {
|
||||
return "00:"
|
||||
}
|
||||
|
||||
if minute < 10 {
|
||||
return "0\(minute):"
|
||||
}
|
||||
|
||||
return "\(minute):"
|
||||
}
|
||||
|
||||
private func getSecond(second: Int) -> String {
|
||||
if second == 0 {
|
||||
return "00"
|
||||
}
|
||||
|
||||
if second < 10 {
|
||||
return "0\(second)"
|
||||
}
|
||||
return "\(second)"
|
||||
}
|
||||
}
|
||||
|
|
@ -23,15 +23,17 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
|
||||
static let shared = MagicSearchSingleton()
|
||||
private var coreContext = CoreContext.shared
|
||||
private var contactsManager = ContactsManager.shared
|
||||
|
||||
private var magicSearch: MagicSearch!
|
||||
|
||||
var currentFilter: String = ""
|
||||
var previousFilter: String?
|
||||
|
||||
var needUpdateLastSearchContacts = false
|
||||
var currentFilterSuggestions: String = ""
|
||||
var previousFilterSuggestions: String?
|
||||
|
||||
@Published var lastSearch: [SearchResult] = []
|
||||
var needUpdateLastSearchContacts = false
|
||||
|
||||
private var limitSearchToLinphoneAccounts = true
|
||||
|
||||
|
|
@ -47,7 +49,39 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
|
||||
self.magicSearch.publisher?.onSearchResultsReceived?.postOnMainQueue { (magicSearch: MagicSearch) in
|
||||
self.needUpdateLastSearchContacts = true
|
||||
self.lastSearch = magicSearch.lastSearch
|
||||
|
||||
var lastSearchFriend: [SearchResult] = []
|
||||
var lastSearchSuggestions: [SearchResult] = []
|
||||
|
||||
magicSearch.lastSearch.forEach { searchResult in
|
||||
if searchResult.friend != nil {
|
||||
lastSearchFriend.append(searchResult)
|
||||
} else {
|
||||
lastSearchSuggestions.append(searchResult)
|
||||
}
|
||||
}
|
||||
|
||||
self.contactsManager.lastSearch = lastSearchFriend.sorted(by: {
|
||||
$0.friend!.name!.lowercased().folding(options: .diacriticInsensitive, locale: .current)
|
||||
<
|
||||
$1.friend!.name!.lowercased().folding(options: .diacriticInsensitive, locale: .current)
|
||||
})
|
||||
|
||||
self.contactsManager.lastSearchSuggestions = lastSearchSuggestions.sorted(by: {
|
||||
$0.address!.asStringUriOnly() < $1.address!.asStringUriOnly()
|
||||
})
|
||||
|
||||
self.contactsManager.avatarListModel.forEach { contactAvatarModel in
|
||||
contactAvatarModel.removeAllDelegate()
|
||||
}
|
||||
|
||||
self.contactsManager.avatarListModel.removeAll()
|
||||
|
||||
self.contactsManager.lastSearch.forEach { searchResult in
|
||||
if searchResult.friend != nil {
|
||||
self.contactsManager.avatarListModel.append(ContactAvatarModel(friend: searchResult.friend!, withPresence: true))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -75,4 +109,28 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
aggregation: MagicSearch.Aggregation.Friend)
|
||||
}
|
||||
}
|
||||
|
||||
func searchForSuggestions() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
var needResetCache = false
|
||||
|
||||
DispatchQueue.main.sync {
|
||||
if let oldFilter = self.previousFilterSuggestions {
|
||||
if oldFilter.count > self.currentFilterSuggestions.count || oldFilter != self.currentFilterSuggestions {
|
||||
needResetCache = true
|
||||
}
|
||||
}
|
||||
self.previousFilterSuggestions = self.currentFilterSuggestions
|
||||
}
|
||||
if needResetCache {
|
||||
self.magicSearch.resetSearchCache()
|
||||
}
|
||||
|
||||
self.magicSearch.getContactsListAsync(
|
||||
filter: self.currentFilterSuggestions,
|
||||
domain: self.domainDefaultAccount,
|
||||
sourceFlags: MagicSearch.Source.All.rawValue,
|
||||
aggregation: MagicSearch.Aggregation.Friend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,13 @@ class PermissionManager: ObservableObject {
|
|||
|
||||
private init() {}
|
||||
|
||||
|
||||
func getPermissions(){
|
||||
photoLibraryRequestPermission()
|
||||
cameraRequestPermission()
|
||||
contactsRequestPermission()
|
||||
}
|
||||
|
||||
func photoLibraryRequestPermission() {
|
||||
PHPhotoLibrary.requestAuthorization(for: .readWrite, handler: {status in
|
||||
DispatchQueue.main.async {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue