mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-04-17 20:08:31 +00:00
Add media and documents list to the conversation
This commit is contained in:
parent
9ac0445347
commit
8223d20fc6
11 changed files with 944 additions and 3 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public enum AppGitInfo {
|
public enum AppGitInfo {
|
||||||
public static let branch = "master"
|
public static let branch = "feature/medias_and_documents_lists"
|
||||||
public static let commit = "efed662a3"
|
public static let commit = "4dbdf455f"
|
||||||
public static let tag = "6.1.0-alpha"
|
public static let tag = "6.1.0-alpha"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,11 @@
|
||||||
"conversation_menu_configure_ephemeral_messages" = "Ephemeral messages";
|
"conversation_menu_configure_ephemeral_messages" = "Ephemeral messages";
|
||||||
"conversation_menu_media_files" = "Media";
|
"conversation_menu_media_files" = "Media";
|
||||||
"conversation_menu_documents_files" = "Documents";
|
"conversation_menu_documents_files" = "Documents";
|
||||||
|
"conversation_no_media_found" = "No media found…";
|
||||||
|
"conversation_no_document_found" = "No document found…";
|
||||||
|
"conversation_media_list_title" = "Shared media";
|
||||||
|
"conversation_document_list_title" = "Shared documents";
|
||||||
|
"conversation_details_media_documents_title" = "Media & documents";
|
||||||
"conversation_message_forward_cancelled_toast" = "Message forward was cancelled";
|
"conversation_message_forward_cancelled_toast" = "Message forward was cancelled";
|
||||||
"conversation_message_forwarded_toast" = "Message was forwarded";
|
"conversation_message_forwarded_toast" = "Message was forwarded";
|
||||||
"conversation_message_meeting_cancelled_label" = "Meeting has been cancelled!";
|
"conversation_message_meeting_cancelled_label" = "Meeting has been cancelled!";
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,11 @@
|
||||||
"conversation_menu_configure_ephemeral_messages" = "Messages éphémères";
|
"conversation_menu_configure_ephemeral_messages" = "Messages éphémères";
|
||||||
"conversation_menu_media_files" = "Médias";
|
"conversation_menu_media_files" = "Médias";
|
||||||
"conversation_menu_documents_files" = "Documents";
|
"conversation_menu_documents_files" = "Documents";
|
||||||
|
"conversation_no_media_found" = "Aucun média pour le moment…";
|
||||||
|
"conversation_no_document_found" = "Aucun document pour le moment…";
|
||||||
|
"conversation_media_list_title" = "Médias partagés";
|
||||||
|
"conversation_document_list_title" = "Documents partagés";
|
||||||
|
"conversation_details_media_documents_title" = "Médias & documents";
|
||||||
"conversation_message_forward_cancelled_toast" = "Transfert annulé";
|
"conversation_message_forward_cancelled_toast" = "Transfert annulé";
|
||||||
"conversation_message_forwarded_toast" = "Message transféré";
|
"conversation_message_forwarded_toast" = "Message transféré";
|
||||||
"conversation_message_meeting_cancelled_label" = "La réunion a été annulée";
|
"conversation_message_meeting_cancelled_label" = "La réunion a été annulée";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
* 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 ConversationDocumentsListFragment: View {
|
||||||
|
@EnvironmentObject var conversationViewModel: ConversationViewModel
|
||||||
|
|
||||||
|
@StateObject private var conversationDocumentsListViewModel = ConversationDocumentsListViewModel()
|
||||||
|
|
||||||
|
@Binding var isShowDocumentsFilesFragment: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
ZStack {
|
||||||
|
VStack(spacing: 1) {
|
||||||
|
|
||||||
|
Rectangle()
|
||||||
|
.foregroundStyle(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, 2)
|
||||||
|
.padding(.leading, -10)
|
||||||
|
.onTapGesture {
|
||||||
|
withAnimation {
|
||||||
|
isShowDocumentsFilesFragment = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("conversation_document_list_title")
|
||||||
|
.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) {
|
||||||
|
List {
|
||||||
|
ForEach(conversationDocumentsListViewModel.documentsList, id: \.path) { file in
|
||||||
|
MediaGridItemView(file: file)
|
||||||
|
.background()
|
||||||
|
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||||
|
.listRowSeparator(.hidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.safeAreaInset(edge: .top, content: {
|
||||||
|
Spacer()
|
||||||
|
.frame(height: 12)
|
||||||
|
})
|
||||||
|
.listStyle(.plain)
|
||||||
|
.overlay(
|
||||||
|
VStack {
|
||||||
|
if true {
|
||||||
|
Spacer()
|
||||||
|
Text("conversation_no_document_found")
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
.default_text_style_800(styleSize: 16)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.all)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
.background(Color.gray100)
|
||||||
|
}
|
||||||
|
.navigationTitle("")
|
||||||
|
.navigationBarHidden(true)
|
||||||
|
.onDisappear {
|
||||||
|
withAnimation {
|
||||||
|
isShowDocumentsFilesFragment = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DocumentRow: View {
|
||||||
|
|
||||||
|
@ObservedObject var file: FileModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack(alignment: .bottomTrailing) {
|
||||||
|
if let previewPath = file.mediaPreview,
|
||||||
|
let image = UIImage(contentsOfFile: previewPath) {
|
||||||
|
Image(uiImage: image)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
|
.frame(height: 110)
|
||||||
|
.clipped()
|
||||||
|
} else {
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color.gray.opacity(0.2))
|
||||||
|
.frame(height: 110)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let duration = file.audioVideoDuration, file.isVideoPreview {
|
||||||
|
Text(duration)
|
||||||
|
.font(.caption2)
|
||||||
|
.padding(6)
|
||||||
|
.background(Color.black.opacity(0.6))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||||||
|
.padding(6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cornerRadius(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -59,9 +59,11 @@ struct ConversationFragment: View {
|
||||||
@State private var mediasIsLoading = false
|
@State private var mediasIsLoading = false
|
||||||
@State private var voiceRecordingInProgress = false
|
@State private var voiceRecordingInProgress = false
|
||||||
|
|
||||||
@State private var isShowConversationForwardMessageFragment = false
|
|
||||||
@State private var isShowEphemeralFragment = false
|
@State private var isShowEphemeralFragment = false
|
||||||
|
@State private var isShowMediaFilesFragment = false
|
||||||
|
@State private var isShowDocumentsFilesFragment = false
|
||||||
@State private var isShowInfoConversationFragment = false
|
@State private var isShowInfoConversationFragment = false
|
||||||
|
@State private var isShowConversationForwardMessageFragment = false
|
||||||
|
|
||||||
@Binding var isShowConversationFragment: Bool
|
@Binding var isShowConversationFragment: Bool
|
||||||
@Binding var isShowStartCallGroupPopup: Bool
|
@Binding var isShowStartCallGroupPopup: Bool
|
||||||
|
|
@ -456,6 +458,42 @@ struct ConversationFragment: View {
|
||||||
.padding(.all, 10)
|
.padding(.all, 10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
isMenuOpen = false
|
||||||
|
withAnimation {
|
||||||
|
isShowMediaFilesFragment = true
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
Text("conversation_menu_media_files")
|
||||||
|
Spacer()
|
||||||
|
Image("image")
|
||||||
|
.renderingMode(.template)
|
||||||
|
.resizable()
|
||||||
|
.foregroundStyle(Color.grayMain2c500)
|
||||||
|
.frame(width: 25, height: 25, alignment: .leading)
|
||||||
|
.padding(.all, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
isMenuOpen = false
|
||||||
|
withAnimation {
|
||||||
|
isShowDocumentsFilesFragment = true
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
Text("conversation_menu_documents_files")
|
||||||
|
Spacer()
|
||||||
|
Image("file-pdf")
|
||||||
|
.renderingMode(.template)
|
||||||
|
.resizable()
|
||||||
|
.foregroundStyle(Color.grayMain2c500)
|
||||||
|
.frame(width: 25, height: 25, alignment: .leading)
|
||||||
|
.padding(.all, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image("dots-three-vertical")
|
Image("dots-three-vertical")
|
||||||
|
|
@ -1519,6 +1557,8 @@ struct ConversationFragment: View {
|
||||||
ConversationInfoFragment(
|
ConversationInfoFragment(
|
||||||
isMuted: $isMuted,
|
isMuted: $isMuted,
|
||||||
isShowEphemeralFragment: $isShowEphemeralFragment,
|
isShowEphemeralFragment: $isShowEphemeralFragment,
|
||||||
|
isShowMediaFilesFragment: $isShowMediaFilesFragment,
|
||||||
|
isShowDocumentsFilesFragment: $isShowDocumentsFilesFragment,
|
||||||
isShowStartCallGroupPopup: $isShowStartCallGroupPopup,
|
isShowStartCallGroupPopup: $isShowStartCallGroupPopup,
|
||||||
isShowInfoConversationFragment: $isShowInfoConversationFragment,
|
isShowInfoConversationFragment: $isShowInfoConversationFragment,
|
||||||
isShowEditContactFragment: $isShowEditContactFragment,
|
isShowEditContactFragment: $isShowEditContactFragment,
|
||||||
|
|
@ -1543,6 +1583,24 @@ struct ConversationFragment: View {
|
||||||
.transition(.move(edge: .trailing))
|
.transition(.move(edge: .trailing))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isShowMediaFilesFragment {
|
||||||
|
ConversationMediaListFragment(
|
||||||
|
isShowMediaFilesFragment: $isShowMediaFilesFragment
|
||||||
|
)
|
||||||
|
.environmentObject(conversationViewModel)
|
||||||
|
.zIndex(5)
|
||||||
|
.transition(.move(edge: .trailing))
|
||||||
|
}
|
||||||
|
|
||||||
|
if isShowDocumentsFilesFragment {
|
||||||
|
ConversationDocumentsListFragment(
|
||||||
|
isShowDocumentsFilesFragment: $isShowDocumentsFilesFragment
|
||||||
|
)
|
||||||
|
.environmentObject(conversationViewModel)
|
||||||
|
.zIndex(5)
|
||||||
|
.transition(.move(edge: .trailing))
|
||||||
|
}
|
||||||
|
|
||||||
if conversationViewModel.searchInProgress {
|
if conversationViewModel.searchInProgress {
|
||||||
PopupLoadingView()
|
PopupLoadingView()
|
||||||
.background(.black.opacity(0.65))
|
.background(.black.opacity(0.65))
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ struct ConversationInfoFragment: View {
|
||||||
|
|
||||||
@Binding var isMuted: Bool
|
@Binding var isMuted: Bool
|
||||||
@Binding var isShowEphemeralFragment: Bool
|
@Binding var isShowEphemeralFragment: Bool
|
||||||
|
@Binding var isShowMediaFilesFragment: Bool
|
||||||
|
@Binding var isShowDocumentsFilesFragment: Bool
|
||||||
@Binding var isShowStartCallGroupPopup: Bool
|
@Binding var isShowStartCallGroupPopup: Bool
|
||||||
@Binding var isShowInfoConversationFragment: Bool
|
@Binding var isShowInfoConversationFragment: Bool
|
||||||
@Binding var isShowEditContactFragment: Bool
|
@Binding var isShowEditContactFragment: Bool
|
||||||
|
|
@ -525,6 +527,69 @@ struct ConversationInfoFragment: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text("conversation_details_media_documents_title")
|
||||||
|
.default_text_style_800(styleSize: 18)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 20)
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Button(
|
||||||
|
action: {
|
||||||
|
withAnimation {
|
||||||
|
isShowMediaFilesFragment = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
HStack {
|
||||||
|
Image("image")
|
||||||
|
.renderingMode(.template)
|
||||||
|
.resizable()
|
||||||
|
.foregroundStyle(Color.grayMain2c600)
|
||||||
|
.frame(width: 25, height: 25)
|
||||||
|
|
||||||
|
Text("conversation_menu_media_files")
|
||||||
|
.default_text_style(styleSize: 16)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.lineLimit(1)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.frame(height: 60)
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
Button(
|
||||||
|
action: {
|
||||||
|
withAnimation {
|
||||||
|
isShowDocumentsFilesFragment = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
HStack {
|
||||||
|
Image("file-pdf")
|
||||||
|
.renderingMode(.template)
|
||||||
|
.resizable()
|
||||||
|
.foregroundStyle(Color.grayMain2c600)
|
||||||
|
.frame(width: 25, height: 25)
|
||||||
|
|
||||||
|
Text("conversation_menu_documents_files")
|
||||||
|
.default_text_style(styleSize: 16)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.lineLimit(1)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.frame(height: 60)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
.background(.white)
|
||||||
|
.cornerRadius(15)
|
||||||
|
.padding(.all)
|
||||||
|
|
||||||
Text("contact_details_actions_title")
|
Text("contact_details_actions_title")
|
||||||
.default_text_style_800(styleSize: 18)
|
.default_text_style_800(styleSize: 18)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
@ -710,6 +775,8 @@ struct ConversationInfoFragment: View {
|
||||||
ConversationInfoFragment(
|
ConversationInfoFragment(
|
||||||
isMuted: .constant(false),
|
isMuted: .constant(false),
|
||||||
isShowEphemeralFragment: .constant(false),
|
isShowEphemeralFragment: .constant(false),
|
||||||
|
isShowMediaFilesFragment: .constant(false),
|
||||||
|
isShowDocumentsFilesFragment: .constant(false),
|
||||||
isShowStartCallGroupPopup: .constant(false),
|
isShowStartCallGroupPopup: .constant(false),
|
||||||
isShowInfoConversationFragment: .constant(true),
|
isShowInfoConversationFragment: .constant(true),
|
||||||
isShowEditContactFragment: .constant(false),
|
isShowEditContactFragment: .constant(false),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
/*
|
||||||
|
* 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 ConversationMediaListFragment: View {
|
||||||
|
@EnvironmentObject var conversationViewModel: ConversationViewModel
|
||||||
|
|
||||||
|
@StateObject private var conversationMediaListViewModel = ConversationMediaListViewModel()
|
||||||
|
|
||||||
|
@Binding var isShowMediaFilesFragment: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
ZStack {
|
||||||
|
VStack(spacing: 1) {
|
||||||
|
|
||||||
|
Rectangle()
|
||||||
|
.foregroundStyle(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, 2)
|
||||||
|
.padding(.leading, -10)
|
||||||
|
.onTapGesture {
|
||||||
|
withAnimation {
|
||||||
|
isShowMediaFilesFragment = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("conversation_media_list_title")
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
.default_text_style_orange_800(styleSize: 16)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 50)
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.bottom, 4)
|
||||||
|
.background(.white)
|
||||||
|
|
||||||
|
ConversationMediaGridView(viewModel: conversationMediaListViewModel)
|
||||||
|
}
|
||||||
|
.background(Color.gray100)
|
||||||
|
}
|
||||||
|
.navigationTitle("")
|
||||||
|
.navigationBarHidden(true)
|
||||||
|
.onDisappear {
|
||||||
|
withAnimation {
|
||||||
|
isShowMediaFilesFragment = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConversationMediaGridView: View {
|
||||||
|
|
||||||
|
@ObservedObject var viewModel: ConversationMediaListViewModel
|
||||||
|
|
||||||
|
private let columns = [
|
||||||
|
GridItem(.flexible(), spacing: 1),
|
||||||
|
GridItem(.flexible(), spacing: 1),
|
||||||
|
GridItem(.flexible(), spacing: 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
if !viewModel.mediaList.isEmpty && !viewModel.operationInProgress {
|
||||||
|
ScrollView {
|
||||||
|
LazyVGrid(columns: columns, spacing: 1) {
|
||||||
|
ForEach(viewModel.mediaList, id: \.path) { file in
|
||||||
|
MediaGridItemView(file: file)
|
||||||
|
.onTapGesture {
|
||||||
|
//viewModel.openMediaEvent.send(file)
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
if file == viewModel.mediaList.last {
|
||||||
|
viewModel.loadMoreData(totalItemsCount: viewModel.mediaList.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 2)
|
||||||
|
.padding(.top, 12)
|
||||||
|
}
|
||||||
|
} else if viewModel.mediaList.isEmpty && !viewModel.operationInProgress {
|
||||||
|
Spacer()
|
||||||
|
Text("conversation_no_media_found")
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.default_text_style_800(styleSize: 16)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MediaGridItemView: View {
|
||||||
|
|
||||||
|
@ObservedObject var file: FileModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack(alignment: .bottomTrailing) {
|
||||||
|
if let previewPath = file.mediaPreview,
|
||||||
|
let image = UIImage(contentsOfFile: previewPath) {
|
||||||
|
Image(uiImage: image)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
|
.frame(width: 120, height: 120)
|
||||||
|
.clipped()
|
||||||
|
} else {
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color.gray.opacity(0.2))
|
||||||
|
.frame(width: 120, height: 120)
|
||||||
|
}
|
||||||
|
|
||||||
|
if file.isVideoPreview {
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image("play-fill")
|
||||||
|
.renderingMode(.template)
|
||||||
|
.resizable()
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.frame(width: 35, height: 35)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(width: 120, height: 120)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let duration = file.audioVideoDuration, file.isVideoPreview {
|
||||||
|
Text(duration)
|
||||||
|
.font(.caption2)
|
||||||
|
.padding(6)
|
||||||
|
.background(Color.black.opacity(0.6))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||||||
|
.padding(6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cornerRadius(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
235
Linphone/UI/Main/Conversations/Model/FileModel.swift
Normal file
235
Linphone/UI/Main/Conversations/Model/FileModel.swift
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||||
|
*
|
||||||
|
* This file is part of Linphone
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AVFoundation
|
||||||
|
import PDFKit
|
||||||
|
|
||||||
|
final class FileModel: ObservableObject, Equatable {
|
||||||
|
|
||||||
|
// MARK: - Inputs
|
||||||
|
let path: String
|
||||||
|
let fileName: String
|
||||||
|
let fileSize: Int64
|
||||||
|
let fileCreationTimestamp: Int64
|
||||||
|
let isEncrypted: Bool
|
||||||
|
let originalPath: String
|
||||||
|
let isFromEphemeralMessage: Bool
|
||||||
|
let isWaitingToBeDownloaded: Bool
|
||||||
|
let flexboxLayoutWrapBefore: Bool
|
||||||
|
private let onClicked: ((FileModel) -> Void)?
|
||||||
|
|
||||||
|
@Published var formattedFileSize: String = ""
|
||||||
|
@Published var transferProgress: Int = -1
|
||||||
|
@Published var transferProgressLabel: String = ""
|
||||||
|
@Published var mediaPreview: String? = nil
|
||||||
|
@Published var mediaPreviewAvailable: Bool = false
|
||||||
|
@Published var audioVideoDuration: String? = nil
|
||||||
|
|
||||||
|
// MARK: - Computed
|
||||||
|
let mimeTypeString: String
|
||||||
|
let isMedia: Bool
|
||||||
|
let isImage: Bool
|
||||||
|
let isVideoPreview: Bool
|
||||||
|
let isPdf: Bool
|
||||||
|
let isAudio: Bool
|
||||||
|
|
||||||
|
let month: String
|
||||||
|
let dateTime: String
|
||||||
|
|
||||||
|
static func == (lhs: FileModel, rhs: FileModel) -> Bool {
|
||||||
|
return lhs.path == rhs.path
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
init(
|
||||||
|
path: String,
|
||||||
|
fileName: String,
|
||||||
|
fileSize: Int64,
|
||||||
|
fileCreationTimestamp: Int64,
|
||||||
|
isEncrypted: Bool,
|
||||||
|
originalPath: String,
|
||||||
|
isFromEphemeralMessage: Bool,
|
||||||
|
isWaitingToBeDownloaded: Bool = false,
|
||||||
|
flexboxLayoutWrapBefore: Bool = false,
|
||||||
|
onClicked: ((FileModel) -> Void)? = nil
|
||||||
|
) {
|
||||||
|
self.path = path
|
||||||
|
self.fileName = fileName
|
||||||
|
self.fileSize = fileSize
|
||||||
|
self.fileCreationTimestamp = fileCreationTimestamp
|
||||||
|
self.isEncrypted = isEncrypted
|
||||||
|
self.originalPath = originalPath
|
||||||
|
self.isFromEphemeralMessage = isFromEphemeralMessage
|
||||||
|
self.isWaitingToBeDownloaded = isWaitingToBeDownloaded
|
||||||
|
self.flexboxLayoutWrapBefore = flexboxLayoutWrapBefore
|
||||||
|
self.onClicked = onClicked
|
||||||
|
|
||||||
|
let ext = (path as NSString).pathExtension.lowercased()
|
||||||
|
self.isPdf = ext == "pdf"
|
||||||
|
|
||||||
|
let mime = FileModel.mimeType(from: ext)
|
||||||
|
self.mimeTypeString = mime
|
||||||
|
|
||||||
|
self.isImage = mime.hasPrefix("image/")
|
||||||
|
self.isVideoPreview = mime.hasPrefix("video/")
|
||||||
|
self.isAudio = mime.hasPrefix("audio/")
|
||||||
|
self.isMedia = isImage || isVideoPreview
|
||||||
|
|
||||||
|
self.month = FileModel.month(from: fileCreationTimestamp)
|
||||||
|
self.dateTime = FileModel.formatDate(timestamp: fileCreationTimestamp)
|
||||||
|
|
||||||
|
computeFileSize(fileSize)
|
||||||
|
updateTransferProgress(-1)
|
||||||
|
|
||||||
|
if !isWaitingToBeDownloaded {
|
||||||
|
if isPdf { loadPdfPreview() }
|
||||||
|
if isImage {
|
||||||
|
mediaPreview = path
|
||||||
|
mediaPreviewAvailable = true
|
||||||
|
} else if isVideoPreview {
|
||||||
|
loadVideoPreview()
|
||||||
|
}
|
||||||
|
if isVideoPreview || isAudio {
|
||||||
|
getDuration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
func onClick() {
|
||||||
|
onClicked?(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func destroy() {
|
||||||
|
guard isEncrypted else { return }
|
||||||
|
DispatchQueue.global(qos: .background).async {
|
||||||
|
try? FileManager.default.removeItem(atPath: self.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteFile() async {
|
||||||
|
try? FileManager.default.removeItem(atPath: path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeFileSize(_ size: Int64) {
|
||||||
|
formattedFileSize = FileModel.bytesToReadable(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTransferProgress(_ percent: Int) {
|
||||||
|
transferProgress = percent
|
||||||
|
transferProgressLabel = (percent < 0 || percent > 100) ? "" : "\(percent)%"
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Preview
|
||||||
|
private func loadPdfPreview() {
|
||||||
|
DispatchQueue.global(qos: .utility).async {
|
||||||
|
guard let pdf = PDFDocument(url: URL(fileURLWithPath: self.path)),
|
||||||
|
let page = pdf.page(at: 0) else { return }
|
||||||
|
|
||||||
|
let pageRect = page.bounds(for: .mediaBox)
|
||||||
|
let renderer = UIGraphicsImageRenderer(size: pageRect.size)
|
||||||
|
let image = renderer.image { ctx in
|
||||||
|
UIColor.white.set()
|
||||||
|
ctx.fill(pageRect)
|
||||||
|
page.draw(with: .mediaBox, to: ctx.cgContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let data = image.jpegData(compressionQuality: 0.8) {
|
||||||
|
let url = FileModel.cacheFileURL(name: "\(self.fileName).jpg")
|
||||||
|
try? data.write(to: url)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.mediaPreview = url.path
|
||||||
|
self.mediaPreviewAvailable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadVideoPreview() {
|
||||||
|
DispatchQueue.global(qos: .utility).async {
|
||||||
|
let asset = AVAsset(url: URL(fileURLWithPath: self.path))
|
||||||
|
let generator = AVAssetImageGenerator(asset: asset)
|
||||||
|
generator.appliesPreferredTrackTransform = true
|
||||||
|
|
||||||
|
let time = CMTime(seconds: 1, preferredTimescale: 600)
|
||||||
|
if let cgImage = try? generator.copyCGImage(at: time, actualTime: nil) {
|
||||||
|
let image = UIImage(cgImage: cgImage)
|
||||||
|
if let data = image.jpegData(compressionQuality: 0.8) {
|
||||||
|
let url = FileModel.cacheFileURL(name: "\(self.fileName).jpg")
|
||||||
|
try? data.write(to: url)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.mediaPreview = url.path
|
||||||
|
self.mediaPreviewAvailable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getDuration() {
|
||||||
|
let asset = AVAsset(url: URL(fileURLWithPath: path))
|
||||||
|
let seconds = Int(CMTimeGetSeconds(asset.duration))
|
||||||
|
audioVideoDuration = FileModel.formatDuration(seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Utils
|
||||||
|
private static func cacheFileURL(name: String) -> URL {
|
||||||
|
let dir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||||
|
return dir.appendingPathComponent(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func bytesToReadable(_ bytes: Int64) -> String {
|
||||||
|
let formatter = ByteCountFormatter()
|
||||||
|
formatter.countStyle = .file
|
||||||
|
return formatter.string(fromByteCount: bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func mimeType(from ext: String) -> String {
|
||||||
|
switch ext {
|
||||||
|
case "jpg", "jpeg", "png", "heic": return "image/jpeg"
|
||||||
|
case "mp4", "mov": return "video/mp4"
|
||||||
|
case "mp3", "wav", "m4a": return "audio/mpeg"
|
||||||
|
case "pdf": return "application/pdf"
|
||||||
|
default: return "application/octet-stream"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func formatDuration(_ seconds: Int) -> String {
|
||||||
|
let min = seconds / 60
|
||||||
|
let sec = seconds % 60
|
||||||
|
return String(format: "%02d:%02d", min, sec)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func formatDate(timestamp: Int64) -> String {
|
||||||
|
let date = Date(timeIntervalSince1970: TimeInterval(timestamp / 1000))
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateStyle = .medium
|
||||||
|
formatter.timeStyle = .short
|
||||||
|
return formatter.string(from: date)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func month(from timestamp: Int64) -> String {
|
||||||
|
let date = Date(timeIntervalSince1970: TimeInterval(timestamp / 1000))
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateFormat = "MMMM"
|
||||||
|
return formatter.string(from: date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* 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 Foundation
|
||||||
|
import linphonesw
|
||||||
|
|
||||||
|
final class ConversationDocumentsListViewModel: ObservableObject {
|
||||||
|
|
||||||
|
private static let TAG = "[ConversationDocumentsListViewModel]"
|
||||||
|
private static let CONTENTS_PER_PAGE = 20
|
||||||
|
|
||||||
|
@Published var documentsList: [FileModel] = []
|
||||||
|
@Published var operationInProgress: Bool = false
|
||||||
|
|
||||||
|
private var totalDocumentsCount: Int = -1
|
||||||
|
|
||||||
|
private var conversationModel: ConversationModel!
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if let conversationModelTmp = SharedMainViewModel.shared.displayedConversation {
|
||||||
|
self.conversationModel = conversationModelTmp
|
||||||
|
loadDocumentsList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Loading
|
||||||
|
private func loadDocumentsList() {
|
||||||
|
operationInProgress = true
|
||||||
|
totalDocumentsCount = self.conversationModel.chatRoom.documentContentsSize
|
||||||
|
|
||||||
|
let contentsToLoad = min(totalDocumentsCount, Self.CONTENTS_PER_PAGE)
|
||||||
|
let contents = self.conversationModel.chatRoom.getDocumentContentsRange(begin: 0, end: contentsToLoad)
|
||||||
|
|
||||||
|
documentsList = getFileModelsList(from: contents)
|
||||||
|
operationInProgress = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMoreData(totalItemsCount: Int) {
|
||||||
|
guard totalItemsCount < totalDocumentsCount else { return }
|
||||||
|
|
||||||
|
var upperBound = totalItemsCount + Self.CONTENTS_PER_PAGE
|
||||||
|
if upperBound > totalDocumentsCount {
|
||||||
|
upperBound = totalDocumentsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
let contents = self.conversationModel.chatRoom.getDocumentContentsRange(begin: totalItemsCount, end: upperBound)
|
||||||
|
let newModels = getFileModelsList(from: contents)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.documentsList.append(contentsOf: newModels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Mapping Content -> FileModel
|
||||||
|
private func getFileModelsList(from contents: [Content]) -> [FileModel] {
|
||||||
|
var list: [FileModel] = []
|
||||||
|
|
||||||
|
for documentContent in contents {
|
||||||
|
let isEncrypted = documentContent.isFileEncrypted
|
||||||
|
let originalPath = documentContent.filePath ?? ""
|
||||||
|
let path = isEncrypted ? documentContent.exportPlainFile() : originalPath
|
||||||
|
let name = documentContent.name ?? ""
|
||||||
|
let size = Int64(documentContent.size)
|
||||||
|
let timestamp = documentContent.creationTimestamp
|
||||||
|
|
||||||
|
if path.isEmpty || name.isEmpty { continue }
|
||||||
|
|
||||||
|
let ephemeral: Bool
|
||||||
|
if let messageId = documentContent.relatedChatMessageId {
|
||||||
|
if let chatMessage = self.conversationModel.chatRoom.findMessage(messageId: messageId) {
|
||||||
|
ephemeral = chatMessage.isEphemeral
|
||||||
|
} else {
|
||||||
|
ephemeral = self.conversationModel.chatRoom.ephemeralEnabled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ephemeral = self.conversationModel.chatRoom.ephemeralEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
let model = FileModel(
|
||||||
|
path: path,
|
||||||
|
fileName: name,
|
||||||
|
fileSize: size,
|
||||||
|
fileCreationTimestamp: Int64(timestamp),
|
||||||
|
isEncrypted: isEncrypted,
|
||||||
|
originalPath: originalPath,
|
||||||
|
isFromEphemeralMessage: ephemeral
|
||||||
|
)
|
||||||
|
|
||||||
|
list.append(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* 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 Foundation
|
||||||
|
import linphonesw
|
||||||
|
|
||||||
|
final class ConversationMediaListViewModel: ObservableObject {
|
||||||
|
|
||||||
|
private static let TAG = "[ConversationMediaListViewModel]"
|
||||||
|
private static let CONTENTS_PER_PAGE = 50
|
||||||
|
|
||||||
|
@Published var mediaList: [FileModel] = []
|
||||||
|
@Published var operationInProgress: Bool = false
|
||||||
|
|
||||||
|
private var totalMediaCount: Int = -1
|
||||||
|
|
||||||
|
private var conversationModel: ConversationModel!
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if let conversationModelTmp = SharedMainViewModel.shared.displayedConversation {
|
||||||
|
self.conversationModel = conversationModelTmp
|
||||||
|
loadMediaList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Loading
|
||||||
|
|
||||||
|
private func loadMediaList() {
|
||||||
|
operationInProgress = true
|
||||||
|
Log.info("\(Self.TAG) Loading media contents for conversation \(conversationModel.chatRoom.identifier ?? "No ID")")
|
||||||
|
|
||||||
|
totalMediaCount = conversationModel.chatRoom.mediaContentsSize
|
||||||
|
Log.info("\(Self.TAG) Media contents size is [\(totalMediaCount)]")
|
||||||
|
|
||||||
|
let contentsToLoad = min(totalMediaCount, Self.CONTENTS_PER_PAGE)
|
||||||
|
let contents = conversationModel.chatRoom.getMediaContentsRange(begin: 0, end: contentsToLoad)
|
||||||
|
|
||||||
|
Log.info("\(Self.TAG) \(contents.count) media have been fetched")
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.mediaList = self.getFileModelsList(from: contents)
|
||||||
|
self.operationInProgress = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMoreData(totalItemsCount: Int) {
|
||||||
|
CoreContext.shared.doOnCoreQueue { core in
|
||||||
|
Log.info("\(Self.TAG) Loading more data, current total is \(totalItemsCount), max size is \(self.totalMediaCount)")
|
||||||
|
|
||||||
|
guard totalItemsCount < self.totalMediaCount else { return }
|
||||||
|
|
||||||
|
var upperBound = totalItemsCount + Self.CONTENTS_PER_PAGE
|
||||||
|
if upperBound > self.totalMediaCount {
|
||||||
|
upperBound = self.totalMediaCount
|
||||||
|
}
|
||||||
|
|
||||||
|
let contents = self.conversationModel.chatRoom.getMediaContentsRange(begin: totalItemsCount, end: upperBound)
|
||||||
|
Log.info("\(Self.TAG) \(contents.count) contents loaded, adding them to list")
|
||||||
|
|
||||||
|
let newModels = self.getFileModelsList(from: contents)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.mediaList.append(contentsOf: newModels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Mapping Content -> FileModel
|
||||||
|
|
||||||
|
private func getFileModelsList(from contents: [Content]) -> [FileModel] {
|
||||||
|
var list: [FileModel] = []
|
||||||
|
|
||||||
|
for mediaContent in contents {
|
||||||
|
|
||||||
|
if mediaContent.isVoiceRecording { continue }
|
||||||
|
|
||||||
|
let isEncrypted = mediaContent.isFileEncrypted
|
||||||
|
let originalPath = mediaContent.filePath ?? ""
|
||||||
|
|
||||||
|
let path: String
|
||||||
|
if isEncrypted {
|
||||||
|
Log.info("\(Self.TAG) [VFS] Content is encrypted, requesting plain file path for file \(originalPath)")
|
||||||
|
path = mediaContent.exportPlainFile()
|
||||||
|
} else {
|
||||||
|
path = originalPath
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = mediaContent.name ?? ""
|
||||||
|
let size = Int64(mediaContent.size)
|
||||||
|
let timestamp = mediaContent.creationTimestamp
|
||||||
|
|
||||||
|
if !path.isEmpty && !name.isEmpty {
|
||||||
|
let model = FileModel(
|
||||||
|
path: path,
|
||||||
|
fileName: name,
|
||||||
|
fileSize: size,
|
||||||
|
fileCreationTimestamp: Int64(timestamp),
|
||||||
|
isEncrypted: isEncrypted,
|
||||||
|
originalPath: originalPath,
|
||||||
|
isFromEphemeralMessage: conversationModel.chatRoom.ephemeralEnabled
|
||||||
|
)
|
||||||
|
list.append(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -163,6 +163,11 @@
|
||||||
D7A2EDD62AC18115005D90FC /* SharedMainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A2EDD52AC18115005D90FC /* SharedMainViewModel.swift */; };
|
D7A2EDD62AC18115005D90FC /* SharedMainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A2EDD52AC18115005D90FC /* SharedMainViewModel.swift */; };
|
||||||
D7ADF6002AFE356400212231 /* Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADF5FF2AFE356400212231 /* Avatar.swift */; };
|
D7ADF6002AFE356400212231 /* Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADF5FF2AFE356400212231 /* Avatar.swift */; };
|
||||||
D7AEB9472F29128500298546 /* Shared.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = D7AEB9462F29128500298546 /* Shared.xcconfig */; };
|
D7AEB9472F29128500298546 /* Shared.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = D7AEB9462F29128500298546 /* Shared.xcconfig */; };
|
||||||
|
D7AEB9722F324A5E00298546 /* ConversationMediaListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AEB9712F324A5C00298546 /* ConversationMediaListFragment.swift */; };
|
||||||
|
D7AEB9742F324A6F00298546 /* ConversationDocumentsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AEB9732F324A6E00298546 /* ConversationDocumentsListFragment.swift */; };
|
||||||
|
D7AEB9762F39E2A400298546 /* ConversationMediaListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AEB9752F39E2A300298546 /* ConversationMediaListViewModel.swift */; };
|
||||||
|
D7AEB9782F39E2C300298546 /* ConversationDocumentsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AEB9772F39E2C100298546 /* ConversationDocumentsListViewModel.swift */; };
|
||||||
|
D7AEB97A2F39E83600298546 /* FileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AEB9792F39E83500298546 /* FileModel.swift */; };
|
||||||
D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */; };
|
D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */; };
|
||||||
D7B5678E2B28888F00DE63EB /* CallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B5678D2B28888F00DE63EB /* CallView.swift */; };
|
D7B5678E2B28888F00DE63EB /* CallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B5678D2B28888F00DE63EB /* CallView.swift */; };
|
||||||
D7B99E992B29B39000BE7BF2 /* CallViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B99E982B29B39000BE7BF2 /* CallViewModel.swift */; };
|
D7B99E992B29B39000BE7BF2 /* CallViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B99E982B29B39000BE7BF2 /* CallViewModel.swift */; };
|
||||||
|
|
@ -423,6 +428,11 @@
|
||||||
D7A2EDDA2AC19EEC005D90FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
D7A2EDDA2AC19EEC005D90FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||||
D7ADF5FF2AFE356400212231 /* Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Avatar.swift; sourceTree = "<group>"; };
|
D7ADF5FF2AFE356400212231 /* Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Avatar.swift; sourceTree = "<group>"; };
|
||||||
D7AEB9462F29128500298546 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = "<group>"; };
|
D7AEB9462F29128500298546 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = "<group>"; };
|
||||||
|
D7AEB9712F324A5C00298546 /* ConversationMediaListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMediaListFragment.swift; sourceTree = "<group>"; };
|
||||||
|
D7AEB9732F324A6E00298546 /* ConversationDocumentsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationDocumentsListFragment.swift; sourceTree = "<group>"; };
|
||||||
|
D7AEB9752F39E2A300298546 /* ConversationMediaListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMediaListViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
D7AEB9772F39E2C100298546 /* ConversationDocumentsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationDocumentsListViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
D7AEB9792F39E83500298546 /* FileModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileModel.swift; sourceTree = "<group>"; };
|
||||||
D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInnerFragment.swift; sourceTree = "<group>"; };
|
D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInnerFragment.swift; sourceTree = "<group>"; };
|
||||||
D7B5678D2B28888F00DE63EB /* CallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallView.swift; sourceTree = "<group>"; };
|
D7B5678D2B28888F00DE63EB /* CallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallView.swift; sourceTree = "<group>"; };
|
||||||
D7B99E982B29B39000BE7BF2 /* CallViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallViewModel.swift; sourceTree = "<group>"; };
|
D7B99E982B29B39000BE7BF2 /* CallViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -638,6 +648,7 @@
|
||||||
D70959EF2B8DF33B0014AC0B /* Model */ = {
|
D70959EF2B8DF33B0014AC0B /* Model */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D7AEB9792F39E83500298546 /* FileModel.swift */,
|
||||||
D77A080D2CB6BCA10095D589 /* MessageConferenceInfo.swift */,
|
D77A080D2CB6BCA10095D589 /* MessageConferenceInfo.swift */,
|
||||||
D70959F02B8DF3EC0014AC0B /* ConversationModel.swift */,
|
D70959F02B8DF3EC0014AC0B /* ConversationModel.swift */,
|
||||||
D7E6ADF22B9875C20009A2BC /* Message.swift */,
|
D7E6ADF22B9875C20009A2BC /* Message.swift */,
|
||||||
|
|
@ -1068,6 +1079,8 @@
|
||||||
D7CEE0362B7A212C00FD79B7 /* ViewModel */ = {
|
D7CEE0362B7A212C00FD79B7 /* ViewModel */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D7AEB9772F39E2C100298546 /* ConversationDocumentsListViewModel.swift */,
|
||||||
|
D7AEB9752F39E2A300298546 /* ConversationMediaListViewModel.swift */,
|
||||||
D7CEE0372B7A214F00FD79B7 /* ConversationsListViewModel.swift */,
|
D7CEE0372B7A214F00FD79B7 /* ConversationsListViewModel.swift */,
|
||||||
D70A26EF2B7D02E6006CC8FC /* ConversationViewModel.swift */,
|
D70A26EF2B7D02E6006CC8FC /* ConversationViewModel.swift */,
|
||||||
D759CB652C3FBE1D00AC35E8 /* StartConversationViewModel.swift */,
|
D759CB652C3FBE1D00AC35E8 /* StartConversationViewModel.swift */,
|
||||||
|
|
@ -1079,6 +1092,8 @@
|
||||||
D7CEE0392B7A232200FD79B7 /* Fragments */ = {
|
D7CEE0392B7A232200FD79B7 /* Fragments */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D7AEB9732F324A6E00298546 /* ConversationDocumentsListFragment.swift */,
|
||||||
|
D7AEB9712F324A5C00298546 /* ConversationMediaListFragment.swift */,
|
||||||
D7EFD1E32CD11F53005E67CD /* EphemeralFragment.swift */,
|
D7EFD1E32CD11F53005E67CD /* EphemeralFragment.swift */,
|
||||||
D7CEE03A2B7A234200FD79B7 /* ConversationsFragment.swift */,
|
D7CEE03A2B7A234200FD79B7 /* ConversationsFragment.swift */,
|
||||||
D7CEE03C2B7A23B200FD79B7 /* ConversationsListFragment.swift */,
|
D7CEE03C2B7A23B200FD79B7 /* ConversationsListFragment.swift */,
|
||||||
|
|
@ -1453,6 +1468,7 @@
|
||||||
D7E6D0492AE933AD00A57AAF /* FavoriteContactsListFragment.swift in Sources */,
|
D7E6D0492AE933AD00A57AAF /* FavoriteContactsListFragment.swift in Sources */,
|
||||||
662B69DB2B25DE25007118BF /* ProviderDelegate.swift in Sources */,
|
662B69DB2B25DE25007118BF /* ProviderDelegate.swift in Sources */,
|
||||||
D706BA822ADD72D100278F45 /* DeviceRotationViewModifier.swift in Sources */,
|
D706BA822ADD72D100278F45 /* DeviceRotationViewModifier.swift in Sources */,
|
||||||
|
D7AEB9742F324A6F00298546 /* ConversationDocumentsListFragment.swift in Sources */,
|
||||||
D732A9132B04C7A300DB42BA /* HistoryListFragment.swift in Sources */,
|
D732A9132B04C7A300DB42BA /* HistoryListFragment.swift in Sources */,
|
||||||
D7C2DA1D2CA44DE400A2441B /* EventModel.swift in Sources */,
|
D7C2DA1D2CA44DE400A2441B /* EventModel.swift in Sources */,
|
||||||
D719ABC92ABC6FD700B41C10 /* CoreContext.swift in Sources */,
|
D719ABC92ABC6FD700B41C10 /* CoreContext.swift in Sources */,
|
||||||
|
|
@ -1464,6 +1480,7 @@
|
||||||
66C491FD2B24D36500CEA16D /* AudioRouteUtils.swift in Sources */,
|
66C491FD2B24D36500CEA16D /* AudioRouteUtils.swift in Sources */,
|
||||||
D738ACEE2E857BF10039F7D1 /* KeyboardResponder.swift in Sources */,
|
D738ACEE2E857BF10039F7D1 /* KeyboardResponder.swift in Sources */,
|
||||||
D70959F12B8DF3EC0014AC0B /* ConversationModel.swift in Sources */,
|
D70959F12B8DF3EC0014AC0B /* ConversationModel.swift in Sources */,
|
||||||
|
D7AEB9762F39E2A400298546 /* ConversationMediaListViewModel.swift in Sources */,
|
||||||
C6DC4E3D2C199C4E009096FD /* BundleExtenion.swift in Sources */,
|
C6DC4E3D2C199C4E009096FD /* BundleExtenion.swift in Sources */,
|
||||||
D7343FEF2D3FE16C0059D784 /* HelpViewModel.swift in Sources */,
|
D7343FEF2D3FE16C0059D784 /* HelpViewModel.swift in Sources */,
|
||||||
D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */,
|
D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */,
|
||||||
|
|
@ -1474,6 +1491,7 @@
|
||||||
D7C3650A2AF001C300FE6142 /* EditContactFragment.swift in Sources */,
|
D7C3650A2AF001C300FE6142 /* EditContactFragment.swift in Sources */,
|
||||||
D7A03FBD2ACC2DB60081A588 /* ContactsView.swift in Sources */,
|
D7A03FBD2ACC2DB60081A588 /* ContactsView.swift in Sources */,
|
||||||
D78607712D36CB8A009E6A7E /* SettingsAdvancedFragment.swift in Sources */,
|
D78607712D36CB8A009E6A7E /* SettingsAdvancedFragment.swift in Sources */,
|
||||||
|
D7AEB97A2F39E83600298546 /* FileModel.swift in Sources */,
|
||||||
D762102C2E97FDFD002E7999 /* CardDavAddressBookConfigurationFragment.swift in Sources */,
|
D762102C2E97FDFD002E7999 /* CardDavAddressBookConfigurationFragment.swift in Sources */,
|
||||||
66E50A492BD12B2300AD61CA /* MeetingsView.swift in Sources */,
|
66E50A492BD12B2300AD61CA /* MeetingsView.swift in Sources */,
|
||||||
D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */,
|
D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */,
|
||||||
|
|
@ -1482,6 +1500,7 @@
|
||||||
C6A5A9452C10B6270070FEA4 /* OIDAuthStateExtension.swift in Sources */,
|
C6A5A9452C10B6270070FEA4 /* OIDAuthStateExtension.swift in Sources */,
|
||||||
D732A90F2B04C3B400DB42BA /* HistoryFragment.swift in Sources */,
|
D732A90F2B04C3B400DB42BA /* HistoryFragment.swift in Sources */,
|
||||||
D79F1C162CD3D6AD00FF0A05 /* ConversationInfoFragment.swift in Sources */,
|
D79F1C162CD3D6AD00FF0A05 /* ConversationInfoFragment.swift in Sources */,
|
||||||
|
D7AEB9722F324A5E00298546 /* ConversationMediaListFragment.swift in Sources */,
|
||||||
D7D1F5262EDD91B30034EEB0 /* RecordingMediaPlayerFragment.swift in Sources */,
|
D7D1F5262EDD91B30034EEB0 /* RecordingMediaPlayerFragment.swift in Sources */,
|
||||||
D7C500422D2BE98100DD53EC /* AccountSettingsViewModel.swift in Sources */,
|
D7C500422D2BE98100DD53EC /* AccountSettingsViewModel.swift in Sources */,
|
||||||
D79622342B1DFE600037EACD /* DialerBottomSheet.swift in Sources */,
|
D79622342B1DFE600037EACD /* DialerBottomSheet.swift in Sources */,
|
||||||
|
|
@ -1576,6 +1595,7 @@
|
||||||
D726E43F2B19E56F0083C415 /* StartCallViewModel.swift in Sources */,
|
D726E43F2B19E56F0083C415 /* StartCallViewModel.swift in Sources */,
|
||||||
D70A26EE2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift in Sources */,
|
D70A26EE2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift in Sources */,
|
||||||
D7D1698C2AE66FA500109A5C /* MagicSearchSingleton.swift in Sources */,
|
D7D1698C2AE66FA500109A5C /* MagicSearchSingleton.swift in Sources */,
|
||||||
|
D7AEB9782F39E2C300298546 /* ConversationDocumentsListViewModel.swift in Sources */,
|
||||||
D714DE602C1B3B34006C1F1D /* RegisterViewModel.swift in Sources */,
|
D714DE602C1B3B34006C1F1D /* RegisterViewModel.swift in Sources */,
|
||||||
D70C82A72C85F5910087F43F /* ConversationForwardMessageViewModel.swift in Sources */,
|
D70C82A72C85F5910087F43F /* ConversationForwardMessageViewModel.swift in Sources */,
|
||||||
D74DA0122C047F0700A8561D /* HistoryModel.swift in Sources */,
|
D74DA0122C047F0700A8561D /* HistoryModel.swift in Sources */,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue