diff --git a/Linphone/Assets.xcassets/phone.imageset/phone.svg b/Linphone/Assets.xcassets/phone.imageset/phone.svg
index ac3ff5a1c..6eb862926 100644
--- a/Linphone/Assets.xcassets/phone.imageset/phone.svg
+++ b/Linphone/Assets.xcassets/phone.imageset/phone.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift
index c2cd38132..023e4680b 100644
--- a/Linphone/Core/CoreContext.swift
+++ b/Linphone/Core/CoreContext.swift
@@ -200,6 +200,26 @@ class CoreContext: ObservableObject {
self.forceRemotePushToMatchVoipPushSettings(account: acc)
}
+ let container = FileUtil.sharedContainerUrl()
+ let recordingsDir = container.appendingPathComponent("Library/Recordings")
+
+ let fm = FileManager.default
+
+ if !fm.fileExists(atPath: recordingsDir.path) {
+ do {
+ try fm.createDirectory(
+ at: recordingsDir,
+ withIntermediateDirectories: true,
+ attributes: nil
+ )
+ print("Recordings directory created.")
+ } catch {
+ print("Error creating directory: \(error)")
+ }
+ } else {
+ print("Recordings directory already exists.")
+ }
+
self.mCoreDelegate = CoreDelegateStub(onGlobalStateChanged: { (core: Core, state: GlobalState, _: String) in
if state == GlobalState.On {
#if DEBUG
diff --git a/Linphone/TelecomManager/TelecomManager.swift b/Linphone/TelecomManager/TelecomManager.swift
index 3c44bb469..eedf117d2 100644
--- a/Linphone/TelecomManager/TelecomManager.swift
+++ b/Linphone/TelecomManager/TelecomManager.swift
@@ -195,17 +195,13 @@ class TelecomManager: ObservableObject {
}
}
- private func makeRecordFilePath() -> String {
- var filePath = "recording_"
- let now = Date()
- let dateFormat = DateFormatter()
- dateFormat.dateFormat = "E-d-MMM-yyyy-HH-mm-ss"
- let date = dateFormat.string(from: now)
- filePath = filePath.appending("\(date).mkv")
+ private func makeRecordFilePath(address: String) -> String {
+ var filePath = "call_recording_sip_" + address.dropFirst(4) + "_on_"
- let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
- let writablePath = paths[0]
- return writablePath.appending("/\(filePath)")
+ filePath = filePath.appending("\(Int(Date().timeIntervalSince1970)).mkv")
+
+ let writablePath = FileUtil.sharedContainerUrl().appendingPathComponent("Library/Recordings/\(filePath)")
+ return writablePath.path
}
func doCall(core: Core, addr: Address, isSas: Bool, isVideo: Bool, isConference: Bool = false) throws {
@@ -237,7 +233,7 @@ class TelecomManager: ObservableObject {
// Log.directLog(BCTBX_LOG_DEBUG, text: "record file path: \(writablePath)")
// lcallParams.recordFile = writablePath
- lcallParams.recordFile = makeRecordFilePath()
+ lcallParams.recordFile = makeRecordFilePath(address: addr.asStringUriOnly())
if isSas {
lcallParams.mediaEncryption = .ZRTP
@@ -292,7 +288,7 @@ class TelecomManager: ObservableObject {
func acceptCall(core: Core, call: Call, hasVideo: Bool) {
do {
let callParams = try core.createCallParams(call: call)
- callParams.recordFile = makeRecordFilePath()
+ callParams.recordFile = makeRecordFilePath(address: call.remoteAddress?.asStringUriOnly() ?? "")
callParams.videoEnabled = hasVideo
/*if (ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference")) {
let low_bandwidth = (AppManager.network() == .network_2g)
diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift
index 3fba105a3..271716a7e 100644
--- a/Linphone/UI/Main/ContentView.swift
+++ b/Linphone/UI/Main/ContentView.swift
@@ -65,6 +65,7 @@ struct ContentView: View {
@State var isShowConversationFragment = false
@State var isShowAccountProfileFragment = false
@State var isShowSettingsFragment = false
+ @State var isShowRecordingsListFragment = false
@State var isShowHelpFragment = false
@State var fullscreenVideo = false
@@ -1041,6 +1042,7 @@ struct ContentView: View {
isShowLoginFragment: $isShowLoginFragment,
isShowAccountProfileFragment: $isShowAccountProfileFragment,
isShowSettingsFragment: $isShowSettingsFragment,
+ isShowRecordingsListFragment: $isShowRecordingsListFragment,
isShowHelpFragment: $isShowHelpFragment
)
.environmentObject(accountProfileViewModel)
@@ -1285,6 +1287,14 @@ struct ContentView: View {
.transition(.move(edge: .trailing))
}
+ if isShowRecordingsListFragment {
+ RecordingsListFragment(
+ isShowRecordingsListFragment: $isShowRecordingsListFragment
+ )
+ .zIndex(3)
+ .transition(.move(edge: .trailing))
+ }
+
if isShowHelpFragment {
HelpFragment(
isShowHelpFragment: $isShowHelpFragment
diff --git a/Linphone/UI/Main/Fragments/SideMenu.swift b/Linphone/UI/Main/Fragments/SideMenu.swift
index f984aa5b1..ae3abe5d7 100644
--- a/Linphone/UI/Main/Fragments/SideMenu.swift
+++ b/Linphone/UI/Main/Fragments/SideMenu.swift
@@ -32,6 +32,7 @@ struct SideMenu: View {
@Binding var isShowLoginFragment: Bool
@Binding var isShowAccountProfileFragment: Bool
@Binding var isShowSettingsFragment: Bool
+ @Binding var isShowRecordingsListFragment: Bool
@Binding var isShowHelpFragment: Bool
@State private var showHelp = false
@@ -137,12 +138,15 @@ struct SideMenu: View {
}
}
- /*
SideMenuEntry(
iconName: "record-fill",
title: "recordings_title"
- )
- */
+ ).onTapGesture {
+ self.menuClose()
+ withAnimation {
+ isShowRecordingsListFragment = true
+ }
+ }
SideMenuEntry(
iconName: "question",
@@ -152,7 +156,6 @@ struct SideMenu: View {
withAnimation {
isShowHelpFragment = true
}
-
}
}
.padding(.bottom, safeAreaInsets.bottom + 13)
@@ -176,15 +179,15 @@ struct SideMenu: View {
#Preview {
GeometryReader { geometry in
- @State var triggerNavigateToLogin: Bool = false
SideMenu(
width: geometry.size.width / 5 * 4,
isOpen: .constant(true),
menuClose: {},
safeAreaInsets: geometry.safeAreaInsets,
- isShowLoginFragment: $triggerNavigateToLogin,
+ isShowLoginFragment: .constant(false),
isShowAccountProfileFragment: .constant(false),
isShowSettingsFragment: .constant(false),
+ isShowRecordingsListFragment: .constant(false),
isShowHelpFragment: .constant(false)
)
.ignoresSafeArea(.all)
diff --git a/Linphone/UI/Main/Help/Fragments/HelpFragment.swift b/Linphone/UI/Main/Help/Fragments/HelpFragment.swift
index 18c55d618..004bda777 100644
--- a/Linphone/UI/Main/Help/Fragments/HelpFragment.swift
+++ b/Linphone/UI/Main/Help/Fragments/HelpFragment.swift
@@ -25,10 +25,6 @@ struct HelpFragment: View {
@Binding var isShowHelpFragment: Bool
- @State var advancedSettingsIsOpen: Bool = false
-
- @FocusState var isVoicemailUriFocused: Bool
-
var showAssistant: Bool {
(CoreContext.shared.coreIsStarted && CoreContext.shared.accounts.isEmpty)
|| SharedMainViewModel.shared.displayProfileMode
diff --git a/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift
index c11f57416..c8e8d4240 100644
--- a/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift
+++ b/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift
@@ -152,8 +152,10 @@ struct HistoryRow: View {
if !historyModel.isConf {
Image("phone")
+ .renderingMode(.template)
.resizable()
.frame(width: 25, height: 25)
+ .foregroundStyle(Color.grayMain2c600)
.padding(.all, 10)
.padding(.trailing, 5)
.highPriorityGesture(
diff --git a/Linphone/UI/Main/Recordings/Fragments/RecordingsListFragment.swift b/Linphone/UI/Main/Recordings/Fragments/RecordingsListFragment.swift
new file mode 100644
index 000000000..54cb18f7b
--- /dev/null
+++ b/Linphone/UI/Main/Recordings/Fragments/RecordingsListFragment.swift
@@ -0,0 +1,142 @@
+/*
+ * 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 .
+ */
+
+import SwiftUI
+
+struct RecordingsListFragment: View {
+
+ @StateObject private var recordingsListViewModel = RecordingsListViewModel()
+
+ @Binding var isShowRecordingsListFragment: Bool
+
+ var body: some View {
+ NavigationView {
+ ZStack {
+ VStack(spacing: 1) {
+ Rectangle()
+ .foregroundColor(Color.orangeMain500)
+ .edgesIgnoringSafeArea(.top)
+ .frame(height: 0)
+
+ HStack {
+ Image("caret-left")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(Color.orangeMain500)
+ .frame(width: 25, height: 25, alignment: .leading)
+ .padding(.all, 10)
+ .padding(.top, 4)
+ .padding(.leading, -10)
+ .onTapGesture {
+ withAnimation {
+ isShowRecordingsListFragment = false
+ }
+ }
+
+ Text("recordings_title")
+ .default_text_style_orange_800(styleSize: 16)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding(.top, 4)
+ .lineLimit(1)
+
+ Spacer()
+ }
+ .frame(maxWidth: .infinity)
+ .frame(height: 50)
+ .padding(.horizontal)
+ .padding(.bottom, 4)
+ .background(.white)
+
+ ScrollView {
+ VStack(spacing: 0) {
+ VStack(spacing: 20) {
+ ForEach(Array(recordingsListViewModel.recordings.enumerated()), id: \.offset) { index, recording in
+ if index == 0 || recording.month != recordingsListViewModel.recordings[index-1].month {
+ createMonthLine(model: recording)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+
+ HStack {
+ VStack {
+ HStack {
+ Image("phone")
+ .renderingMode(.template)
+ .resizable()
+ .frame(width: 25, height: 25)
+ .foregroundStyle(Color.grayMain2c600)
+
+ Text(recording.displayName)
+ .default_text_style_700(styleSize: 14)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+
+ Spacer()
+
+ Text(recording.dateTime)
+ .default_text_style(styleSize: 14)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+
+ VStack {
+ Image("play-fill")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(Color.orangeMain500)
+ .frame(width: 30, height: 30)
+ .padding(.leading, -6)
+
+ Spacer()
+
+ Text(recording.formattedDuration)
+ .default_text_style(styleSize: 14)
+ .frame(alignment: .center)
+ }
+ .padding(.trailing, 6)
+ }
+ .frame(height: 60)
+ .padding(20)
+ .background(.white)
+ .clipShape(RoundedRectangle(cornerRadius: 10))
+ .shadow(color: .gray.opacity(0.4), radius: 4)
+ }
+ }
+ .padding(.all, 20)
+ }
+ .frame(maxWidth: .infinity)
+ }
+ .background(Color.gray100)
+ }
+ .background(Color.gray100)
+ }
+ .navigationTitle("")
+ .navigationBarHidden(true)
+ }
+ .navigationViewStyle(StackNavigationViewStyle())
+ .navigationTitle("")
+ .navigationBarHidden(true)
+ }
+
+ @ViewBuilder
+ func createMonthLine(model: RecordingModel) -> some View {
+ Text(model.month)
+ .fontWeight(.bold)
+ .padding(5)
+ .default_text_style_500(styleSize: 22)
+ }
+}
diff --git a/Linphone/UI/Main/Recordings/Models/RecordingModel.swift b/Linphone/UI/Main/Recordings/Models/RecordingModel.swift
new file mode 100644
index 000000000..a345b7c25
--- /dev/null
+++ b/Linphone/UI/Main/Recordings/Models/RecordingModel.swift
@@ -0,0 +1,180 @@
+/*
+ * 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 .
+ */
+
+import linphonesw
+import SwiftUI
+
+class RecordingModel: ObservableObject {
+ static let TAG = "[Recording Model]"
+
+ var filePath: String = ""
+ var fileName: String = ""
+ var sipUri: String = ""
+ var displayName: String = ""
+ var timestamp: Int64 = 0
+ var month: String = ""
+ var dateTime: String = ""
+ var formattedDuration: String = ""
+ var duration: Int = 0
+
+ init(filePath: String, fileName: String, isLegacy: Bool = false) {
+ self.filePath = filePath
+ self.fileName = fileName
+
+ var sipUriTmp: String = ""
+ var displayNameTmp: String = ""
+ var timestampTmp: Int64 = 0
+
+ CoreContext.shared.doOnCoreQueue { core in
+ if isLegacy {
+ let parts = fileName.split(separator: "_")
+ let username = String(parts.first ?? "")
+ let sipAddress = core.interpretUrl(url: username, applyInternationalPrefix: false)
+ sipUriTmp = sipAddress?.asStringUriOnly() ?? username
+
+ if let address = sipAddress {
+ ContactsManager.shared.getFriendWithAddressInCoreQueue(address: address) { friendResult in
+ if let addressFriend = friendResult {
+ displayNameTmp = addressFriend.name!
+ } else {
+ if address.displayName != nil {
+ displayNameTmp = address.displayName!
+ } else if address.username != nil {
+ displayNameTmp = address.username!
+ } else {
+ displayNameTmp = String(address.asStringUriOnly().dropFirst(4))
+ }
+ }
+ }
+ } else {
+ displayNameTmp = sipUriTmp
+ }
+
+ if parts.count > 1 {
+ let parsedDate = String(parts[1])
+ let formatter = DateFormatter()
+ formatter.dateFormat = "dd-MM-yyyy-HH-mm-ss"
+ if let date = formatter.date(from: parsedDate) {
+ timestampTmp = Int64(date.timeIntervalSince1970 * 1000)
+ } else {
+ Log.error("\(RecordingModel.TAG) Failed to parse legacy timestamp \(parsedDate)")
+ }
+ }
+ } else {
+ let headerLength = LinphoneUtils.RECORDING_FILE_NAME_HEADER.count
+ let withoutHeader = String(fileName.dropFirst(headerLength))
+ guard let sepRange = withoutHeader.range(of: LinphoneUtils.RECORDING_FILE_NAME_URI_TIMESTAMP_SEPARATOR) else {
+ fatalError("\(RecordingModel.TAG) Invalid file name format \(withoutHeader)")
+ }
+
+ sipUriTmp = String(withoutHeader[.. String {
+ let locale = Locale.current
+ let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
+
+ let dateFormatter = DateFormatter()
+ dateFormatter.locale = locale
+
+ if Calendar.current.isDate(date, equalTo: .now, toGranularity: .year) {
+ dateFormatter.dateFormat = locale.identifier == "fr_FR"
+ ? "EEEE d MMMM"
+ : "EEEE, MMMM d"
+ } else {
+ dateFormatter.dateFormat = locale.identifier == "fr_FR"
+ ? "EEEE d MMMM yyyy"
+ : "EEEE, MMMM d, yyyy"
+ }
+
+ let timeFormatter = DateFormatter()
+ timeFormatter.locale = locale
+ timeFormatter.dateFormat = locale.identifier == "fr_FR"
+ ? "HH:mm"
+ : "h:mm a"
+
+ return "\(dateFormatter.string(from: date).capitalized) - \(timeFormatter.string(from: date))"
+ }
+
+ func formattedMonthYear(timestamp: Int64) -> String {
+ let locale = Locale.current
+ let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
+
+ let formatter = DateFormatter()
+ formatter.locale = locale
+ if Calendar.current.isDate(date, equalTo: .now, toGranularity: .year) {
+ formatter.dateFormat = "MMMM"
+ } else {
+ formatter.dateFormat = "MMMM yyyy"
+ }
+
+ return formatter.string(from: date).capitalized
+ }
+}
diff --git a/Linphone/UI/Main/Recordings/ViewModel/RecordingsListViewModel.swift b/Linphone/UI/Main/Recordings/ViewModel/RecordingsListViewModel.swift
new file mode 100644
index 000000000..ada873838
--- /dev/null
+++ b/Linphone/UI/Main/Recordings/ViewModel/RecordingsListViewModel.swift
@@ -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 .
+ */
+
+import Foundation
+import Combine
+
+class RecordingsListViewModel: ObservableObject {
+ private let TAG = "[RecordingsListViewModel]"
+
+ @Published var recordings: [RecordingModel] = []
+ @Published var searchBarVisible: Bool = false
+ @Published var searchFilter: String = ""
+ @Published var fetchInProgress: Bool = true
+
+ @Published var focusSearchBarEvent: Bool? = nil
+
+ private let legacyRecordRegex = try! NSRegularExpression(pattern: ".*/(.*)_(\\d{2}-\\d{2}-\\d{4}-\\d{2}-\\d{2}-\\d{2})\\..*")
+
+ init() {
+ fetchInProgress = true
+ CoreContext.shared.doOnCoreQueue { core in
+ self.computeList(filter: "")
+ }
+ }
+
+ func openSearchBar() {
+ searchBarVisible = true
+ focusSearchBarEvent = true
+ }
+
+ func closeSearchBar() {
+ clearFilter()
+ searchBarVisible = false
+ focusSearchBarEvent = false
+ }
+
+ func clearFilter() {
+ if searchFilter.isEmpty {
+ searchBarVisible = false
+ focusSearchBarEvent = false
+ } else {
+ searchFilter = ""
+ }
+ }
+
+ func applyFilter(_ filter: String) {
+ DispatchQueue.global(qos: .background).async {
+ self.computeList(filter: filter)
+ }
+ }
+
+ private func computeList(filter: String) {
+ var list: [RecordingModel] = []
+
+ let dir1 = FileUtil.sharedContainerUrl().appendingPathComponent("Library/Recordings")
+ if let files = try? FileManager.default.contentsOfDirectory(at: dir1, includingPropertiesForKeys: nil) {
+ for file in files {
+ let path = file.path
+ let name = file.lastPathComponent
+
+ let model = RecordingModel(filePath: path, fileName: name)
+
+ if filter.isEmpty || model.sipUri.contains(filter) {
+ list.append(model)
+ }
+ }
+ }
+
+ list.sort { $0.timestamp > $1.timestamp }
+
+ DispatchQueue.main.async {
+ self.recordings = list
+ self.fetchInProgress = false
+ }
+ }
+}
diff --git a/Linphone/Utils/LinphoneUtils.swift b/Linphone/Utils/LinphoneUtils.swift
index b5cc01df5..0d6a4e79e 100644
--- a/Linphone/Utils/LinphoneUtils.swift
+++ b/Linphone/Utils/LinphoneUtils.swift
@@ -21,6 +21,11 @@ import Foundation
import linphonesw
class LinphoneUtils: NSObject {
+ static let RECORDING_FILE_NAME_HEADER = "call_recording_sip_"
+ static let RECORDING_FILE_NAME_URI_TIMESTAMP_SEPARATOR = "_on_"
+ static let RECORDING_MKV_FILE_EXTENSION = ".mkv"
+ static let RECORDING_SMFF_FILE_EXTENSION = ".smff"
+
public class func isChatRoomAGroup(chatRoom: ChatRoom) -> Bool {
let oneToOne = chatRoom.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue)
let conference = chatRoom.hasCapability(mask: ChatRoom.Capabilities.Conference.rawValue)
diff --git a/LinphoneApp.xcodeproj/project.pbxproj b/LinphoneApp.xcodeproj/project.pbxproj
index d958e07c6..2c8e765f1 100644
--- a/LinphoneApp.xcodeproj/project.pbxproj
+++ b/LinphoneApp.xcodeproj/project.pbxproj
@@ -147,6 +147,9 @@
D78E062C2BEA69BC00CE3783 /* CallStatisticsSheetBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78E062B2BEA69BC00CE3783 /* CallStatisticsSheetBottomSheet.swift */; };
D78E062E2BEA69F400CE3783 /* AudioRouteBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78E062D2BEA69F400CE3783 /* AudioRouteBottomSheet.swift */; };
D78E06302BEA6A4A00CE3783 /* ChangeLayoutBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78E062F2BEA6A4A00CE3783 /* ChangeLayoutBottomSheet.swift */; };
+ D795F57E2EC5F9500022C17D /* RecordingsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D795F57D2EC5F9480022C17D /* RecordingsListFragment.swift */; };
+ D795F5802EC5F9660022C17D /* RecordingsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D795F57F2EC5F95B0022C17D /* RecordingsListViewModel.swift */; };
+ D795F5832EC6133C0022C17D /* RecordingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D795F5822EC6133A0022C17D /* RecordingModel.swift */; };
D79622342B1DFE600037EACD /* DialerBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D79622332B1DFE600037EACD /* DialerBottomSheet.swift */; };
D796F2002B0BB61A0041115F /* ToastViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D796F1FF2B0BB61A0041115F /* ToastViewModel.swift */; };
D79F1C162CD3D6AD00FF0A05 /* ConversationInfoFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D79F1C152CD3D6AD00FF0A05 /* ConversationInfoFragment.swift */; };
@@ -379,6 +382,9 @@
D78E062B2BEA69BC00CE3783 /* CallStatisticsSheetBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallStatisticsSheetBottomSheet.swift; sourceTree = ""; };
D78E062D2BEA69F400CE3783 /* AudioRouteBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRouteBottomSheet.swift; sourceTree = ""; };
D78E062F2BEA6A4A00CE3783 /* ChangeLayoutBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeLayoutBottomSheet.swift; sourceTree = ""; };
+ D795F57D2EC5F9480022C17D /* RecordingsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingsListFragment.swift; sourceTree = ""; };
+ D795F57F2EC5F95B0022C17D /* RecordingsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingsListViewModel.swift; sourceTree = ""; };
+ D795F5822EC6133A0022C17D /* RecordingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingModel.swift; sourceTree = ""; };
D79622332B1DFE600037EACD /* DialerBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialerBottomSheet.swift; sourceTree = ""; };
D796F1FF2B0BB61A0041115F /* ToastViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastViewModel.swift; sourceTree = ""; };
D79F1C152CD3D6AD00FF0A05 /* ConversationInfoFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationInfoFragment.swift; sourceTree = ""; };
@@ -695,6 +701,7 @@
D7A03FBE2ACC2E010081A588 /* History */,
66E56BC52BA45E49006CE56F /* Meetings */,
66D382032CEB7DB80063E1C5 /* Models */,
+ D795F57A2EC5F89B0022C17D /* Recordings */,
D7DC096A2CFA192200A6D47C /* Settings */,
D7A2EDD42AC180FE005D90FC /* Viewmodel */,
D719ABB82ABC67BF00B41C10 /* ContentView.swift */,
@@ -895,6 +902,40 @@
path = ViewModel;
sourceTree = "";
};
+ D795F57A2EC5F89B0022C17D /* Recordings */ = {
+ isa = PBXGroup;
+ children = (
+ D795F57B2EC5F8FF0022C17D /* Fragments */,
+ D795F5812EC613220022C17D /* Models */,
+ D795F57C2EC5F9090022C17D /* ViewModel */,
+ );
+ path = Recordings;
+ sourceTree = "";
+ };
+ D795F57B2EC5F8FF0022C17D /* Fragments */ = {
+ isa = PBXGroup;
+ children = (
+ D795F57D2EC5F9480022C17D /* RecordingsListFragment.swift */,
+ );
+ path = Fragments;
+ sourceTree = "";
+ };
+ D795F57C2EC5F9090022C17D /* ViewModel */ = {
+ isa = PBXGroup;
+ children = (
+ D795F57F2EC5F95B0022C17D /* RecordingsListViewModel.swift */,
+ );
+ path = ViewModel;
+ sourceTree = "";
+ };
+ D795F5812EC613220022C17D /* Models */ = {
+ isa = PBXGroup;
+ children = (
+ D795F5822EC6133A0022C17D /* RecordingModel.swift */,
+ );
+ path = Models;
+ sourceTree = "";
+ };
D7A03FBB2ACC2D850081A588 /* Contacts */ = {
isa = PBXGroup;
children = (
@@ -1280,6 +1321,7 @@
buildActionMask = 2147483647;
files = (
D7C3650E2AF15BF200FE6142 /* PhotoPicker.swift in Sources */,
+ D795F57E2EC5F9500022C17D /* RecordingsListFragment.swift in Sources */,
D7ADF6002AFE356400212231 /* Avatar.swift in Sources */,
D7CEE03B2B7A234200FD79B7 /* ConversationsFragment.swift in Sources */,
D71707202AC5989C0037746F /* TextExtension.swift in Sources */,
@@ -1380,8 +1422,10 @@
D7A03FC02ACC2E390081A588 /* HistoryView.swift in Sources */,
D748BF2E2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift in Sources */,
6613A0B42BAEBE3F008923A4 /* MeetingViewModel.swift in Sources */,
+ D795F5832EC6133C0022C17D /* RecordingModel.swift in Sources */,
D7173EBE2B7A5C0A00BCC481 /* LinphoneUtils.swift in Sources */,
66C492012B24DB6900CEA16D /* Log.swift in Sources */,
+ D795F5802EC5F9660022C17D /* RecordingsListViewModel.swift in Sources */,
D756C8182D352C5F00A58F2F /* CorePreferences.swift in Sources */,
C6A5A9432C10B5ED0070FEA4 /* DecodableExtension.swift in Sources */,
D714035B2BE11E00004BD8CA /* CallMediaEncryptionModel.swift in Sources */,