forked from mirrors/linphone-iphone
Start new call view
This commit is contained in:
parent
8b14538fcd
commit
e47a04c5d9
20 changed files with 876 additions and 127 deletions
|
|
@ -28,6 +28,8 @@
|
|||
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 */; };
|
||||
|
|
@ -51,6 +53,7 @@
|
|||
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 */; };
|
||||
|
|
@ -106,6 +109,8 @@
|
|||
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>"; };
|
||||
|
|
@ -129,6 +134,7 @@
|
|||
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>"; };
|
||||
|
|
@ -295,6 +301,7 @@
|
|||
children = (
|
||||
D72250622ADE9615008FB426 /* HistoryViewModel.swift */,
|
||||
D732A9142B04C7FE00DB42BA /* HistoryListViewModel.swift */,
|
||||
D726E43E2B19E56F0083C415 /* StartCallViewModel.swift */,
|
||||
);
|
||||
path = ViewModel;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -314,6 +321,8 @@
|
|||
D732A90E2B04C3B400DB42BA /* HistoryFragment.swift */,
|
||||
D732A9122B04C7A300DB42BA /* HistoryListFragment.swift */,
|
||||
D732A91A2B061BD900DB42BA /* HistoryListBottomSheet.swift */,
|
||||
D726E43C2B19E4FE0083C415 /* StartCallFragment.swift */,
|
||||
D79622332B1DFE600037EACD /* DialerBottomSheet.swift */,
|
||||
);
|
||||
path = Fragments;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -576,6 +585,7 @@
|
|||
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 */,
|
||||
|
|
@ -605,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 */,
|
||||
|
|
|
|||
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 |
|
|
@ -35,6 +35,7 @@ final class ContactsManager: ObservableObject {
|
|||
var linphoneFriendList: FriendList?
|
||||
|
||||
@Published var lastSearch: [SearchResult] = []
|
||||
@Published var lastSearchSuggestions: [SearchResult] = []
|
||||
@Published var avatarListModel: [ContactAvatarModel] = []
|
||||
|
||||
private init() {
|
||||
|
|
@ -121,7 +122,8 @@ final class ContactsManager: ObservableObject {
|
|||
&& 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: String(Int.random(in: 1...1000)) + ((imageThumbnail == nil) ? "-default" : ""),
|
||||
contact: newContact, linphoneFriend: false, existingFriend: nil)
|
||||
}
|
||||
})
|
||||
|
|
@ -167,12 +169,12 @@ final class ContactsManager: ObservableObject {
|
|||
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 {
|
||||
|
|
@ -260,15 +262,16 @@ final class ContactsManager: ObservableObject {
|
|||
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 urlName = URL(string: name + prefix)
|
||||
let imagePath = urlName != nil ? urlName!.absoluteString.replacingOccurrences(of: "%", with: "") : String(Int.random(in: 1...1000))
|
||||
let decodedData: () = try data.write(to: directory!.appendingPathComponent(imagePath + ".png"))
|
||||
|
||||
completion(decodedData, imagePath + ".png")
|
||||
} catch {
|
||||
print("Error: ", error)
|
||||
|
|
|
|||
|
|
@ -87,15 +87,18 @@ final class CoreContext: ObservableObject {
|
|||
self.mCore.autoIterateEnabled = false
|
||||
self.mCore.friendsDatabasePath = "\(configDir)/friends.db"
|
||||
|
||||
print("configDirconfigDirconfigDir \(configDir)")
|
||||
|
||||
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
|
||||
}
|
||||
self.coreIsStarted = true
|
||||
}
|
||||
|
||||
try? self.mCore.start()
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ struct LinphoneApp: App {
|
|||
@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 {
|
||||
|
|
@ -37,17 +38,21 @@ struct LinphoneApp: App {
|
|||
WelcomeView()
|
||||
} else if coreContext.defaultAccount == nil || sharedMainViewModel.displayProfileMode {
|
||||
AssistantView()
|
||||
} else if coreContext.defaultAccount != nil
|
||||
} else if coreContext.defaultAccount != nil
|
||||
&& contactViewModel != nil
|
||||
&& editContactViewModel != nil
|
||||
&& historyViewModel != nil
|
||||
&& historyListViewModel != nil {
|
||||
&& historyListViewModel != nil
|
||||
&& startCallViewModel != nil {
|
||||
ContentView(
|
||||
contactViewModel: contactViewModel!,
|
||||
editContactViewModel: editContactViewModel!,
|
||||
historyViewModel: historyViewModel!,
|
||||
historyListViewModel: historyListViewModel!
|
||||
historyListViewModel: historyListViewModel!,
|
||||
startCallViewModel: startCallViewModel!
|
||||
)
|
||||
} else {
|
||||
SplashScreen()
|
||||
}
|
||||
} else {
|
||||
SplashScreen()
|
||||
|
|
@ -56,6 +61,7 @@ struct LinphoneApp: App {
|
|||
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,6 +104,39 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"+" : {
|
||||
|
||||
},
|
||||
"0" : {
|
||||
|
||||
},
|
||||
"1" : {
|
||||
|
||||
},
|
||||
"2" : {
|
||||
|
||||
},
|
||||
"3" : {
|
||||
|
||||
},
|
||||
"4" : {
|
||||
|
||||
},
|
||||
"5" : {
|
||||
|
||||
},
|
||||
"6" : {
|
||||
|
||||
},
|
||||
"7" : {
|
||||
|
||||
},
|
||||
"8" : {
|
||||
|
||||
},
|
||||
"9" : {
|
||||
|
||||
},
|
||||
"Accept all" : {
|
||||
|
||||
|
|
@ -313,6 +352,9 @@
|
|||
},
|
||||
"My Profile" : {
|
||||
|
||||
},
|
||||
"New call" : {
|
||||
|
||||
},
|
||||
"New contact" : {
|
||||
|
||||
|
|
@ -393,6 +435,9 @@
|
|||
},
|
||||
"Scan QR code" : {
|
||||
|
||||
},
|
||||
"Search contact or history call" : {
|
||||
|
||||
},
|
||||
"Sécurisé" : {
|
||||
|
||||
|
|
@ -429,6 +474,9 @@
|
|||
},
|
||||
"Start" : {
|
||||
|
||||
},
|
||||
"Suggestions" : {
|
||||
|
||||
},
|
||||
"TCP" : {
|
||||
|
||||
|
|
@ -479,6 +527,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Username error" : {
|
||||
|
||||
},
|
||||
"Video Call" : {
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -72,7 +72,29 @@ struct ContactsInnerFragment: View {
|
|||
.padding(.top, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
ContactsListFragment(contactViewModel: contactViewModel, contactsListViewModel: ContactsListViewModel(), showingSheet: $showingSheet)
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,105 +21,83 @@ import SwiftUI
|
|||
import linphonesw
|
||||
|
||||
struct ContactsListFragment: View {
|
||||
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var contactsListViewModel: ContactsListViewModel
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
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)
|
||||
}
|
||||
}
|
||||
.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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -491,8 +491,8 @@ struct EditContactFragment: View {
|
|||
?? ContactsManager.shared.textToImage(
|
||||
firstName: editContactViewModel.firstName, lastName: editContactViewModel.lastName),
|
||||
name: editContactViewModel.firstName
|
||||
+ editContactViewModel.lastName
|
||||
+ String(Int.random(in: 1...1000))
|
||||
+ editContactViewModel.lastName,
|
||||
prefix: String(Int.random(in: 1...1000))
|
||||
+ ((selectedImage == nil) ? "-default" : ""),
|
||||
contact: newContact, linphoneFriend: true, existingFriend: editContactViewModel.selectedEditFriend)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import linphonesw
|
|||
struct ContentView: View {
|
||||
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
|
@ -35,6 +36,7 @@ struct ContentView: View {
|
|||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
@ObservedObject var historyViewModel: HistoryViewModel
|
||||
@ObservedObject var historyListViewModel: HistoryListViewModel
|
||||
@ObservedObject var startCallViewModel: StartCallViewModel
|
||||
|
||||
@State var index = 0
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
|
|
@ -43,10 +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 isShowDeleteContactPopup = false
|
||||
@State var isShowDeleteAllHistoryPopup = false
|
||||
@State var isShowEditContactFragment = false
|
||||
@State var isShowStartCallFragment = false
|
||||
@State var isShowDismissPopup = false
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -322,6 +326,7 @@ struct ContentView: View {
|
|||
contactViewModel: contactViewModel,
|
||||
editContactViewModel: editContactViewModel,
|
||||
index: $index,
|
||||
isShowStartCallFragment: $isShowStartCallFragment,
|
||||
isShowEditContactFragment: $isShowEditContactFragment
|
||||
)
|
||||
}
|
||||
|
|
@ -502,6 +507,22 @@ struct ContentView: View {
|
|||
}
|
||||
}
|
||||
|
||||
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(
|
||||
|
|
@ -600,8 +621,8 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
//if sharedMainViewModel.displayToast {
|
||||
ToastView()
|
||||
.zIndex(3)
|
||||
ToastView()
|
||||
.zIndex(3)
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
|
@ -626,7 +647,9 @@ struct ContentView: View {
|
|||
.onChange(of: scenePhase) { newPhase in
|
||||
if newPhase == .active {
|
||||
coreContext.onForeground()
|
||||
contactsManager.fetchContacts()
|
||||
if !isShowStartCallFragment {
|
||||
contactsManager.fetchContacts()
|
||||
}
|
||||
print("Active")
|
||||
} else if newPhase == .inactive {
|
||||
print("Inactive")
|
||||
|
|
@ -649,7 +672,8 @@ struct ContentView: View {
|
|||
contactViewModel: ContactViewModel(),
|
||||
editContactViewModel: EditContactViewModel(),
|
||||
historyViewModel: HistoryViewModel(),
|
||||
historyListViewModel: HistoryListViewModel()
|
||||
historyListViewModel: HistoryListViewModel(),
|
||||
startCallViewModel: StartCallViewModel()
|
||||
)
|
||||
}
|
||||
// swiftlint:enable type_body_length
|
||||
|
|
|
|||
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)
|
||||
)
|
||||
}
|
||||
|
|
@ -49,7 +49,9 @@ struct HistoryListFragment: View {
|
|||
: ContactAvatarModel(friend: nil, withPresence: false)
|
||||
|
||||
if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty {
|
||||
Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 45)
|
||||
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 {
|
||||
|
|
|
|||
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,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import linphonesw
|
||||
|
||||
struct HistoryView: View {
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ struct HistoryView: View {
|
|||
@ObservedObject var editContactViewModel: EditContactViewModel
|
||||
|
||||
@Binding var index: Int
|
||||
@Binding var isShowStartCallFragment: Bool
|
||||
@Binding var isShowEditContactFragment: Bool
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -42,6 +44,10 @@ struct HistoryView: View {
|
|||
)
|
||||
|
||||
Button {
|
||||
withAnimation {
|
||||
MagicSearchSingleton.shared.searchForSuggestions()
|
||||
isShowStartCallFragment.toggle()
|
||||
}
|
||||
} label: {
|
||||
Image("phone-plus")
|
||||
.padding()
|
||||
|
|
|
|||
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() {}
|
||||
}
|
||||
|
|
@ -50,7 +50,8 @@ 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: String(Int.random(in: 1...1000)) + ((imageThumbnail == nil) ? "-default" : ""),
|
||||
contact: newContact,
|
||||
linphoneFriend: false,
|
||||
existingFriend: ContactsManager.shared.getFriendWithContact(contact: newContact))
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
var currentFilter: String = ""
|
||||
var previousFilter: String?
|
||||
|
||||
var currentFilterSuggestions: String = ""
|
||||
var previousFilterSuggestions: String?
|
||||
|
||||
var needUpdateLastSearchContacts = false
|
||||
|
||||
private var limitSearchToLinphoneAccounts = true
|
||||
|
|
@ -46,11 +49,27 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
|
||||
self.magicSearch.publisher?.onSearchResultsReceived?.postOnMainQueue { (magicSearch: MagicSearch) in
|
||||
self.needUpdateLastSearchContacts = true
|
||||
self.contactsManager.lastSearch = magicSearch.lastSearch.sorted(by: {
|
||||
|
||||
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()
|
||||
|
|
@ -91,26 +110,26 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func searchForContactsWithResult(sourceFlags: Int) {
|
||||
func searchForSuggestions() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
var needResetCache = false
|
||||
|
||||
DispatchQueue.main.sync {
|
||||
if let oldFilter = self.previousFilter {
|
||||
if oldFilter.count > self.currentFilter.count || oldFilter != self.currentFilter {
|
||||
if let oldFilter = self.previousFilterSuggestions {
|
||||
if oldFilter.count > self.currentFilterSuggestions.count || oldFilter != self.currentFilterSuggestions {
|
||||
needResetCache = true
|
||||
}
|
||||
}
|
||||
self.previousFilter = self.currentFilter
|
||||
self.previousFilterSuggestions = self.currentFilterSuggestions
|
||||
}
|
||||
if needResetCache {
|
||||
self.magicSearch.resetSearchCache()
|
||||
}
|
||||
|
||||
self.magicSearch.getContactsListAsync(
|
||||
filter: self.currentFilter,
|
||||
domain: self.allContact ? "" : self.domainDefaultAccount,
|
||||
sourceFlags: sourceFlags,
|
||||
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