Share contact vcard

This commit is contained in:
benoit.martins 2023-11-09 16:58:50 +01:00
parent 3b625b9063
commit ada6065b21
12 changed files with 323 additions and 164 deletions

View file

@ -28,6 +28,7 @@
D72343342ACEFFC3009AA24E /* QRScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72343332ACEFFC3009AA24E /* QRScanner.swift */; };
D72343362AD037AF009AA24E /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72343352AD037AF009AA24E /* ToastView.swift */; };
D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72992382ADD7F68003AF125 /* HistoryContactFragment.swift */; };
D732A9092AFD235500DB42BA /* ShareSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A9082AFD235500DB42BA /* ShareSheetController.swift */; };
D748BF2C2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */; };
D748BF2E2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */; };
D74C9CF82ACACECE0021626A /* WelcomePage1Fragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74C9CF72ACACECE0021626A /* WelcomePage1Fragment.swift */; };
@ -93,6 +94,7 @@
D72343332ACEFFC3009AA24E /* QRScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScanner.swift; sourceTree = "<group>"; };
D72343352AD037AF009AA24E /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
D72992382ADD7F68003AF125 /* HistoryContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryContactFragment.swift; sourceTree = "<group>"; };
D732A9082AFD235500DB42BA /* ShareSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheetController.swift; sourceTree = "<group>"; };
D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountLoginFragment.swift; sourceTree = "<group>"; };
D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountWarningFragment.swift; sourceTree = "<group>"; };
D74C9CF72ACACECE0021626A /* WelcomePage1Fragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomePage1Fragment.swift; sourceTree = "<group>"; };
@ -162,6 +164,7 @@
D7D1698B2AE66FA500109A5C /* MagicSearchSingleton.swift */,
D7C3650D2AF15BF200FE6142 /* PhotoPicker.swift */,
D7C48DF32AFA66F900D938CB /* EditContactController.swift */,
D732A9082AFD235500DB42BA /* ShareSheetController.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -532,6 +535,7 @@
D748BF2C2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift in Sources */,
D74C9CF82ACACECE0021626A /* WelcomePage1Fragment.swift in Sources */,
D7E6D0552AEBFCCE00A57AAF /* ContactsInnerFragment.swift in Sources */,
D732A9092AFD235500DB42BA /* ShareSheetController.swift in Sources */,
D72343362AD037AF009AA24E /* ToastView.swift in Sources */,
D7FB55112AD447FD00A5AB15 /* RegisterFragment.swift in Sources */,
D7C48DF62AFCDF4700D938CB /* ContactInnerActionsFragment.swift in Sources */,

View file

@ -31,6 +31,7 @@ struct ContactFragment: View {
@Binding var isShowDismissPopup: Bool
@State private var showingSheet = false
@State private var showShareSheet = false
var body: some View {
if #available(iOS 16.0, *) {
@ -38,40 +39,56 @@ struct ContactFragment: View {
ContactInnerFragment(
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,
cnContact: CNContact(),
isShowDeletePopup: $isShowDeletePopup,
cnContact: CNContact(),
isShowDeletePopup: $isShowDeletePopup,
showingSheet: $showingSheet,
showShareSheet: $showShareSheet,
isShowDismissPopup: $isShowDismissPopup
)
.sheet(isPresented: $showingSheet) {
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
.presentationDetents([.fraction(0.2)])
}
.sheet(isPresented: $showingSheet) {
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
.presentationDetents([.fraction(0.2)])
}
.sheet(isPresented: $showShareSheet) {
ShareSheet(friendToShare: MagicSearchSingleton.shared.lastSearch[contactViewModel.indexDisplayedFriend!].friend!)
.presentationDetents([.medium])
.edgesIgnoringSafeArea(.bottom)
}
} else {
ContactInnerFragment(
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,
cnContact: CNContact(),
cnContact: CNContact(),
isShowDeletePopup: $isShowDeletePopup,
showingSheet: $showingSheet,
showShareSheet: $showShareSheet,
isShowDismissPopup: $isShowDismissPopup
)
.halfSheet(showSheet: $showingSheet) {
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
} onDismiss: {}
.halfSheet(showSheet: $showingSheet) {
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
} onDismiss: {}
.sheet(isPresented: $showShareSheet) {
ShareSheet(friendToShare: MagicSearchSingleton.shared.lastSearch[contactViewModel.indexDisplayedFriend!].friend!)
.edgesIgnoringSafeArea(.bottom)
}
}
} else {
ContactInnerFragment(
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,
cnContact: CNContact(),
cnContact: CNContact(),
isShowDeletePopup: $isShowDeletePopup,
showingSheet: $showingSheet,
showShareSheet: $showShareSheet,
isShowDismissPopup: $isShowDismissPopup
)
.halfSheet(showSheet: $showingSheet) {
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
} onDismiss: {}
.halfSheet(showSheet: $showingSheet) {
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
} onDismiss: {}
.sheet(isPresented: $showShareSheet) {
ShareSheet(friendToShare: MagicSearchSingleton.shared.lastSearch[contactViewModel.indexDisplayedFriend!].friend!)
.edgesIgnoringSafeArea(.bottom)
}
}
}

View file

@ -16,6 +16,7 @@ struct ContactInnerActionsFragment: View {
@State private var informationIsOpen = true
@Binding var showingSheet: Bool
@Binding var showShareSheet: Bool
@Binding var isShowDeletePopup: Bool
@Binding var isShowDismissPopup: Bool
@ -278,27 +279,6 @@ struct ContactInnerActionsFragment: View {
}
)
}
/*
Button {
} label: {
HStack {
Image("pencil-simple")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
Text("Edit")
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
.fixedSize(horizontal: false, vertical: true)
Spacer()
}
.padding(.vertical, 15)
.padding(.horizontal, 20)
}
*/
VStack {
Divider()
@ -340,6 +320,7 @@ struct ContactInnerActionsFragment: View {
.padding(.horizontal)
Button {
showShareSheet.toggle()
} label: {
HStack {
Image("share-network")
@ -453,6 +434,7 @@ struct ContactInnerActionsFragment: View {
contactViewModel: ContactViewModel(),
editContactViewModel: EditContactViewModel(),
showingSheet: .constant(false),
showShareSheet: .constant(false),
isShowDeletePopup: .constant(false),
isShowDismissPopup: .constant(false),
actionEditButton: {}

View file

@ -37,6 +37,7 @@ struct ContactInnerFragment: View {
@Binding var isShowDeletePopup: Bool
@Binding var showingSheet: Bool
@Binding var showShareSheet: Bool
@Binding var isShowDismissPopup: Bool
var body: some View {
@ -122,6 +123,7 @@ struct ContactInnerFragment: View {
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
case .failure:
@ -253,6 +255,7 @@ struct ContactInnerFragment: View {
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,
showingSheet: $showingSheet,
showShareSheet: $showShareSheet,
isShowDeletePopup: $isShowDeletePopup,
isShowDismissPopup: $isShowDismissPopup,
actionEditButton: editNativeContact
@ -305,6 +308,7 @@ struct ContactInnerFragment: View {
editContactViewModel: EditContactViewModel(),
isShowDeletePopup: .constant(false),
showingSheet: .constant(false),
showShareSheet: .constant(false),
isShowDismissPopup: .constant(false)
)
}

View file

@ -93,7 +93,12 @@ struct ContactListBottomSheet: View {
if contactViewModel.stringToCopy.prefix(4) != "sip:" {
Button {
if #available(iOS 16.0, *) {
showingSheet.toggle()
if idiom != .pad {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
} else {
showingSheet.toggle()
dismiss()

View file

@ -28,6 +28,7 @@ struct ContactsFragment: View {
@Binding var isShowDeletePopup: Bool
@State private var showingSheet = false
@State private var showShareSheet = false
var body: some View {
ZStack {
@ -35,20 +36,48 @@ struct ContactsFragment: View {
if idiom != .pad {
ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet)
.sheet(isPresented: $showingSheet) {
ContactsListBottomSheet(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet)
ContactsListBottomSheet(
contactViewModel: contactViewModel,
isShowDeletePopup: $isShowDeletePopup,
showingSheet: $showingSheet,
showShareSheet: $showShareSheet
)
.presentationDetents([.fraction(0.2)])
}
.sheet(isPresented: $showShareSheet) {
ShareSheet(friendToShare: contactViewModel.selectedFriendToShare!)
.presentationDetents([.medium])
.edgesIgnoringSafeArea(.bottom)
}
} else {
ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet)
.halfSheet(showSheet: $showingSheet) {
ContactsListBottomSheet(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet)
ContactsListBottomSheet(
contactViewModel: contactViewModel,
isShowDeletePopup: $isShowDeletePopup,
showingSheet: $showingSheet,
showShareSheet: $showShareSheet
)
} onDismiss: {}
.sheet(isPresented: $showShareSheet) {
ShareSheet(friendToShare: contactViewModel.selectedFriendToShare!)
.edgesIgnoringSafeArea(.bottom)
}
}
} else {
ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet)
.halfSheet(showSheet: $showingSheet) {
ContactsListBottomSheet(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet)
ContactsListBottomSheet(
contactViewModel: contactViewModel,
isShowDeletePopup: $isShowDeletePopup,
showingSheet: $showingSheet,
showShareSheet: $showShareSheet
)
} onDismiss: {}
.sheet(isPresented: $showShareSheet) {
ShareSheet(friendToShare: contactViewModel.selectedFriendToShare!)
.edgesIgnoringSafeArea(.bottom)
}
}
}
}

View file

@ -19,147 +19,169 @@
import SwiftUI
import linphonesw
import Contacts
struct ContactsListBottomSheet: View {
@Environment(\.dismiss) var dismiss
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
@ObservedObject var magicSearch = MagicSearchSingleton.shared
@ObservedObject var contactViewModel: ContactViewModel
@State private var orientation = UIDevice.current.orientation
@Environment(\.dismiss) var dismiss
@ObservedObject var magicSearch = MagicSearchSingleton.shared
@ObservedObject var contactViewModel: ContactViewModel
@State private var orientation = UIDevice.current.orientation
@Binding var isShowDeletePopup: Bool
@Binding var showingSheet: Bool
var body: some View {
VStack(alignment: .leading) {
if idiom != .pad && (orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
Spacer()
HStack {
Spacer()
Button("Close") {
if #available(iOS 16.0, *) {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
}
}
.padding(.trailing)
}
Spacer()
Button {
if contactViewModel.selectedFriend != nil {
contactViewModel.selectedFriend!.starred.toggle()
}
self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
if #available(iOS 16.0, *) {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
} label: {
HStack {
@Binding var showingSheet: Bool
@Binding var showShareSheet: Bool
var body: some View {
VStack(alignment: .leading) {
if idiom != .pad && (orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
Spacer()
HStack {
Spacer()
Button("Close") {
if #available(iOS 16.0, *) {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
}
}
.padding(.trailing)
}
Spacer()
Button {
if contactViewModel.selectedFriend != nil {
contactViewModel.selectedFriend!.starred.toggle()
}
self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
if #available(iOS 16.0, *) {
if idiom != .pad {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
} else {
showingSheet.toggle()
dismiss()
}
} label: {
HStack {
Image(contactViewModel.selectedFriend != nil && contactViewModel.selectedFriend!.starred == true ? "heart-fill" : "heart")
.renderingMode(.template)
.resizable()
.renderingMode(.template)
.resizable()
.foregroundStyle(
contactViewModel.selectedFriend != nil && contactViewModel.selectedFriend!.starred == true
? Color.redDanger500
: Color.grayMain2c500
)
.frame(width: 25, height: 25, alignment: .leading)
Text(contactViewModel.selectedFriend != nil && contactViewModel.selectedFriend!.starred == true
? "Remove from favourites"
: "Add to favourites")
.default_text_style(styleSize: 16)
Spacer()
}
.frame(maxHeight: .infinity)
}
.padding(.horizontal, 30)
.background(Color.gray100)
VStack {
Divider()
}
.frame(maxWidth: .infinity)
Button {
if #available(iOS 16.0, *) {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
} label: {
HStack {
Image("share-network")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25, alignment: .leading)
Text("Share")
.default_text_style(styleSize: 16)
Spacer()
}
.frame(maxHeight: .infinity)
}
.padding(.horizontal, 30)
.background(Color.gray100)
VStack {
Divider()
}
.frame(maxWidth: .infinity)
Button {
if contactViewModel.selectedFriend != nil {
isShowDeletePopup.toggle()
}
.frame(width: 25, height: 25, alignment: .leading)
Text(contactViewModel.selectedFriend != nil && contactViewModel.selectedFriend!.starred == true
? "Remove from favourites"
: "Add to favourites")
.default_text_style(styleSize: 16)
Spacer()
}
.frame(maxHeight: .infinity)
}
.padding(.horizontal, 30)
.background(Color.gray100)
VStack {
Divider()
}
.frame(maxWidth: .infinity)
Button {
if #available(iOS 16.0, *) {
if idiom != .pad {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
} else {
showingSheet.toggle()
dismiss()
}
if #available(iOS 16.0, *) {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
} label: {
HStack {
Image("trash-simple")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.redDanger500)
.frame(width: 25, height: 25, alignment: .leading)
Text("Delete")
.foregroundStyle(Color.redDanger500)
.default_text_style(styleSize: 16)
Spacer()
}
.frame(maxHeight: .infinity)
}
.padding(.horizontal, 30)
.background(Color.gray100)
}
contactViewModel.selectedFriendToShare = contactViewModel.selectedFriend
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
showShareSheet.toggle()
}
} label: {
HStack {
Image("share-network")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25, alignment: .leading)
Text("Share")
.default_text_style(styleSize: 16)
Spacer()
}
.frame(maxHeight: .infinity)
}
.padding(.horizontal, 30)
.background(Color.gray100)
VStack {
Divider()
}
.frame(maxWidth: .infinity)
Button {
if contactViewModel.selectedFriend != nil {
isShowDeletePopup.toggle()
}
if #available(iOS 16.0, *) {
if idiom != .pad {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
} else {
showingSheet.toggle()
dismiss()
}
} label: {
HStack {
Image("trash-simple")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.redDanger500)
.frame(width: 25, height: 25, alignment: .leading)
Text("Delete")
.foregroundStyle(Color.redDanger500)
.default_text_style(styleSize: 16)
Spacer()
}
.frame(maxHeight: .infinity)
}
.padding(.horizontal, 30)
.background(Color.gray100)
}
.background(Color.gray100)
.frame(maxWidth: .infinity)
.onRotate { newOrientation in
orientation = newOrientation
}
.frame(maxWidth: .infinity)
.onRotate { newOrientation in
orientation = newOrientation
}
.onDisappear {
contactViewModel.selectedFriend = nil
}
}
}
}

View file

@ -72,6 +72,7 @@ struct ContactsListFragment: View {
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 45, height: 45)
.clipShape(Circle())
case .failure:

View file

@ -137,6 +137,7 @@ struct EditContactFragment: View {
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
case .failure:
@ -156,6 +157,7 @@ struct EditContactFragment: View {
} else {
Image(uiImage: selectedImage!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
}

View file

@ -48,6 +48,7 @@ struct FavoriteContactsListFragment: View {
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 45, height: 45)
.clipShape(Circle())
case .failure:

View file

@ -26,6 +26,7 @@ class ContactViewModel: ObservableObject {
var stringToCopy: String = ""
var selectedFriend: Friend?
var selectedFriendToShare: Friend?
var selectedFriendToDelete: Friend?
private var magicSearch = MagicSearchSingleton.shared

View file

@ -0,0 +1,91 @@
/*
* 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 SwiftUI
import linphonesw
struct ShareSheet: UIViewControllerRepresentable {
typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
let friendToShare: Friend
var activityItems: [Any] = []
let applicationActivities: [UIActivity]? = nil
let excludedActivityTypes: [UIActivity.ActivityType]? = nil
let callback: Callback? = nil
func makeUIViewController(context: Context) -> UIActivityViewController {
let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first
if directoryURL != nil {
if friendToShare.name != nil {
let filename = friendToShare.name!.replacingOccurrences(of: " ", with: "")
let fileURL = directoryURL!
.appendingPathComponent(filename)
.appendingPathExtension("vcf")
if friendToShare.vcard != nil {
try? friendToShare.vcard!.asVcard4String().write(to: fileURL, atomically: false, encoding: String.Encoding.utf8)
let controller = UIActivityViewController(
activityItems: [fileURL],
applicationActivities: applicationActivities
)
controller.excludedActivityTypes = excludedActivityTypes
controller.completionWithItemsHandler = callback
return controller
}
}
}
let controller = UIActivityViewController(
activityItems: activityItems,
applicationActivities: applicationActivities)
controller.excludedActivityTypes = excludedActivityTypes
controller.completionWithItemsHandler = callback
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
// nothing to do here
}
func shareContacts(friend: String) {
let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first
if directoryURL != nil {
let filename = NSUUID().uuidString
let fileURL = directoryURL!
.appendingPathComponent(filename)
.appendingPathExtension("vcf")
try? friend.write(to: fileURL, atomically: false, encoding: String.Encoding.utf8)
}
/*
let activityViewController = UIActivityViewController(
activityItems: [fileURL],
applicationActivities: nil
)
*/
}
}