History call detail with calllogs

This commit is contained in:
Benoit Martins 2023-11-17 18:19:32 +01:00
commit 773d9584f4
6 changed files with 331 additions and 77 deletions

View file

@ -41,6 +41,7 @@
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 */; };
@ -111,6 +112,7 @@
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>"; };
@ -168,6 +170,7 @@
children = (
D717071D2AC5922E0037746F /* ColorExtension.swift */,
D717071F2AC5989C0037746F /* TextExtension.swift */,
D76005F52B0798B00054B79A /* IntExtension.swift */,
D74C9D002ACB098C0021626A /* PermissionManager.swift */,
D7D1698B2AE66FA500109A5C /* MagicSearchSingleton.swift */,
D7C3650D2AF15BF200FE6142 /* PhotoPicker.swift */,
@ -543,6 +546,7 @@
D719ABB72ABC67BF00B41C10 /* LinphoneApp.swift in Sources */,
D732A91B2B061BD900DB42BA /* HistoryListBottomSheet.swift in Sources */,
D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */,
D76005F62B0798B00054B79A /* IntExtension.swift in Sources */,
D7E6D0512AEBDBD500A57AAF /* ContactsListBottomSheet.swift in Sources */,
D7A2EDD62AC18115005D90FC /* SharedMainViewModel.swift in Sources */,
D7A03FC62ACC458A0081A588 /* SplashScreen.swift in Sources */,

View file

@ -107,6 +107,9 @@
},
"Add the contact" : {
},
"Add to contacts" : {
},
"Add to favourites" : {
@ -202,6 +205,9 @@
},
"Delete all history" : {
},
"Delete history" : {
},
"Delete this contact" : {
@ -262,6 +268,9 @@
},
"I understand" : {
},
"Incoming Call" : {
},
"Information" : {
@ -328,6 +337,9 @@
},
"Other actions" : {
},
"Outgoing Call" : {
},
"password" : {
"extractionState" : "manual",

View file

@ -427,10 +427,18 @@ struct ContentView: View {
.background(Color.gray100)
.ignoresSafeArea(.keyboard)
} else if self.index == 1 {
HistoryContactFragment(historyViewModel: historyViewModel, isShowDeleteAllHistoryPopup: $isShowDeleteAllHistoryPopup)
.frame(maxWidth: .infinity)
.background(Color.gray100)
.ignoresSafeArea(.keyboard)
HistoryContactFragment(
historyViewModel: historyViewModel,
historyListViewModel: historyListViewModel,
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,
isShowDeleteAllHistoryPopup: $isShowDeleteAllHistoryPopup,
isShowEditContactFragment: $isShowEditContactFragment,
indexPage: $index
)
.frame(maxWidth: .infinity)
.background(Color.gray100)
.ignoresSafeArea(.keyboard)
}
}
.onAppear {
@ -527,11 +535,14 @@ struct ContentView: View {
content: Text("All calls will be removed from the history."),
titleFirstButton: Text("Cancel"),
actionFirstButton: {
self.isShowDeleteAllHistoryPopup.toggle()},
self.isShowDeleteAllHistoryPopup.toggle()
historyListViewModel.callLogsAddressToDelete = ""
},
titleSecondButton: Text("Ok"),
actionSecondButton: {
historyListViewModel.removeCallLogs()
self.isShowDeleteAllHistoryPopup.toggle()
historyViewModel.displayedCall = nil
})
.background(.black.opacity(0.65))
.zIndex(3)

View file

@ -18,6 +18,7 @@
*/
import SwiftUI
import UniformTypeIdentifiers
struct HistoryContactFragment: View {
@ -25,10 +26,15 @@ struct HistoryContactFragment: View {
@ObservedObject var sharedMainViewModel = SharedMainViewModel()
@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 {
@ -60,13 +66,52 @@ struct HistoryContactFragment: View {
Spacer()
Menu {
let fromAddressFriend = historyViewModel.displayedCall != nil ? ContactsManager.shared.getFriendWithAddress(address: historyViewModel.displayedCall!.fromAddress!) : nil
let toAddressFriend = historyViewModel.displayedCall != nil ? ContactsManager.shared.getFriendWithAddress(address: historyViewModel.displayedCall!.toAddress!) : nil
let addressFriend = historyViewModel.displayedCall != nil ? (historyViewModel.displayedCall!.dir == .Incoming ? fromAddressFriend : toAddressFriend) : nil
Button {
isMenuOpen = false
indexPage = 0
if ContactsManager.shared.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 = MagicSearchSingleton.shared.lastSearch.firstIndex(
where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressCall.asStringUriOnly()})})
if friendIndex != nil {
withAnimation {
historyViewModel.displayedCall = nil
contactViewModel.indexDisplayedFriend = friendIndex
}
}
} else {
let addressCall = historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing
? historyViewModel.displayedCall!.toAddress!
: historyViewModel.displayedCall!.fromAddress!
withAnimation {
historyViewModel.displayedCall = nil
isShowEditContactFragment.toggle()
editContactViewModel.sipAddresses.removeAll()
editContactViewModel.sipAddresses.append(String(addressCall.asStringUriOnly().dropFirst(4)))
editContactViewModel.sipAddresses.append("")
}
}
} label: {
HStack {
Text("See all")
Text(addressFriend != nil ? "See contact" : "Add to contacts")
Spacer()
Image("green-check")
Image(addressFriend != nil ? "user-circle" : "plus-circle")
.resizable()
.frame(width: 25, height: 25, alignment: .leading)
}
@ -74,11 +119,23 @@ struct HistoryContactFragment: View {
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
)
}
} label: {
HStack {
Text("See Linphone contact")
Text("Copy SIP address")
Spacer()
Image("green-check")
Image("copy")
.resizable()
.frame(width: 25, height: 25, alignment: .leading)
}
@ -86,9 +143,18 @@ struct HistoryContactFragment: View {
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 all history")
Text("Delete history")
Spacer()
Image("trash-simple-red")
.resizable()
@ -166,6 +232,13 @@ struct HistoryContactFragment: View {
.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)
@ -188,6 +261,13 @@ struct HistoryContactFragment: View {
.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)
@ -213,7 +293,14 @@ struct HistoryContactFragment: View {
.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)
@ -235,7 +322,14 @@ struct HistoryContactFragment: View {
.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)
@ -253,6 +347,22 @@ struct HistoryContactFragment: View {
.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("En ligne")
.foregroundStyle(Color.greenSuccess500)
@ -260,6 +370,7 @@ struct HistoryContactFragment: View {
.default_text_style_300(styleSize: 12)
.frame(maxWidth: .infinity)
.frame(height: 20)
.padding(.top, 5)
}
}
.frame(minHeight: 150)
@ -355,58 +466,61 @@ struct HistoryContactFragment: View {
.background(Color.gray100)
VStack(spacing: 0) {
let fromAddressFriend = historyViewModel.displayedCall != nil ? ContactsManager.shared.getFriendWithAddress(address: historyViewModel.displayedCall!.fromAddress!) : nil
let toAddressFriend = historyViewModel.displayedCall != nil ? ContactsManager.shared.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 != nil {
ForEach(0..<addressFriend!.addresses.count, id: \.self) { index in
Button {
} label: {
HStack {
VStack {
Text("SIP address :")
.default_text_style_700(styleSize: 14)
.frame(maxWidth: .infinity, alignment: .leading)
Text(addressFriend!.addresses[index].asStringUriOnly().dropFirst(4))
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
.fixedSize(horizontal: false, vertical: true)
}
Spacer()
Image("phone")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
.onTapGesture {
withAnimation {
}
}
}
.padding(.vertical, 15)
.padding(.horizontal, 20)
}
if !addressFriend!.phoneNumbers.isEmpty
|| index < addressFriend!.addresses.count - 1 {
VStack {
Divider()
}
.padding(.horizontal)
}
}
}
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(.horizontal)
.zIndex(-1)
.transition(.move(edge: .top))
.padding(.all)
}
.frame(maxWidth: sharedMainViewModel.maxWidth)
}
@ -425,5 +539,13 @@ struct HistoryContactFragment: View {
}
#Preview {
HistoryContactFragment(historyViewModel: HistoryViewModel(), isShowDeleteAllHistoryPopup: .constant(false))
HistoryContactFragment(
historyViewModel: HistoryViewModel(),
historyListViewModel: HistoryListViewModel(),
contactViewModel: ContactViewModel(),
editContactViewModel: EditContactViewModel(),
isShowDeleteAllHistoryPopup: .constant(false),
isShowEditContactFragment: .constant(false),
indexPage: .constant(1)
)
}

View file

@ -26,6 +26,8 @@ class HistoryListViewModel: ObservableObject {
@Published var callLogs: [CallLog] = []
var callLogsTmp: [CallLog] = []
var callLogsAddressToDelete = ""
init() {
computeCallLogsList()
}
@ -35,10 +37,10 @@ class HistoryListViewModel: ObservableObject {
let account = core.defaultAccount
let logs = account?.callLogs != nil ? account!.callLogs : core.callLogs
self.callLogs.removeAll()
self.callLogsTmp.removeAll()
DispatchQueue.main.async {
self.callLogs.removeAll()
self.callLogsTmp.removeAll()
logs.forEach { log in
self.callLogs.append(log)
self.callLogsTmp.append(log)
@ -72,19 +74,44 @@ class HistoryListViewModel: ObservableObject {
}
}
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 formatter.string(from: myNSDate)
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)
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"
@ -120,15 +147,26 @@ class HistoryListViewModel: ObservableObject {
}
func removeCallLogs() {
coreContext.doOnCoreQueue { core in
let account = core.defaultAccount
if account != nil {
account!.clearCallLogs()
} else {
core.clearCallLogs()
if callLogsAddressToDelete.isEmpty {
coreContext.doOnCoreQueue { core in
let account = core.defaultAccount
if account != nil {
account!.clearCallLogs()
} else {
core.clearCallLogs()
}
self.callLogs.removeAll()
self.callLogsTmp.removeAll()
}
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)
}
}
@ -137,6 +175,6 @@ class HistoryListViewModel: ObservableObject {
self.callLogs.remove(at: index!)
let indexTmp = self.callLogsTmp.firstIndex(where: {$0.callId == callLog.callId})
self.callLogsTmp.remove(at: index!)
self.callLogsTmp.remove(at: indexTmp!)
}
}

View 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)"
}
}