Add shared extension

This commit is contained in:
Benoit Martins 2025-06-25 12:33:24 +02:00
parent 54fa7be51d
commit faf84164a4
15 changed files with 557 additions and 134 deletions

View file

@ -394,118 +394,122 @@ final class ContactsManager: ObservableObject {
}
func addFriendListDelegate() {
CoreContext.shared.mCore.friendListSubscriptionEnabled = true
CoreContext.shared.mCore.friendsLists.forEach { friendList in
friendList.updateSubscriptions()
}
let friendListDelegateTmp = FriendListDelegateStub(
onContactCreated: { (friendList: FriendList, linphoneFriend: Friend) in
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onContactCreated")
},
onContactDeleted: { (friendList: FriendList, linphoneFriend: Friend) in
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onContactDeleted")
},
onContactUpdated: { (friendList: FriendList, newFriend: Friend, oldFriend: Friend) in
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onContactUpdated")
},
onSyncStatusChanged: { (friendList: FriendList, status: FriendList.SyncStatus?, message: String?) in
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onSyncStatusChanged")
if status == .Successful {
friendList.friends.forEach { friend in
let addressTmp = friend.address?.clone()?.asStringUriOnly() ?? ""
let newContact = Contact(
identifier: UUID().uuidString,
firstName: friend.name ?? addressTmp,
lastName: "",
organizationName: "",
jobTitle: "",
displayName: friend.address?.displayName ?? "",
sipAddresses: friend.addresses.map { $0.asStringUriOnly() },
phoneNumbers: [],
imageData: ""
)
self.textToImageInMainThread(firstName: friend.name ?? addressTmp, lastName: "") { image in
self.saveImage(
image: image,
name: friend.name ?? addressTmp,
prefix: "-default",
contact: newContact, linphoneFriend: false, existingFriend: friend) {
}
}
}
}
MagicSearchSingleton.shared.searchForContactsWithoutCoreThread(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
},
onPresenceReceived: { (friendList: FriendList, friends: [Friend?]) in
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onPresenceReceived \(friends.count)")
},
onNewSipAddressDiscovered: { (friendList: FriendList, linphoneFriend: Friend, sipUri: String) in
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onNewSipAddressDiscovered \(linphoneFriend.name ?? "")")
var addedAvatarListModel: [ContactAvatarModel] = []
if !self.avatarListModel.contains(where: {$0.friend?.name == linphoneFriend.name}) {
if let address = try? Factory.Instance.createAddress(addr: sipUri) {
linphoneFriend.edit()
linphoneFriend.addAddress(address: address)
linphoneFriend.done()
let addressTmp = linphoneFriend.address?.clone()?.asStringUriOnly() ?? ""
addedAvatarListModel.append(
ContactAvatarModel(
friend: linphoneFriend,
name: linphoneFriend.name ?? "",
address: addressTmp,
withPresence: true
)
)
addedAvatarListModel += self.avatarListModel
addedAvatarListModel = addedAvatarListModel.sorted { $0.name < $1.name }
DispatchQueue.main.async {
self.avatarListModel = addedAvatarListModel
NotificationCenter.default.post(
name: NSNotification.Name("ContactAdded"),
object: nil,
userInfo: ["address": addressTmp]
)
}
}
}
self.coreContext.doOnCoreQueue { _ in
CoreContext.shared.mCore.friendListSubscriptionEnabled = true
CoreContext.shared.mCore.friendsLists.forEach { friendList in
friendList.updateSubscriptions()
}
let friendListDelegateTmp = FriendListDelegateStub(
onContactCreated: { (friendList: FriendList, linphoneFriend: Friend) in
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onContactCreated")
},
onContactDeleted: { (friendList: FriendList, linphoneFriend: Friend) in
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onContactDeleted")
},
onContactUpdated: { (friendList: FriendList, newFriend: Friend, oldFriend: Friend) in
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onContactUpdated")
},
onSyncStatusChanged: { (friendList: FriendList, status: FriendList.SyncStatus?, message: String?) in
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onSyncStatusChanged")
if status == .Successful {
friendList.friends.forEach { friend in
let addressTmp = friend.address?.clone()?.asStringUriOnly() ?? ""
let newContact = Contact(
identifier: UUID().uuidString,
firstName: friend.name ?? addressTmp,
lastName: "",
organizationName: "",
jobTitle: "",
displayName: friend.address?.displayName ?? "",
sipAddresses: friend.addresses.map { $0.asStringUriOnly() },
phoneNumbers: [],
imageData: ""
)
self.textToImageInMainThread(firstName: friend.name ?? addressTmp, lastName: "") { image in
self.saveImage(
image: image,
name: friend.name ?? addressTmp,
prefix: "-default",
contact: newContact, linphoneFriend: false, existingFriend: friend) {
}
}
}
}
MagicSearchSingleton.shared.searchForContactsWithoutCoreThread(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
},
onPresenceReceived: { (friendList: FriendList, friends: [Friend?]) in
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onPresenceReceived \(friends.count)")
},
onNewSipAddressDiscovered: { (friendList: FriendList, linphoneFriend: Friend, sipUri: String) in
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onNewSipAddressDiscovered \(linphoneFriend.name ?? "")")
var addedAvatarListModel: [ContactAvatarModel] = []
if !self.avatarListModel.contains(where: {$0.friend?.name == linphoneFriend.name}) {
if let address = try? Factory.Instance.createAddress(addr: sipUri) {
linphoneFriend.edit()
linphoneFriend.addAddress(address: address)
linphoneFriend.done()
let addressTmp = linphoneFriend.address?.clone()?.asStringUriOnly() ?? ""
addedAvatarListModel.append(
ContactAvatarModel(
friend: linphoneFriend,
name: linphoneFriend.name ?? "",
address: addressTmp,
withPresence: true
)
)
addedAvatarListModel += self.avatarListModel
addedAvatarListModel = addedAvatarListModel.sorted { $0.name < $1.name }
DispatchQueue.main.async {
self.avatarListModel = addedAvatarListModel
NotificationCenter.default.post(
name: NSNotification.Name("ContactAdded"),
object: nil,
userInfo: ["address": addressTmp]
)
}
}
}
}
)
CoreContext.shared.mCore.friendsLists.forEach { friendList in
friendList.addDelegate(delegate: friendListDelegateTmp)
}
)
CoreContext.shared.mCore.friendsLists.forEach { friendList in
friendList.addDelegate(delegate: friendListDelegateTmp)
}
}
func addCoreDelegate(core: Core) {
self.coreDelegate = CoreDelegateStub(
onFriendListCreated: { (_: Core, friendList: FriendList) in
Log.info("\(ContactsManager.TAG) Friend list \(friendList.displayName) created")
if self.friendListDelegate != nil {
friendList.addDelegate(delegate: self.friendListDelegate!)
self.coreContext.doOnCoreQueue { _ in
self.coreDelegate = CoreDelegateStub(
onFriendListCreated: { (_: Core, friendList: FriendList) in
Log.info("\(ContactsManager.TAG) Friend list \(friendList.displayName) created")
if self.friendListDelegate != nil {
friendList.addDelegate(delegate: self.friendListDelegate!)
}
}, onFriendListRemoved: { (_: Core, friendList: FriendList) in
Log.info("\(ContactsManager.TAG) Friend list \(friendList.displayName) removed")
if self.friendListDelegate != nil {
friendList.removeDelegate(delegate: self.friendListDelegate!)
}
}, onDefaultAccountChanged: { (_: Core, _: Account?) in
Log.info("\(ContactsManager.TAG) Default account changed, update all contacts' model showTrust value")
//updateContactsModelDependingOnDefaultAccountMode()
}
}, onFriendListRemoved: { (_: Core, friendList: FriendList) in
Log.info("\(ContactsManager.TAG) Friend list \(friendList.displayName) removed")
if self.friendListDelegate != nil {
friendList.removeDelegate(delegate: self.friendListDelegate!)
}
}, onDefaultAccountChanged: { (_: Core, _: Account?) in
Log.info("\(ContactsManager.TAG) Default account changed, update all contacts' model showTrust value")
//updateContactsModelDependingOnDefaultAccountMode()
)
if self.coreDelegate != nil {
core.addDelegate(delegate: self.coreDelegate!)
}
)
if self.coreDelegate != nil {
core.addDelegate(delegate: self.coreDelegate!)
}
}

View file

@ -4,6 +4,16 @@
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>org.linphone.phone</string>
<key>CFBundleURLSchemes</key>
<array>
<string>linphone-message</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>

View file

@ -190,19 +190,34 @@ struct RootView: View {
ToastView().zIndex(3)
}
} else {
MainViewSwitcher(
coreContext: coreContext,
navigationManager: navigationManager,
sharedMainViewModel: sharedMainViewModel,
pendingURL: $pendingURL,
appDelegate: appDelegate
)
ZStack {
MainViewSwitcher(
coreContext: coreContext,
navigationManager: navigationManager,
sharedMainViewModel: sharedMainViewModel,
pendingURL: $pendingURL,
appDelegate: appDelegate
)
if coreContext.coreIsStarted {
VStack {} // Force trigger .onAppear
.onAppear {
if let url = pendingURL {
URIHandler.handleURL(url: url)
pendingURL = nil
}
}
}
}
}
} else {
SplashScreen()
}
}
.onOpenURL { url in
if SharedMainViewModel.shared.displayedConversation != nil && url.absoluteString.contains("linphone-message://") {
SharedMainViewModel.shared.displayedConversation = nil
}
if coreContext.coreIsStarted {
URIHandler.handleURL(url: url)
} else {
@ -230,19 +245,7 @@ struct MainViewSwitcher: View {
let appDelegate: AppDelegate
var body: some View {
ZStack {
if coreContext.coreIsStarted {
selectedMainView()
VStack {} // Force trigger .onAppear
.onAppear {
if let url = pendingURL {
URIHandler.handleURL(url: url)
pendingURL = nil
}
}
}
}
selectedMainView()
}
@ViewBuilder

View file

@ -218,6 +218,8 @@
"conversation_event_participant_removed" = "%@ has left";
"conversation_event_subject_changed" = "New subject: %@";
"conversation_failed_to_create_toast" = "Failed to create conversation!";
"conversations_files_waiting_to_be_shared_single" = "1 file waiting to be shared";
"conversations_files_waiting_to_be_shared_multiple" = "%@ files waiting to be shared";
"conversation_forward_message_title" = "Forward message to…";
"conversation_info_add_participants_label" = "Add participants";
"conversation_info_admin_menu_remove_participant" = "Remove from the group";

View file

@ -218,6 +218,8 @@
"conversation_event_participant_removed" = "%@ a quitté la conversation";
"conversation_event_subject_changed" = "La conversation a été renommée : %@";
"conversation_failed_to_create_toast" = "Échec de création de la conversation !";
"conversations_files_waiting_to_be_shared_single" = "1 fichier en attente de partage";
"conversations_files_waiting_to_be_shared_multiple" = "%@ fichiers en attente de partage";
"conversation_forward_message_title" = "Transférer à…";
"conversation_info_add_participants_label" = "Ajouter des participants";
"conversation_info_admin_menu_remove_participant" = "Retirer participant";

View file

@ -85,6 +85,47 @@ struct ContentView: View {
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
if !sharedMainViewModel.fileUrlsToShare.isEmpty && !telecomManager.callInProgress || (telecomManager.callInProgress && !telecomManager.callDisplayed) {
HStack {
Image("share-network")
.renderingMode(.template)
.resizable()
.foregroundStyle(.white)
.frame(width: 26, height: 26)
.padding(.leading, 10)
if sharedMainViewModel.fileUrlsToShare.count > 1 {
Text(String(format: String(localized: "conversations_files_waiting_to_be_shared_multiple"), sharedMainViewModel.fileUrlsToShare.count.description))
.default_text_style_white(styleSize: 16)
} else {
Text(String(localized: "conversations_files_waiting_to_be_shared_single"))
.default_text_style_white(styleSize: 16)
}
Spacer()
Button(
action: {
withAnimation {
sharedMainViewModel.fileUrlsToShare = []
}
}, label: {
Image("x")
.renderingMode(.template)
.resizable()
.foregroundStyle(.white)
.frame(width: 26, height: 26)
.padding(.trailing, 10)
}
)
}
.frame(maxWidth: .infinity)
.frame(height: 40)
.padding(.horizontal, 10)
.background(Color.gray)
}
if (telecomManager.callInProgress && !fullscreenVideo && ((!telecomManager.callDisplayed && callViewModel.callsCounter == 1) || callViewModel.callsCounter > 1)) || isShowConversationFragment {
HStack {
Image("phone")
@ -111,7 +152,8 @@ struct ContentView: View {
}
}
.frame(maxWidth: .infinity)
.frame(height: 30)
.frame(height: 40)
.padding(.horizontal, 10)
.background(Color.greenSuccess500)
.onTapGesture {
withAnimation {

View file

@ -199,11 +199,30 @@ struct ConversationFragment: View {
}
}
.navigationViewStyle(.stack)
.onAppear {
if let conv = SharedMainViewModel.shared.displayedConversation {
cachedConversation = conv
}
}
.onAppear {
if let conv = SharedMainViewModel.shared.displayedConversation {
cachedConversation = conv
}
if !SharedMainViewModel.shared.fileUrlsToShare.isEmpty {
var urlList: [URL] = []
SharedMainViewModel.shared.fileUrlsToShare.forEach { urlFile in
urlList.append(URL(fileURLWithPath: urlFile))
}
FilePicker.convertToAttachmentArray(fromResults: urlList) { mediasOrNil, errorOrNil in
if let error = errorOrNil {
print(error)
}
if let medias = mediasOrNil {
conversationViewModel.mediasToSend.append(contentsOf: medias)
}
}
SharedMainViewModel.shared.fileUrlsToShare.removeAll()
}
}
}
// swiftlint:disable cyclomatic_complexity

View file

@ -39,6 +39,8 @@ class SharedMainViewModel: ObservableObject {
@Published var dialPlansLabelList: [String] = []
@Published var dialPlansShortLabelList: [String] = []
@Published var fileUrlsToShare: [String] = []
@Published var operationInProgress = false
let welcomeViewKey = "welcome_view"

View file

@ -27,6 +27,7 @@ class URIHandler {
private static let callSchemes = ["sip", "sip-linphone", "linphone-sip", "tel", "callto"]
private static let secureCallSchemes = ["sips", "sips-linphone", "linphone-sips"]
private static let configurationSchemes = ["linphone-config"]
private static let sharedExtensionSchemes = ["linphone-message"]
private static var uriHandlerCoreDelegate: CoreDelegateStub?
@ -63,6 +64,8 @@ class URIHandler {
initiateCall(url: url, withScheme: "sip")
} else if configurationSchemes.contains(scheme) {
initiateConfiguration(url: url)
} else if sharedExtensionSchemes.contains(scheme) {
processReceivedFiles(url: url)
} else if scheme == SingleSignOnManager.shared.ssoRedirectUri.scheme {
continueSSO(url: url)
} else {
@ -113,6 +116,21 @@ class URIHandler {
}
}
private static func processReceivedFiles(url: URL) {
Log.info("[URIHandler] processing received files from URL: \(url.path)")
var urlString = url.path
if urlString.starts(with: "//") {
urlString = String(urlString.dropFirst(2))
}
for urlFile in urlString.components(separatedBy: ",") {
SharedMainViewModel.shared.fileUrlsToShare.append(urlFile)
}
SharedMainViewModel.shared.changeIndexView(indexViewInt: 2)
}
private static func continueSSO(url: URL) {
if let authorizationFlow = SingleSignOnManager.shared.currentAuthorizationFlow,
authorizationFlow.resumeExternalUserAgentFlow(with: url) {

View file

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objectVersion = 70;
objects = {
/* Begin PBXBuildFile section */
@ -108,6 +108,7 @@
D73449992BC6932A00778C56 /* MeetingWaitingRoomFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73449982BC6932A00778C56 /* MeetingWaitingRoomFragment.swift */; };
D734499B2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D734499A2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift */; };
D737AEEF2DA011F2005C1280 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D737AEED2DA011F2005C1280 /* Localizable.strings */; };
D7458F392E0BDCF4000C957A /* linphoneExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D7458F2F2E0BDCF4000C957A /* linphoneExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
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 */; };
@ -204,6 +205,13 @@
remoteGlobalIDString = 660AAF7A2B839271004C0FA6;
remoteInfo = msgNotificationService;
};
D7458F372E0BDCF4000C957A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D719ABAB2ABC67BF00B41C10 /* Project object */;
proxyType = 1;
remoteGlobalIDString = D7458F2E2E0BDCF4000C957A;
remoteInfo = linphoneExtension;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@ -214,6 +222,7 @@
dstSubfolderSpec = 13;
files = (
660AAF7F2B839272004C0FA6 /* msgNotificationService.appex in Embed Foundation Extensions */,
D7458F392E0BDCF4000C957A /* linphoneExtension.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
@ -321,6 +330,7 @@
D734499A2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingWaitingRoomViewModel.swift; sourceTree = "<group>"; };
D737AEEE2DA011F2005C1280 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Localizable/en.lproj/Localizable.strings; sourceTree = "<group>"; };
D737AEF02DA01203005C1280 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = Localizable/fr.lproj/Localizable.strings; sourceTree = "<group>"; };
D7458F2F2E0BDCF4000C957A /* linphoneExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = linphoneExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
@ -403,6 +413,20 @@
D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterFragment.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
D7458F3C2E0BDCF4000C957A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = D7458F2E2E0BDCF4000C957A /* linphoneExtension */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
D7458F302E0BDCF4000C957A /* linphoneExtension */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (D7458F3C2E0BDCF4000C957A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = linphoneExtension; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
660AAF782B839271004C0FA6 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
@ -425,6 +449,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
D7458F2C2E0BDCF4000C957A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@ -568,6 +599,7 @@
660D8A702B517D260092694D /* GoogleService-Info.plist */,
D719ABB52ABC67BF00B41C10 /* Linphone */,
660AAF7C2B839272004C0FA6 /* msgNotificationService */,
D7458F302E0BDCF4000C957A /* linphoneExtension */,
D719ABB42ABC67BF00B41C10 /* Products */,
);
sourceTree = "<group>";
@ -577,6 +609,7 @@
children = (
D719ABB32ABC67BF00B41C10 /* LinphoneApp.app */,
660AAF7B2B839271004C0FA6 /* msgNotificationService.appex */,
D7458F2F2E0BDCF4000C957A /* linphoneExtension.appex */,
);
name = Products;
sourceTree = "<group>";
@ -1037,12 +1070,35 @@
);
dependencies = (
660AAF7E2B839272004C0FA6 /* PBXTargetDependency */,
D7458F382E0BDCF4000C957A /* PBXTargetDependency */,
);
name = LinphoneApp;
productName = Linphone;
productReference = D719ABB32ABC67BF00B41C10 /* LinphoneApp.app */;
productType = "com.apple.product-type.application";
};
D7458F2E2E0BDCF4000C957A /* linphoneExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = D7458F3D2E0BDCF4000C957A /* Build configuration list for PBXNativeTarget "linphoneExtension" */;
buildPhases = (
D7458F2B2E0BDCF4000C957A /* Sources */,
D7458F2C2E0BDCF4000C957A /* Frameworks */,
D7458F2D2E0BDCF4000C957A /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
D7458F302E0BDCF4000C957A /* linphoneExtension */,
);
name = linphoneExtension;
packageProductDependencies = (
);
productName = linphoneExtension;
productReference = D7458F2F2E0BDCF4000C957A /* linphoneExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@ -1050,7 +1106,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1430;
LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1430;
TargetAttributes = {
660AAF7A2B839271004C0FA6 = {
@ -1060,6 +1116,9 @@
D719ABB22ABC67BF00B41C10 = {
CreatedOnToolsVersion = 14.3.1;
};
D7458F2E2E0BDCF4000C957A = {
CreatedOnToolsVersion = 16.4;
};
};
};
buildConfigurationList = D719ABAE2ABC67BF00B41C10 /* Build configuration list for PBXProject "LinphoneApp" */;
@ -1085,6 +1144,7 @@
targets = (
D719ABB22ABC67BF00B41C10 /* LinphoneApp */,
660AAF7A2B839271004C0FA6 /* msgNotificationService */,
D7458F2E2E0BDCF4000C957A /* linphoneExtension */,
);
};
/* End PBXProject section */
@ -1121,6 +1181,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
D7458F2D2E0BDCF4000C957A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@ -1320,6 +1387,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
D7458F2B2E0BDCF4000C957A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@ -1328,6 +1402,11 @@
target = 660AAF7A2B839271004C0FA6 /* msgNotificationService */;
targetProxy = 660AAF7D2B839272004C0FA6 /* PBXContainerItemProxy */;
};
D7458F382E0BDCF4000C957A /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D7458F2E2E0BDCF4000C957A /* linphoneExtension */;
targetProxy = D7458F372E0BDCF4000C957A /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@ -1664,6 +1743,74 @@
};
name = Release;
};
D7458F3A2E0BDCF4000C957A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CODE_SIGN_ENTITLEMENTS = linphoneExtension/linphoneExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Z2V957B3D6;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = linphoneExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = linphoneExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.linphoneExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
D7458F3B2E0BDCF4000C957A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CODE_SIGN_ENTITLEMENTS = linphoneExtension/linphoneExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Z2V957B3D6;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = linphoneExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = linphoneExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.linphoneExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@ -1694,6 +1841,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D7458F3D2E0BDCF4000C957A /* Build configuration list for PBXNativeTarget "linphoneExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D7458F3A2E0BDCF4000C957A /* Debug */,
D7458F3B2E0BDCF4000C957A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */

View file

@ -115,7 +115,7 @@
"location" : "https://gitlab.linphone.org/BC/public/linphone-sdk-swift-ios.git",
"state" : {
"branch" : "stable",
"revision" : "ffedf77da711ea0f90ddd99a1785a84268413122"
"revision" : "2d65f089792cb91c50f51dc26417c7d4c0ebb988"
}
},
{

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Share View Controller-->
<scene sceneID="ceB-am-kn3">
<objects>
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<string>TRUEPREDICATE</string>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,113 @@
/*
* Copyright (c) 2010-2024 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 UIKit
import Social
class ShareViewController: SLComposeServiceViewController {
var remainingSlots = 12
override func isContentValid() -> Bool {
return true
}
override func viewDidAppear(_ animated: Bool) {
handleSharedFiles()
}
override func configurationItems() -> [Any]! {
return []
}
private func handleSharedFiles() {
guard let extensionItems = self.extensionContext?.inputItems as? [NSExtensionItem] else { return }
var fileURLs: [URL] = []
let dispatchGroup = DispatchGroup()
for item in extensionItems {
if let attachments = item.attachments {
for provider in attachments {
guard remainingSlots > 0 else { break }
if provider.hasItemConformingToTypeIdentifier("public.item") {
remainingSlots -= 1
dispatchGroup.enter()
provider.loadFileRepresentation(forTypeIdentifier: "public.item") { urlFile, error in
if let url = urlFile {
if let urlSaved = self.copyFileToSharedContainer(from: url) {
fileURLs.append(urlSaved)
}
}
dispatchGroup.leave()
}
}
}
}
}
dispatchGroup.notify(queue: .main) {
if !fileURLs.isEmpty {
self.openParentApp(with: fileURLs)
} else {
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
}
}
func openParentApp(with fileURLs: [URL]) {
let urlStrings = fileURLs.map { $0.path }
let joinedURLs = urlStrings.joined(separator: ",")
let urlScheme = "linphone-message://\(joinedURLs)"
if let url = URL(string: urlScheme) {
var responder: UIResponder? = self
while responder != nil {
if let application = responder as? UIApplication {
application.open(url)
break
}
responder = responder?.next
}
}
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
func copyFileToSharedContainer(from url: URL) -> URL? {
let fileManager = FileManager.default
guard let sharedContainerURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.org.linphone.phone.linphoneExtension") else {
return nil
}
let destinationURL = sharedContainerURL.appendingPathComponent(url.lastPathComponent)
do {
if fileManager.fileExists(atPath: destinationURL.path) {
try fileManager.removeItem(at: destinationURL)
}
try fileManager.copyItem(at: url, to: destinationURL)
return destinationURL
} catch {
return nil
}
}
}

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.linphone.phone.linphoneExtension</string>
</array>
</dict>
</plist>