Merge pull request #6614 from nextcloud/feature/file-provider-sharing

File sharing for macOS VFS (File Provider Module)
This commit is contained in:
Claudio Cambra 2024-04-17 16:12:56 +08:00 committed by GitHub
commit 3afa861f91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 2392 additions and 5 deletions

View file

@ -35,7 +35,20 @@ if(APPLE)
COMMENT building macOS File Provider extension
VERBATIM)
add_dependencies(mac_overlayplugin mac_fileproviderplugin nextcloud) # for the ownCloud.icns to be generated
add_custom_target( mac_fileprovideruiplugin ALL
xcodebuild ARCHS=${CMAKE_OSX_ARCHITECTURES} ONLY_ACTIVE_ARCH=NO
-project ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj
-target FileProviderUIExt -configuration ${XCODE_TARGET_CONFIGURATION} "SYMROOT=${CMAKE_CURRENT_BINARY_DIR}"
"OC_APPLICATION_EXECUTABLE_NAME=${APPLICATION_EXECUTABLE}"
"OC_APPLICATION_VENDOR=${APPLICATION_VENDOR}"
"OC_APPLICATION_NAME=${APPLICATION_NAME}"
"OC_APPLICATION_REV_DOMAIN=${APPLICATION_REV_DOMAIN}"
"OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX=${SOCKETAPI_TEAM_IDENTIFIER_PREFIX}"
DEPENDS mac_fileproviderplugin
COMMENT building macOS File Provider UI extension
VERBATIM)
add_dependencies(mac_overlayplugin mac_fileproviderplugin mac_fileprovideruiplugin nextcloud) # for the ownCloud.icns to be generated
else()
add_dependencies(mac_overlayplugin nextcloud) # for the ownCloud.icns to be generated
endif()
@ -55,6 +68,10 @@ if(APPLE)
install(DIRECTORY ${OSX_PLUGINS_BINARY_DIR}/FileProviderExt.appex
DESTINATION ${OSX_PLUGINS_INSTALL_DIR}
USE_SOURCE_PERMISSIONS)
install(DIRECTORY ${OSX_PLUGINS_BINARY_DIR}/FileProviderUIExt.appex
DESTINATION ${OSX_PLUGINS_INSTALL_DIR}
USE_SOURCE_PERMISSIONS)
endif()
endif()
endif()

View file

@ -19,12 +19,15 @@ extension Logger {
static let desktopClientConnection = Logger(
subsystem: subsystem, category: "desktopclientconnection")
static let fpUiExtensionService = Logger(subsystem: subsystem, category: "fpUiExtensionService")
static let enumeration = Logger(subsystem: subsystem, category: "enumeration")
static let fileProviderExtension = Logger(
subsystem: subsystem, category: "fileproviderextension")
static let fileTransfer = Logger(subsystem: subsystem, category: "filetransfer")
static let localFileOps = Logger(subsystem: subsystem, category: "localfileoperations")
static let ncFilesDatabase = Logger(subsystem: subsystem, category: "nextcloudfilesdatabase")
static let shares = Logger(subsystem: subsystem, category: "shares")
static let ncAccount = Logger(subsystem: subsystem, category: "ncAccount")
static let materialisedFileHandling = Logger(
subsystem: subsystem, category: "materialisedfilehandling"
)

View file

@ -39,7 +39,8 @@ extension FileProviderExtension: NSFileProviderServicing {
) -> Progress {
Logger.desktopClientConnection.debug("Serving supported service sources")
let clientCommService = ClientCommunicationService(fpExtension: self)
let services = [clientCommService]
let fpuiExtService = FPUIExtensionServiceSource(fpExtension: self)
let services: [NSFileProviderServiceSource] = [clientCommService, fpuiExtService]
completionHandler(services, nil)
let progress = Progress()
progress.cancellationHandler = {

View file

@ -15,6 +15,12 @@
import FileProvider
import Foundation
let ncAccountDictUsernameKey = "usernameKey"
let ncAccountDictPasswordKey = "passwordKey"
let ncAccountDictNcKitAccountKey = "ncKitAccountKey"
let ncAccountDictServerUrlKey = "serverUrlKey"
let ncAccountDictDavFilesUrlKey = "davFilesUrlKey"
struct NextcloudAccount: Equatable {
static let webDavFilesUrlSuffix: String = "/remote.php/dav/files/"
let username, password, ncKitAccount, serverUrl, davFilesUrl: String
@ -26,4 +32,31 @@ struct NextcloudAccount: Equatable {
self.serverUrl = serverUrl
davFilesUrl = serverUrl + NextcloudAccount.webDavFilesUrlSuffix + user
}
init?(dictionary: Dictionary<String, String>) {
guard let username = dictionary[ncAccountDictUsernameKey],
let password = dictionary[ncAccountDictPasswordKey],
let ncKitAccount = dictionary[ncAccountDictNcKitAccountKey],
let serverUrl = dictionary[ncAccountDictServerUrlKey],
let davFilesUrl = dictionary[ncAccountDictDavFilesUrlKey]
else {
return nil
}
self.username = username
self.password = password
self.ncKitAccount = ncKitAccount
self.serverUrl = serverUrl
self.davFilesUrl = davFilesUrl
}
func dictionary() -> Dictionary<String, String> {
return [
ncAccountDictUsernameKey: username,
ncAccountDictPasswordKey: password,
ncAccountDictNcKitAccountKey: ncKitAccount,
ncAccountDictServerUrlKey: serverUrl,
ncAccountDictDavFilesUrlKey: davFilesUrl
]
}
}

View file

@ -0,0 +1,18 @@
//
// FPUIExtensionCommunicationProtocol.swift
// FileProviderExt
//
// Created by Claudio Cambra on 21/2/24.
//
import FileProvider
import NextcloudKit
let fpUiExtensionServiceName = NSFileProviderServiceName(
"com.nextcloud.desktopclient.FPUIExtensionService"
)
@objc protocol FPUIExtensionService {
func credentials() async -> NSDictionary
func itemServerPath(identifier: NSFileProviderItemIdentifier) async -> NSString?
}

View file

@ -0,0 +1,64 @@
//
// FPUIExtensionCommunicationService.swift
// FileProviderExt
//
// Created by Claudio Cambra on 21/2/24.
//
import FileProvider
import Foundation
import NextcloudKit
import OSLog
class FPUIExtensionServiceSource: NSObject, NSFileProviderServiceSource, NSXPCListenerDelegate, FPUIExtensionService {
let listener = NSXPCListener.anonymous()
let serviceName = fpUiExtensionServiceName
let fpExtension: FileProviderExtension
init(fpExtension: FileProviderExtension) {
Logger.fpUiExtensionService.debug("Instantiating FPUIExtensionService service")
self.fpExtension = fpExtension
super.init()
}
func makeListenerEndpoint() throws -> NSXPCListenerEndpoint {
listener.delegate = self
listener.resume()
return listener.endpoint
}
func listener(
_ listener: NSXPCListener,
shouldAcceptNewConnection newConnection: NSXPCConnection
) -> Bool {
newConnection.exportedInterface = NSXPCInterface(with: FPUIExtensionService.self)
newConnection.exportedObject = self
newConnection.resume()
return true
}
//MARK: - FPUIExtensionService protocol methods
func credentials() async -> NSDictionary {
return (fpExtension.ncAccount?.dictionary() ?? [:]) as NSDictionary
}
func itemServerPath(identifier: NSFileProviderItemIdentifier) async -> NSString? {
let rawIdentifier = identifier.rawValue
Logger.shares.info("Fetching shares for item \(rawIdentifier, privacy: .public)")
guard let baseUrl = fpExtension.ncAccount?.davFilesUrl else {
Logger.shares.error("Could not fetch shares as ncAccount on parent extension is nil")
return nil
}
let dbManager = NextcloudFilesDatabaseManager.shared
guard let item = dbManager.itemMetadataFromFileProviderItemIdentifier(identifier) else {
Logger.shares.error("No item \(rawIdentifier, privacy: .public) in db, no shares.")
return nil
}
let completePath = item.serverUrl + "/" + item.fileName
return completePath.replacingOccurrences(of: baseUrl, with: "") as NSString
}
}

View file

@ -0,0 +1,53 @@
//
// DocumentActionViewController.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 20/2/24.
//
import FileProviderUI
import OSLog
class DocumentActionViewController: FPUIActionExtensionViewController {
var domain: NSFileProviderDomain {
guard let identifier = extensionContext.domainIdentifier else {
fatalError("not expected to be called with default domain")
}
return NSFileProviderDomain(
identifier: NSFileProviderDomainIdentifier(rawValue: identifier.rawValue),
displayName: ""
)
}
func prepare(childViewController: NSViewController) {
addChild(childViewController)
view.addSubview(childViewController.view)
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: childViewController.view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: childViewController.view.trailingAnchor),
view.topAnchor.constraint(equalTo: childViewController.view.topAnchor),
view.bottomAnchor.constraint(equalTo: childViewController.view.bottomAnchor)
])
}
override func prepare(
forAction actionIdentifier: String, itemIdentifiers: [NSFileProviderItemIdentifier]
) {
Logger.actionViewController.info("Preparing for action: \(actionIdentifier)")
if actionIdentifier == "com.nextcloud.desktopclient.FileProviderUIExt.ShareAction" {
prepare(childViewController: ShareViewController(itemIdentifiers))
}
}
override func prepare(forError error: Error) {
Logger.actionViewController.info("Preparing for error: \(error.localizedDescription)")
}
override public func loadView() {
self.view = NSView()
}
}

View file

@ -0,0 +1,21 @@
//
// Logger+Extensions.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 21/2/24.
//
import OSLog
extension Logger {
private static var subsystem = Bundle.main.bundleIdentifier!
static let actionViewController = Logger(subsystem: subsystem, category: "actionViewController")
static let shareCapabilities = Logger(subsystem: subsystem, category: "shareCapabilities")
static let shareController = Logger(subsystem: subsystem, category: "shareController")
static let shareeDataSource = Logger(subsystem: subsystem, category: "shareeDataSource")
static let sharesDataSource = Logger(subsystem: subsystem, category: "sharesDataSource")
static let shareOptionsView = Logger(subsystem: subsystem, category: "shareOptionsView")
static let shareViewController = Logger(subsystem: subsystem, category: "shareViewController")
}

View file

@ -0,0 +1,130 @@
//
// NKShare+Extensions.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 28/2/24.
//
import AppKit
import NextcloudKit
extension NKShare {
enum ShareType: Int {
case user = 0
case group = 1
case publicLink = 3
case email = 4
case federatedCloud = 6
case circle = 7
case talkConversation = 10
}
enum PermissionValues: Int {
case readShare = 1
case updateShare = 2
case createShare = 4
case deleteShare = 8
case shareShare = 16
case all = 31
}
var typeImage: NSImage? {
var image: NSImage?
switch shareType {
case ShareType.user.rawValue:
image = NSImage(
systemSymbolName: "person.circle.fill",
accessibilityDescription: "User share icon"
)
case ShareType.group.rawValue:
image = NSImage(
systemSymbolName: "person.2.circle.fill",
accessibilityDescription: "Group share icon"
)
case ShareType.publicLink.rawValue:
image = NSImage(
systemSymbolName: "link.circle.fill",
accessibilityDescription: "Public link share icon"
)
case ShareType.email.rawValue:
image = NSImage(
systemSymbolName: "envelope.circle.fill",
accessibilityDescription: "Email share icon"
)
case ShareType.federatedCloud.rawValue:
image = NSImage(
systemSymbolName: "cloud.circle.fill",
accessibilityDescription: "Federated cloud share icon"
)
case ShareType.circle.rawValue:
image = NSImage(
systemSymbolName: "circle.circle.fill",
accessibilityDescription: "Circle share icon"
)
case ShareType.talkConversation.rawValue:
image = NSImage(
systemSymbolName: "message.circle.fill",
accessibilityDescription: "Talk conversation share icon"
)
default:
return nil
}
var config = NSImage.SymbolConfiguration(textStyle: .body, scale: .large)
if #available(macOS 12.0, *) {
config = config.applying(
.init(paletteColors: [.controlBackgroundColor, .controlAccentColor])
)
}
return image?.withSymbolConfiguration(config)
}
var displayString: String {
if label != "" {
return label
}
switch shareType {
case ShareType.user.rawValue:
return "User share (\(shareWith))"
case ShareType.group.rawValue:
return "Group share (\(shareWith))"
case ShareType.publicLink.rawValue:
return "Public link share"
case ShareType.email.rawValue:
return "Email share (\(shareWith))"
case ShareType.federatedCloud.rawValue:
return "Federated cloud share (\(shareWith))"
case ShareType.circle.rawValue:
return "Circle share (\(shareWith))"
case ShareType.talkConversation.rawValue:
return "Talk conversation share (\(shareWith))"
default:
return "Unknown share"
}
}
var expirationDateString: String? {
guard let date = expirationDate else { return nil }
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
return dateFormatter.string(from: date as Date)
}
var shareesCanEdit: Bool {
get { (permissions & PermissionValues.updateShare.rawValue) != 0 }
set {
if newValue {
permissions |= NKShare.PermissionValues.updateShare.rawValue
} else {
permissions &= ~NKShare.PermissionValues.updateShare.rawValue
}
}
}
static func formattedDateString(date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
return dateFormatter.string(from: date)
}
}

View file

@ -0,0 +1,12 @@
<?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.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX)$(OC_APPLICATION_REV_DOMAIN)</string>
</array>
</dict>
</plist>

View file

@ -0,0 +1,16 @@
<?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.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX)$(OC_APPLICATION_REV_DOMAIN)</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?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>NSExtensionFileProviderActions</key>
<array>
<dict>
<key>NSExtensionFileProviderActionActivationRule</key>
<string>TRUEPREDICATE</string>
<key>NSExtensionFileProviderActionIdentifier</key>
<string>com.nextcloud.desktopclient.FileProviderUIExt.ShareAction</string>
<key>NSExtensionFileProviderActionName</key>
<string>Share options</string>
</dict>
</array>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).DocumentActionViewController</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.fileprovider-actionsui</string>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,131 @@
//
// ShareController.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 4/3/24.
//
import Combine
import Foundation
import NextcloudKit
import OSLog
class ShareController: ObservableObject {
@Published private(set) var share: NKShare
private let kit: NextcloudKit
static func create(
kit: NextcloudKit,
shareType: NKShare.ShareType,
itemServerRelativePath: String,
shareWith: String?,
password: String? = nil,
expireDate: String? = nil,
permissions: Int = 1,
publicUpload: Bool = false,
note: String? = nil,
label: String? = nil,
hideDownload: Bool,
attributes: String? = nil,
options: NKRequestOptions = NKRequestOptions()
) async -> NKError? {
Logger.shareController.info("Creating share: \(itemServerRelativePath)")
return await withCheckedContinuation { continuation in
if shareType == .publicLink {
kit.createShareLink(
path: itemServerRelativePath,
hideDownload: hideDownload,
publicUpload: publicUpload,
password: password,
permissions: permissions,
options: options
) { account, share, data, error in
defer { continuation.resume(returning: error) }
guard error == .success else {
Logger.shareController.error("Error creating link share: \(error)")
return
}
}
} else {
guard let shareWith = shareWith else {
let errorString = "No recipient for share!"
Logger.shareController.error("\(errorString)")
let error = NKError(statusCode: 0, fallbackDescription: errorString)
continuation.resume(returning: error)
return
}
kit.createShare(
path: itemServerRelativePath,
shareType: shareType.rawValue,
shareWith: shareWith,
password: password,
permissions: permissions,
options: options,
attributes: attributes
) { account, share, data, error in
defer { continuation.resume(returning: error) }
guard error == .success else {
Logger.shareController.error("Error creating share: \(error)")
return
}
}
}
}
}
init(share: NKShare, kit: NextcloudKit) {
self.share = share
self.kit = kit
}
func save(
password: String? = nil,
expireDate: String? = nil,
permissions: Int = 1,
publicUpload: Bool = false,
note: String? = nil,
label: String? = nil,
hideDownload: Bool,
attributes: String? = nil,
options: NKRequestOptions = NKRequestOptions()
) async -> NKError? {
Logger.shareController.info("Saving share: \(self.share.url)")
return await withCheckedContinuation { continuation in
kit.updateShare(
idShare: share.idShare,
password: password,
expireDate: expireDate,
permissions: permissions,
publicUpload: publicUpload,
note: note,
label: label,
hideDownload: hideDownload,
attributes: attributes,
options: options
) { account, share, data, error in
Logger.shareController.info("Received update response: \(share?.url ?? "")")
defer { continuation.resume(returning: error) }
guard error == .success, let share = share else {
Logger.shareController.error("Error updating save: \(error.errorDescription)")
return
}
self.share = share
}
}
}
func delete() async -> NKError? {
Logger.shareController.info("Deleting share: \(self.share.url)")
return await withCheckedContinuation { continuation in
kit.deleteShare(idShare: share.idShare) { account, error in
Logger.shareController.info("Received delete response: \(self.share.url)")
defer { continuation.resume(returning: error) }
guard error == .success else {
Logger.shareController.error("Error deleting save: \(error.errorDescription)")
return
}
}
}
}
}

View file

@ -0,0 +1,353 @@
//
// ShareOptionsView.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 28/2/24.
//
import AppKit
import Combine
import NextcloudKit
import OSLog
import SuggestionsTextFieldKit
class ShareOptionsView: NSView {
@IBOutlet private weak var optionsTitleTextField: NSTextField!
@IBOutlet private weak var shareRecipientTextField: NSTextField! // Hide if public link share
@IBOutlet private weak var labelTextField: NSTextField!
@IBOutlet private weak var uploadEditPermissionCheckbox: NSButton!
@IBOutlet private weak var hideDownloadCheckbox: NSButton!
@IBOutlet private weak var passwordProtectCheckbox: NSButton!
@IBOutlet private weak var passwordSecureField: NSSecureTextField!
@IBOutlet private weak var expirationDateCheckbox: NSButton!
@IBOutlet private weak var expirationDatePicker: NSDatePicker!
@IBOutlet private weak var noteForRecipientCheckbox: NSButton!
@IBOutlet private weak var noteTextField: NSTextField!
@IBOutlet private weak var saveButton: NSButton!
@IBOutlet private weak var deleteButton: NSButton!
@IBOutlet private weak var shareTypePicker: NSPopUpButton!
@IBOutlet private weak var publicLinkShareMenuItem: NSMenuItem!
@IBOutlet private weak var userShareMenuItem: NSMenuItem!
@IBOutlet private weak var groupShareMenuItem: NSMenuItem!
@IBOutlet private weak var emailShareMenuItem: NSMenuItem!
@IBOutlet private weak var federatedCloudShareMenuItem: NSMenuItem!
@IBOutlet private weak var circleShare: NSMenuItem!
@IBOutlet private weak var talkConversationShare: NSMenuItem!
var kit: NextcloudKit? {
didSet {
Logger.shareOptionsView.info("Setting up the kit.")
guard let kit = kit else {
Logger.shareOptionsView.error("Could not configure suggestions data source.")
return
}
suggestionsTextFieldDelegate.suggestionsDataSource = ShareeSuggestionsDataSource(
kit: kit
)
suggestionsTextFieldDelegate.confirmationHandler = { suggestion in
guard let sharee = suggestion?.data as? NKSharee else { return }
self.shareRecipientTextField.stringValue = sharee.shareWith
Logger.shareOptionsView.debug("Chose sharee \(sharee.shareWith, privacy: .public)")
}
suggestionsTextFieldDelegate.targetTextField = shareRecipientTextField
}
}
var dataSource: ShareTableViewDataSource?
var controller: ShareController? {
didSet {
guard controller != nil else { return }
optionsTitleTextField.stringValue = "Share options"
deleteButton.title = "Delete"
deleteButton.image = NSImage(
systemSymbolName: "trash", accessibilityDescription: "Delete trash icon"
)
deleteButton.bezelColor = NSColor.systemRed
cancellable?.cancel()
createMode = false
update()
cancellable = controller.publisher.sink { _ in self.update() }
}
}
var createMode = false {
didSet {
Logger.shareOptionsView.info("Create mode set: \(self.createMode)")
shareTypePicker.isHidden = !createMode
shareRecipientTextField.isHidden = !createMode
labelTextField.isHidden = createMode // Cannot set label on create API call
guard createMode else { return }
optionsTitleTextField.stringValue = "Create new share"
deleteButton.title = "Cancel"
deleteButton.image = NSImage(
systemSymbolName: "xmark.bin", accessibilityDescription: "Cancel create icon"
)
deleteButton.bezelColor = NSColor.controlColor
cancellable?.cancel()
cancellable = nil
controller = nil
reset()
setupCreateForm()
}
}
private var cancellable: AnyCancellable?
private var suggestionsWindowController = SuggestionsWindowController()
private var suggestionsTextFieldDelegate = SuggestionsTextFieldDelegate()
private func update() {
guard let share = controller?.share else {
reset()
setAllFields(enabled: false)
saveButton.isEnabled = false
deleteButton.isEnabled = false
return
}
deleteButton.isEnabled = share.canDelete
saveButton.isEnabled = share.canEdit
if share.canEdit {
setAllFields(enabled: true)
labelTextField.stringValue = share.label
uploadEditPermissionCheckbox.state = share.shareesCanEdit ? .on : .off
hideDownloadCheckbox.state = share.hideDownload ? .on : .off
passwordProtectCheckbox.state = share.password.isEmpty ? .off : .on
passwordSecureField.isHidden = passwordProtectCheckbox.state == .off
expirationDateCheckbox.state = share.expirationDate == nil ? .off : .on
expirationDatePicker.isHidden = expirationDateCheckbox.state == .off
noteForRecipientCheckbox.state = share.note.isEmpty ? .off : .on
noteTextField.isHidden = noteForRecipientCheckbox.state == .off
} else {
setAllFields(enabled: false)
reset()
}
}
private func reset() {
shareRecipientTextField.stringValue = ""
labelTextField.stringValue = ""
uploadEditPermissionCheckbox.state = .off
hideDownloadCheckbox.state = .off
passwordProtectCheckbox.state = .off
passwordSecureField.isHidden = true
passwordSecureField.stringValue = ""
expirationDateCheckbox.state = .off
expirationDatePicker.isHidden = true
expirationDatePicker.dateValue = NSDate.now
expirationDatePicker.minDate = NSDate.now
expirationDatePicker.maxDate = nil
noteForRecipientCheckbox.state = .off
noteTextField.isHidden = true
noteTextField.stringValue = ""
}
private func setupCreateForm() {
guard createMode else { return }
setAllFields(enabled: true)
let type = pickedShareType()
shareRecipientTextField.isHidden = type == .publicLink
if let caps = dataSource?.capabilities?.filesSharing {
uploadEditPermissionCheckbox.state =
caps.defaultPermissions & NKShare.PermissionValues.updateShare.rawValue != 0
? .on : .off
switch type {
case .publicLink:
passwordProtectCheckbox.isHidden = false
passwordProtectCheckbox.state = caps.publicLink?.passwordEnforced == true ? .on : .off
passwordProtectCheckbox.isEnabled = caps.publicLink?.passwordEnforced == false
expirationDateCheckbox.state = caps.publicLink?.expireDateEnforced == true ? .on : .off
expirationDateCheckbox.isEnabled = caps.publicLink?.expireDateEnforced == false
expirationDatePicker.dateValue = Date(
timeIntervalSinceNow:
TimeInterval((caps.publicLink?.expireDateDays ?? 1) * 24 * 60 * 60)
)
if caps.publicLink?.expireDateEnforced == true {
expirationDatePicker.maxDate = expirationDatePicker.dateValue
}
case .email:
passwordProtectCheckbox.isHidden = caps.email?.passwordEnabled == false
passwordProtectCheckbox.state = caps.email?.passwordEnforced == true ? .on : .off
default:
passwordProtectCheckbox.isHidden = true
passwordProtectCheckbox.state = .off
break
}
}
passwordSecureField.isHidden = passwordProtectCheckbox.state == .off
expirationDatePicker.isHidden = expirationDateCheckbox.state == .off
}
private func setAllFields(enabled: Bool) {
shareTypePicker.isEnabled = enabled
shareRecipientTextField.isEnabled = enabled
labelTextField.isEnabled = enabled
uploadEditPermissionCheckbox.isEnabled = enabled
hideDownloadCheckbox.isEnabled = enabled
passwordProtectCheckbox.isEnabled = enabled
passwordSecureField.isEnabled = enabled
expirationDateCheckbox.isEnabled = enabled
expirationDatePicker.isEnabled = enabled
noteForRecipientCheckbox.isEnabled = enabled
noteTextField.isEnabled = enabled
saveButton.isEnabled = enabled
deleteButton.isEnabled = enabled
}
private func pickedShareType() -> NKShare.ShareType {
let selectedShareTypeItem = shareTypePicker.selectedItem
var selectedShareType = NKShare.ShareType.publicLink
if selectedShareTypeItem == publicLinkShareMenuItem {
selectedShareType = .publicLink
} else if selectedShareTypeItem == userShareMenuItem {
selectedShareType = .user
} else if selectedShareTypeItem == groupShareMenuItem {
selectedShareType = .group
} else if selectedShareTypeItem == emailShareMenuItem {
selectedShareType = .email
} else if selectedShareTypeItem == federatedCloudShareMenuItem {
selectedShareType = .federatedCloud
} else if selectedShareTypeItem == circleShare {
selectedShareType = .circle
} else if selectedShareTypeItem == talkConversationShare {
selectedShareType = .talkConversation
}
return selectedShareType
}
@IBAction func shareTypePickerAction(_ sender: Any) {
if createMode {
setupCreateForm()
}
}
@IBAction func passwordCheckboxAction(_ sender: Any) {
passwordSecureField.isHidden = passwordProtectCheckbox.state == .off
}
@IBAction func expirationDateCheckboxAction(_ sender: Any) {
expirationDatePicker.isHidden = expirationDateCheckbox.state == .off
}
@IBAction func noteForRecipientCheckboxAction(_ sender: Any) {
noteTextField.isHidden = noteForRecipientCheckbox.state == .off
}
@IBAction func save(_ sender: Any) {
Task { @MainActor in
let password = passwordProtectCheckbox.state == .on
? passwordSecureField.stringValue
: ""
let expireDate = expirationDateCheckbox.state == .on
? NKShare.formattedDateString(date: expirationDatePicker.dateValue)
: ""
let note = noteForRecipientCheckbox.state == .on
? noteTextField.stringValue
: ""
let label = labelTextField.stringValue
let hideDownload = hideDownloadCheckbox.state == .on
let uploadAndEdit = uploadEditPermissionCheckbox.state == .on
guard !createMode else {
Logger.shareOptionsView.info("Creating new share!")
guard let dataSource = dataSource,
let kit = kit,
let itemServerRelativePath = dataSource.itemServerRelativePath
else {
Logger.shareOptionsView.error("Cannot create new share due to missing data.")
Logger.shareOptionsView.error("dataSource: \(self.dataSource)")
Logger.shareOptionsView.error("kit: \(self.kit)")
Logger.shareOptionsView.error(
"path: \(self.dataSource?.itemServerRelativePath ?? "")"
)
return
}
let selectedShareType = pickedShareType()
let shareWith = shareRecipientTextField.stringValue
var permissions = NKShare.PermissionValues.all.rawValue
permissions = uploadAndEdit
? permissions | NKShare.PermissionValues.updateShare.rawValue
: permissions & ~NKShare.PermissionValues.updateShare.rawValue
setAllFields(enabled: false)
deleteButton.isEnabled = false
saveButton.isEnabled = false
let error = await ShareController.create(
kit: kit,
shareType: selectedShareType,
itemServerRelativePath: itemServerRelativePath,
shareWith: shareWith,
password: password,
expireDate: expireDate,
permissions: permissions,
note: note,
label: label,
hideDownload: hideDownload
)
if let error = error, error != .success {
dataSource.uiDelegate?.showError("Error creating: \(error.errorDescription)")
setAllFields(enabled: true)
} else {
dataSource.uiDelegate?.hideOptions(self)
await dataSource.reload()
}
return
}
Logger.shareOptionsView.info("Editing existing share!")
guard let controller = controller else {
Logger.shareOptionsView.error("No valid share controller, cannot edit share.")
return
}
let share = controller.share
let permissions = uploadAndEdit
? share.permissions | NKShare.PermissionValues.updateShare.rawValue
: share.permissions & ~NKShare.PermissionValues.updateShare.rawValue
setAllFields(enabled: false)
deleteButton.isEnabled = false
saveButton.isEnabled = false
let error = await controller.save(
password: password,
expireDate: expireDate,
permissions: permissions,
note: note,
label: label,
hideDownload: hideDownload
)
if let error = error, error != .success {
dataSource?.uiDelegate?.showError("Error updating share: \(error.errorDescription)")
setAllFields(enabled: true)
} else {
dataSource?.uiDelegate?.hideOptions(self)
await dataSource?.reload()
}
}
}
@IBAction func delete(_ sender: Any) {
Task { @MainActor in
guard !createMode else {
dataSource?.uiDelegate?.hideOptions(self)
reset()
return
}
setAllFields(enabled: false)
deleteButton.isEnabled = false
saveButton.isEnabled = false
let error = await controller?.delete()
if let error = error, error != .success {
dataSource?.uiDelegate?.showError("Error deleting share: \(error.errorDescription)")
}
await dataSource?.reload()
}
}
}

View file

@ -0,0 +1,64 @@
//
// ShareTableItemView.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 28/2/24.
//
import AppKit
import NextcloudKit
class ShareTableItemView: NSTableCellView {
@IBOutlet private weak var typeImageView: NSImageView!
@IBOutlet private weak var label: NSTextField!
@IBOutlet private weak var copyLinkButton: NSButton!
private var originalCopyImage: NSImage?
private var copiedButtonImage: NSImage?
private var tempButtonTimer: Timer?
var share: NKShare? {
didSet {
guard let share = share else {
prepareForReuse()
return
}
typeImageView.image = share.typeImage
label.stringValue = share.displayString
copyLinkButton.isHidden = share.shareType != NKShare.ShareType.publicLink.rawValue
}
}
override func prepareForReuse() {
typeImageView.image = nil
label.stringValue = ""
copyLinkButton.isHidden = false
super.prepareForReuse()
}
@IBAction func copyShareLink(sender: Any) {
guard let share = share else { return }
let pasteboard = NSPasteboard.general
pasteboard.declareTypes([.string], owner: nil)
pasteboard.setString(share.url, forType: .string)
guard tempButtonTimer == nil else { return }
originalCopyImage = copyLinkButton.image
copiedButtonImage = NSImage(
systemSymbolName: "checkmark.circle.fill",
accessibilityDescription: "Public link has been copied icon"
)
var config = NSImage.SymbolConfiguration(scale: .medium)
if #available(macOS 12.0, *) {
config = config.applying(.init(hierarchicalColor: .systemGreen))
}
copiedButtonImage = copiedButtonImage?.withSymbolConfiguration(config)
copyLinkButton.image = copiedButtonImage
tempButtonTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { timer in
self.copyLinkButton.image = self.originalCopyImage
self.copiedButtonImage = nil
self.tempButtonTimer?.invalidate()
self.tempButtonTimer = nil
}
}
}

View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
<capability name="Image references" minToolsVersion="12.0"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<tableCellView id="WWf-Il-fKw" customClass="ShareTableItemView" customModule="FileProviderUIExt" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="322" height="42"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wdu-Dj-FUn">
<rect key="frame" x="5" y="5" width="312" height="32"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Ex8-CB-iNc">
<rect key="frame" x="0.0" y="-5" width="32" height="43"/>
<constraints>
<constraint firstAttribute="width" secondItem="Ex8-CB-iNc" secondAttribute="height" multiplier="1:1" id="5ak-dc-HzR"/>
<constraint firstAttribute="height" constant="32" id="Cus-qI-uen"/>
</constraints>
<imageCell key="cell" controlSize="large" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" id="0Xf-Au-QjJ">
<imageReference key="image" image="link.circle.fill" catalog="system" symbolScale="large"/>
</imageCell>
<color key="contentTintColor" name="AccentColor"/>
</imageView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MwZ-er-vB4">
<rect key="frame" x="38" y="8" width="236" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="left" title="Share link" id="Bcz-ws-5yW">
<font key="font" metaFont="systemMedium" size="13"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button horizontalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="y6i-wm-BtQ">
<rect key="frame" x="280" y="0.0" width="32" height="32"/>
<buttonCell key="cell" type="inline" title="Copy share link" bezelStyle="inline" image="doc.on.doc.fill" catalog="system" imagePosition="only" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ram-fe-8Pt">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="smallSystemBold"/>
</buttonCell>
<constraints>
<constraint firstAttribute="width" secondItem="y6i-wm-BtQ" secondAttribute="height" multiplier="1:1" id="BlJ-WU-1y5"/>
</constraints>
<connections>
<action selector="copyShareLinkWithSender:" target="WWf-Il-fKw" id="dgW-8v-wfd"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="Ex8-CB-iNc" firstAttribute="top" secondItem="wdu-Dj-FUn" secondAttribute="top" id="2x8-EU-gHs"/>
<constraint firstItem="y6i-wm-BtQ" firstAttribute="top" secondItem="wdu-Dj-FUn" secondAttribute="top" id="BjV-lg-nyL"/>
<constraint firstAttribute="bottom" secondItem="Ex8-CB-iNc" secondAttribute="bottom" id="dd0-Vh-Pbi"/>
<constraint firstAttribute="bottom" secondItem="y6i-wm-BtQ" secondAttribute="bottom" id="ha0-oR-OxV"/>
<constraint firstItem="MwZ-er-vB4" firstAttribute="centerY" secondItem="Ex8-CB-iNc" secondAttribute="centerY" id="m3A-Tu-qMJ"/>
<constraint firstItem="Ex8-CB-iNc" firstAttribute="leading" secondItem="wdu-Dj-FUn" secondAttribute="leading" id="o1t-TB-uhe"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
</subviews>
<constraints>
<constraint firstItem="wdu-Dj-FUn" firstAttribute="top" secondItem="WWf-Il-fKw" secondAttribute="top" constant="5" id="HIY-8X-dM2"/>
<constraint firstItem="wdu-Dj-FUn" firstAttribute="leading" secondItem="WWf-Il-fKw" secondAttribute="leading" constant="5" id="g7a-hl-t5g"/>
<constraint firstAttribute="bottom" secondItem="wdu-Dj-FUn" secondAttribute="bottom" constant="5" id="gAZ-x8-C4K"/>
<constraint firstAttribute="trailing" secondItem="wdu-Dj-FUn" secondAttribute="trailing" constant="5" id="grc-5X-tMi"/>
</constraints>
<connections>
<outlet property="copyLinkButton" destination="y6i-wm-BtQ" id="8bc-yO-Txo"/>
<outlet property="label" destination="MwZ-er-vB4" id="Gba-jf-C8H"/>
<outlet property="typeImageView" destination="Ex8-CB-iNc" id="T7o-zd-qOo"/>
</connections>
<point key="canvasLocation" x="128" y="-22"/>
</tableCellView>
</objects>
<resources>
<image name="doc.on.doc.fill" catalog="system" width="17" height="19"/>
<image name="link.circle.fill" catalog="system" width="20" height="20"/>
<namedColor name="AccentColor">
<color red="0.0" green="0.46000000000000002" blue="0.89000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>

View file

@ -0,0 +1,254 @@
//
// ShareTableViewDataSource.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 27/2/24.
//
import AppKit
import FileProvider
import NextcloudKit
import NextcloudCapabilitiesKit
import OSLog
class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDelegate {
private let shareItemViewIdentifier = NSUserInterfaceItemIdentifier("ShareTableItemView")
private let shareItemViewNib = NSNib(nibNamed: "ShareTableItemView", bundle: nil)
private let reattemptInterval: TimeInterval = 3.0
var uiDelegate: ShareViewDataSourceUIDelegate?
var sharesTableView: NSTableView? {
didSet {
sharesTableView?.register(shareItemViewNib, forIdentifier: shareItemViewIdentifier)
sharesTableView?.rowHeight = 42.0 // Height of view in ShareTableItemView XIB
sharesTableView?.dataSource = self
sharesTableView?.delegate = self
sharesTableView?.reloadData()
}
}
var capabilities: Capabilities?
var itemMetadata: NKFile?
private(set) var kit: NextcloudKit?
private(set) var itemURL: URL?
private(set) var itemServerRelativePath: String?
private(set) var shares: [NKShare] = [] {
didSet { Task { @MainActor in sharesTableView?.reloadData() } }
}
private var account: NextcloudAccount? {
didSet {
guard let account = account else { return }
kit = NextcloudKit()
kit?.setup(
user: account.username,
userId: account.username,
password: account.password,
urlBase: account.serverUrl
)
}
}
func loadItem(url: URL) {
itemServerRelativePath = nil
itemURL = url
Task {
await reload()
}
}
func reattempt() {
DispatchQueue.main.async {
Timer.scheduledTimer(withTimeInterval: self.reattemptInterval, repeats: false) { _ in
Task { await self.reload() }
}
}
}
func reload() async {
guard let itemURL = itemURL else { return }
guard let itemIdentifier = await withCheckedContinuation({
(continuation: CheckedContinuation<NSFileProviderItemIdentifier?, Never>) -> Void in
NSFileProviderManager.getIdentifierForUserVisibleFile(
at: itemURL
) { identifier, domainIdentifier, error in
defer { continuation.resume(returning: identifier) }
guard error == nil else {
self.presentError("No item with identifier: \(error.debugDescription)")
return
}
}
}) else {
presentError("Could not get identifier for item, no shares can be acquired.")
return
}
do {
let connection = try await serviceConnection(url: itemURL)
guard let serverPath = await connection.itemServerPath(identifier: itemIdentifier),
let credentials = await connection.credentials() as? Dictionary<String, String>,
let convertedAccount = NextcloudAccount(dictionary: credentials),
!convertedAccount.password.isEmpty
else {
presentError("Failed to get details from File Provider Extension. Retrying.")
reattempt()
return
}
let serverPathString = serverPath as String
itemServerRelativePath = serverPathString
account = convertedAccount
await sharesTableView?.deselectAll(self)
capabilities = await fetchCapabilities()
guard capabilities?.filesSharing?.apiEnabled == true else {
presentError("Server does not support shares.")
return
}
itemMetadata = await fetchItemMetadata(itemRelativePath: serverPathString)
guard itemMetadata?.permissions.contains("R") == true else {
presentError("This file cannot be shared.")
return
}
shares = await fetch(
itemIdentifier: itemIdentifier, itemRelativePath: serverPathString
)
} catch let error {
presentError("Could not reload data: \(error), will try again.")
reattempt()
}
}
private func serviceConnection(url: URL) async throws -> FPUIExtensionService {
let services = try await FileManager().fileProviderServicesForItem(at: url)
guard let service = services[fpUiExtensionServiceName] else {
Logger.sharesDataSource.error("Couldn't get service, required service not present")
throw NSFileProviderError(.providerNotFound)
}
let connection: NSXPCConnection
connection = try await service.fileProviderConnection()
connection.remoteObjectInterface = NSXPCInterface(with: FPUIExtensionService.self)
connection.interruptionHandler = {
Logger.sharesDataSource.error("Service connection interrupted")
}
connection.resume()
guard let proxy = connection.remoteObjectProxy as? FPUIExtensionService else {
throw NSFileProviderError(.serverUnreachable)
}
return proxy
}
private func fetch(
itemIdentifier: NSFileProviderItemIdentifier, itemRelativePath: String
) async -> [NKShare] {
Task { @MainActor in uiDelegate?.fetchStarted() }
defer { Task { @MainActor in uiDelegate?.fetchFinished() } }
let rawIdentifier = itemIdentifier.rawValue
Logger.sharesDataSource.info("Fetching shares for item \(rawIdentifier, privacy: .public)")
guard let kit = kit else {
self.presentError("NextcloudKit instance is unavailable, cannot fetch shares!")
return []
}
let parameter = NKShareParameter(path: itemRelativePath)
return await withCheckedContinuation { continuation in
kit.readShares(parameters: parameter) { account, shares, data, error in
let shareCount = shares?.count ?? 0
Logger.sharesDataSource.info("Received \(shareCount, privacy: .public) shares")
defer { continuation.resume(returning: shares ?? []) }
guard error == .success else {
self.presentError("Error fetching shares: \(error.errorDescription)")
return
}
}
}
}
private func fetchCapabilities() async -> Capabilities? {
return await withCheckedContinuation { continuation in
kit?.getCapabilities { account, capabilitiesJson, error in
guard error == .success, let capabilitiesJson = capabilitiesJson else {
self.presentError("Error getting server caps: \(error.errorDescription)")
continuation.resume(returning: nil)
return
}
Logger.sharesDataSource.info("Successfully retrieved server share capabilities")
continuation.resume(returning: Capabilities(data: capabilitiesJson))
}
}
}
private func fetchItemMetadata(itemRelativePath: String) async -> NKFile? {
guard let kit = kit else {
presentError("Could not fetch item metadata as NextcloudKit instance is unavailable")
return nil
}
func slashlessPath(_ string: String) -> String {
var strCopy = string
if strCopy.hasPrefix("/") {
strCopy.removeFirst()
}
if strCopy.hasSuffix("/") {
strCopy.removeLast()
}
return strCopy
}
let nkCommon = kit.nkCommonInstance
let urlBase = slashlessPath(nkCommon.urlBase)
let davSuffix = slashlessPath(nkCommon.dav)
let userId = nkCommon.userId
let itemRelPath = slashlessPath(itemRelativePath)
let itemFullServerPath = "\(urlBase)/\(davSuffix)/files/\(userId)/\(itemRelPath)"
return await withCheckedContinuation { continuation in
kit.readFileOrFolder(serverUrlFileName: itemFullServerPath, depth: "0") {
account, files, data, error in
guard error == .success else {
self.presentError("Error getting item metadata: \(error.errorDescription)")
continuation.resume(returning: nil)
return
}
Logger.sharesDataSource.info("Successfully retrieved item metadata")
continuation.resume(returning: files.first)
}
}
}
private func presentError(_ errorString: String) {
Logger.sharesDataSource.error("\(errorString, privacy: .public)")
Task { @MainActor in self.uiDelegate?.showError(errorString) }
}
// MARK: - NSTableViewDataSource protocol methods
@objc func numberOfRows(in tableView: NSTableView) -> Int {
shares.count
}
// MARK: - NSTableViewDelegate protocol methods
@objc func tableView(
_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int
) -> NSView? {
let share = shares[row]
guard let view = tableView.makeView(
withIdentifier: shareItemViewIdentifier, owner: self
) as? ShareTableItemView else {
Logger.sharesDataSource.error("Acquired item view from table is not a share item view!")
return nil
}
view.share = share
return view
}
@objc func tableViewSelectionDidChange(_ notification: Notification) {
guard let selectedRow = sharesTableView?.selectedRow, selectedRow >= 0 else {
Task { @MainActor in uiDelegate?.hideOptions(self) }
return
}
let share = shares[selectedRow]
Task { @MainActor in uiDelegate?.showOptions(share: share) }
}
}

View file

@ -0,0 +1,176 @@
//
// ShareViewController.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 21/2/24.
//
import AppKit
import FileProvider
import NextcloudKit
import OSLog
import QuickLookThumbnailing
class ShareViewController: NSViewController, ShareViewDataSourceUIDelegate {
let shareDataSource = ShareTableViewDataSource()
let itemIdentifiers: [NSFileProviderItemIdentifier]
@IBOutlet weak var fileNameIcon: NSImageView!
@IBOutlet weak var fileNameLabel: NSTextField!
@IBOutlet weak var descriptionLabel: NSTextField!
@IBOutlet weak var createButton: NSButton!
@IBOutlet weak var closeButton: NSButton!
@IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var optionsView: ShareOptionsView!
@IBOutlet weak var splitView: NSSplitView!
@IBOutlet weak var loadingEffectView: NSVisualEffectView!
@IBOutlet weak var loadingIndicator: NSProgressIndicator!
@IBOutlet weak var errorMessageStackView: NSStackView!
@IBOutlet weak var errorTextLabel: NSTextField!
@IBOutlet weak var noSharesLabel: NSTextField!
public override var nibName: NSNib.Name? {
return NSNib.Name(self.className)
}
var actionViewController: DocumentActionViewController! {
return parent as? DocumentActionViewController
}
init(_ itemIdentifiers: [NSFileProviderItemIdentifier]) {
self.itemIdentifiers = itemIdentifiers
super.init(nibName: nil, bundle: nil)
guard let firstItem = itemIdentifiers.first else {
Logger.shareViewController.error("called without items")
closeAction(self)
return
}
Task {
await processItemIdentifier(firstItem)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
dismissError(self)
hideOptions(self)
}
@IBAction func closeAction(_ sender: Any) {
actionViewController.extensionContext.completeRequest()
}
private func processItemIdentifier(_ itemIdentifier: NSFileProviderItemIdentifier) async {
guard let manager = NSFileProviderManager(for: actionViewController.domain) else {
fatalError("NSFileProviderManager isn't expected to fail")
}
do {
let itemUrl = try await manager.getUserVisibleURL(for: itemIdentifier)
await updateDisplay(itemUrl: itemUrl)
shareDataSource.uiDelegate = self
shareDataSource.sharesTableView = tableView
shareDataSource.loadItem(url: itemUrl)
optionsView.dataSource = shareDataSource
} catch let error {
let errorString = "Error processing item: \(error)"
Logger.shareViewController.error("\(errorString)")
fileNameLabel.stringValue = "Unknown item"
descriptionLabel.stringValue = errorString
}
}
private func updateDisplay(itemUrl: URL) async {
fileNameLabel.stringValue = itemUrl.lastPathComponent
let request = QLThumbnailGenerator.Request(
fileAt: itemUrl,
size: CGSize(width: 128, height: 128),
scale: 1.0,
representationTypes: .icon
)
let generator = QLThumbnailGenerator.shared
let fileThumbnail = await withCheckedContinuation { continuation in
generator.generateRepresentations(for: request) { thumbnail, type, error in
if thumbnail == nil || error != nil {
Logger.shareViewController.error("Could not get thumbnail: \(error)")
}
continuation.resume(returning: thumbnail)
}
}
fileNameIcon.image = fileThumbnail?.nsImage
let resourceValues = try? itemUrl.resourceValues(
forKeys: [.fileSizeKey, .contentModificationDateKey]
)
var sizeDesc = "Unknown size"
var modDesc = "Unknown modification date"
if let fileSize = resourceValues?.fileSize {
sizeDesc = ByteCountFormatter().string(fromByteCount: Int64(fileSize))
}
if let modificationDate = resourceValues?.contentModificationDate {
let modDateString = DateFormatter.localizedString(
from: modificationDate, dateStyle: .short, timeStyle: .short
)
modDesc = "Last modified: \(modDateString)"
}
descriptionLabel.stringValue = "\(sizeDesc) · \(modDesc)"
}
@IBAction func dismissError(_ sender: Any) {
errorMessageStackView.isHidden = true
}
@IBAction func createShare(_ sender: Any) {
guard let kit = shareDataSource.kit else { return }
optionsView.kit = kit
optionsView.createMode = true
tableView.deselectAll(self)
if !splitView.arrangedSubviews.contains(optionsView) {
splitView.addArrangedSubview(optionsView)
optionsView.isHidden = false
}
}
func fetchStarted() {
loadingEffectView.isHidden = false
loadingIndicator.startAnimation(self)
}
func fetchFinished() {
noSharesLabel.isHidden = !shareDataSource.shares.isEmpty
loadingEffectView.isHidden = true
loadingIndicator.stopAnimation(self)
}
func hideOptions(_ sender: Any) {
if sender as? ShareTableViewDataSource == shareDataSource, optionsView.createMode {
// Do not hide options if the table view has had everything deselected when we set the
// options view to be in create mode
return
}
splitView.removeArrangedSubview(optionsView)
optionsView.isHidden = true
}
func showOptions(share: NKShare) {
guard let kit = shareDataSource.kit else { return }
optionsView.kit = kit
optionsView.controller = ShareController(share: share, kit: kit)
if !splitView.arrangedSubviews.contains(optionsView) {
splitView.addArrangedSubview(optionsView)
optionsView.isHidden = false
}
}
func showError(_ errorString: String) {
errorMessageStackView.isHidden = false
errorTextLabel.stringValue = errorString
}
}

View file

@ -0,0 +1,532 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22689"/>
<capability name="Image references" minToolsVersion="12.0"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="ShareViewController" customModule="FileProviderUIExt" customModuleProvider="target">
<connections>
<outlet property="closeButton" destination="aDA-n9-Zly" id="niR-Ad-FEa"/>
<outlet property="createButton" destination="BMA-BP-wHc" id="HEq-bN-i9V"/>
<outlet property="descriptionLabel" destination="gX0-nE-MrU" id="RoY-u1-1on"/>
<outlet property="errorMessageStackView" destination="dFs-Gh-2WQ" id="kkQ-Uq-xk7"/>
<outlet property="errorTextLabel" destination="770-HW-oC7" id="gfn-SV-TNM"/>
<outlet property="fileNameIcon" destination="zSV-DV-Ray" id="336-e0-CEo"/>
<outlet property="fileNameLabel" destination="slV-H6-zJ3" id="DPp-sN-Yff"/>
<outlet property="loadingEffectView" destination="1ud-mC-gQV" id="HMT-Sb-Axl"/>
<outlet property="loadingIndicator" destination="acb-Yu-Zpm" id="9Jf-dE-7LE"/>
<outlet property="noSharesLabel" destination="K3D-6U-Cbr" id="zDP-E4-9bg"/>
<outlet property="optionsView" destination="EXb-m8-yzj" id="uAb-lv-EZ4"/>
<outlet property="splitView" destination="91w-SP-6sl" id="20T-gQ-SPY"/>
<outlet property="tableView" destination="vb0-a6-eeH" id="KQo-eg-dba"/>
<outlet property="view" destination="Jw6-da-U8j" id="5Ek-F1-w7C"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<view translatesAutoresizingMaskIntoConstraints="NO" id="Jw6-da-U8j">
<rect key="frame" x="0.0" y="0.0" width="500" height="829"/>
<subviews>
<splitView arrangesAllSubviews="NO" dividerStyle="thin" translatesAutoresizingMaskIntoConstraints="NO" id="91w-SP-6sl">
<rect key="frame" x="10" y="10" width="480" height="809"/>
<subviews>
<stackView distribution="fill" orientation="vertical" alignment="leading" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wGI-UV-bB3">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<subviews>
<stackView distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9kj-aB-aJh">
<rect key="frame" x="0.0" y="437" width="480" height="43"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="zSV-DV-Ray">
<rect key="frame" x="0.0" y="-3.5" width="43" height="50"/>
<constraints>
<constraint firstAttribute="width" secondItem="zSV-DV-Ray" secondAttribute="height" multiplier="1:1" id="NSH-gA-7lL"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="doc" catalog="system" id="laO-OA-5sJ"/>
</imageView>
<stackView distribution="fill" orientation="vertical" alignment="leading" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="oSY-fV-uws">
<rect key="frame" x="51" y="0.0" width="375" height="43"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="slV-H6-zJ3">
<rect key="frame" x="-2" y="24" width="78" height="19"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="File name" id="Uuo-1j-to8">
<font key="font" metaFont="systemBold" size="16"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gX0-nE-MrU">
<rect key="frame" x="-2" y="0.0" width="146" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="File size · Last modified" id="1GC-Gr-x29">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<button translatesAutoresizingMaskIntoConstraints="NO" id="BMA-BP-wHc">
<rect key="frame" x="434" y="-3.5" width="18.5" height="51"/>
<buttonCell key="cell" type="bevel" title="Create share" bezelStyle="regularSquare" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="cdh-AC-lt4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<imageReference key="image" image="plus" catalog="system" symbolScale="large"/>
</buttonCell>
<connections>
<action selector="createShare:" target="-2" id="NLV-ZM-y1w"/>
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="aDA-n9-Zly">
<rect key="frame" x="460" y="-5" width="20" height="54"/>
<buttonCell key="cell" type="bevel" title="Close" bezelStyle="rounded" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="zNR-DC-3xZ">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<imageReference key="image" image="xmark.circle.fill" catalog="system" symbolScale="large"/>
</buttonCell>
<connections>
<action selector="closeAction:" target="-2" id="D9k-gc-AcN"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="43" id="C79-af-CZw"/>
<constraint firstItem="oSY-fV-uws" firstAttribute="height" secondItem="9kj-aB-aJh" secondAttribute="height" id="I6w-n6-pgZ"/>
<constraint firstAttribute="bottom" secondItem="BMA-BP-wHc" secondAttribute="bottom" id="RE6-Rg-yv9"/>
<constraint firstItem="zSV-DV-Ray" firstAttribute="height" secondItem="9kj-aB-aJh" secondAttribute="height" id="X4V-Vr-Q7m"/>
<constraint firstItem="BMA-BP-wHc" firstAttribute="top" secondItem="9kj-aB-aJh" secondAttribute="top" id="hrc-la-YmY"/>
<constraint firstItem="aDA-n9-Zly" firstAttribute="height" secondItem="9kj-aB-aJh" secondAttribute="height" id="qCh-8Z-jcN"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<stackView distribution="fillProportionally" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" verticalCompressionResistancePriority="1000" detachesHiddenViews="YES" id="dFs-Gh-2WQ">
<rect key="frame" x="0.0" y="308" width="480" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="770-HW-oC7">
<rect key="frame" x="0.0" y="0.0" width="411" height="40"/>
<textFieldCell key="cell" selectable="YES" borderStyle="border" alignment="left" title="A long error message that provides detail about why some operations with the shares failed" drawsBackground="YES" id="Jvl-L9-x8K">
<font key="font" metaFont="systemSemibold" size="13"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
<color key="backgroundColor" name="systemRedColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="qfq-F1-w0b">
<rect key="frame" x="416" y="-4" width="67" height="47"/>
<buttonCell key="cell" type="bevel" title="Dismiss" bezelStyle="regularSquare" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="dLP-yX-dyk">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<color key="bezelColor" name="systemRedColor" catalog="System" colorSpace="catalog"/>
<connections>
<action selector="dismissError:" target="-2" id="qLa-KM-cYK"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="770-HW-oC7" firstAttribute="top" secondItem="dFs-Gh-2WQ" secondAttribute="top" id="2Y8-H2-wTQ"/>
<constraint firstItem="qfq-F1-w0b" firstAttribute="centerY" secondItem="dFs-Gh-2WQ" secondAttribute="centerY" id="e6n-1Y-kUh"/>
<constraint firstAttribute="bottom" secondItem="770-HW-oC7" secondAttribute="bottom" id="fHi-My-Qyc"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<scrollView autohidesScrollers="YES" horizontalLineScroll="17" horizontalPageScroll="10" verticalLineScroll="17" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZzY-1Z-3xa">
<rect key="frame" x="0.0" y="0.0" width="480" height="300"/>
<clipView key="contentView" drawsBackground="NO" id="Ixg-th-Nw0">
<rect key="frame" x="1" y="1" width="478" height="298"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowSizeStyle="automatic" viewBased="YES" id="vb0-a6-eeH">
<rect key="frame" x="0.0" y="0.0" width="478" height="298"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="17" height="0.0"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn identifier="AutomaticTableColumnIdentifier.0" width="466" minWidth="40" maxWidth="1000" id="3Eb-aD-Ueu">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="mEe-4r-5cx">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
</tableColumns>
</tableView>
</subviews>
<nil key="backgroundColor"/>
</clipView>
<constraints>
<constraint firstAttribute="height" constant="300" id="FAW-Ws-zy7"/>
</constraints>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="hQs-hA-bDq">
<rect key="frame" x="1" y="284" width="478" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="ryi-jk-XFM">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<constraints>
<constraint firstItem="ZzY-1Z-3xa" firstAttribute="leading" secondItem="wGI-UV-bB3" secondAttribute="leading" id="528-fv-ZfM"/>
<constraint firstItem="9kj-aB-aJh" firstAttribute="top" secondItem="wGI-UV-bB3" secondAttribute="top" id="EOP-84-2ZH"/>
<constraint firstAttribute="trailing" secondItem="ZzY-1Z-3xa" secondAttribute="trailing" id="Kt7-wJ-gb5"/>
<constraint firstItem="9kj-aB-aJh" firstAttribute="leading" secondItem="wGI-UV-bB3" secondAttribute="leading" id="UyT-D1-Awv"/>
<constraint firstAttribute="trailing" secondItem="9kj-aB-aJh" secondAttribute="trailing" id="kua-OU-nbq"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<view translatesAutoresizingMaskIntoConstraints="NO" id="EXb-m8-yzj" customClass="ShareOptionsView" customModule="FileProviderUIExt" customModuleProvider="target">
<rect key="frame" x="0.0" y="481" width="480" height="328"/>
<subviews>
<stackView distribution="fill" orientation="vertical" alignment="leading" spacing="5" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T4D-9c-PyA">
<rect key="frame" x="10" y="10" width="460" height="308"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" verticalCompressionResistancePriority="749" translatesAutoresizingMaskIntoConstraints="NO" id="AWy-Qo-wHH">
<rect key="frame" x="-2" y="292" width="464" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Share options" id="fzf-0v-uHo">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aRB-vw-b4r">
<rect key="frame" x="-3" y="263" width="467" height="25"/>
<popUpButtonCell key="cell" type="push" title="Public link share" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="JhA-rv-1xy" id="S60-Qi-URJ">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="yag-Vc-J7Y">
<items>
<menuItem title="Public link share" state="on" id="JhA-rv-1xy"/>
<menuItem title="User share" id="CpL-qc-lAA"/>
<menuItem title="Group share" id="bnp-aV-ZvE"/>
<menuItem title="Email share" id="5DB-JD-Ij0">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Federated cloud share" id="RZP-ME-baz">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Circle share" id="yDE-lS-rJZ">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Talk conversation share" id="aHo-Mr-vTn">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="shareTypePickerAction:" target="EXb-m8-yzj" id="LN7-TC-RvV"/>
</connections>
</popUpButton>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="f8k-Ae-oQc">
<rect key="frame" x="0.0" y="240" width="460" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Share recipient" bezelStyle="round" id="Ahi-gU-lmO">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="CXW-ZO-B2f">
<rect key="frame" x="0.0" y="213" width="460" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Share label" bezelStyle="round" id="ZsJ-zc-mFT">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="luZ-Vf-V24">
<rect key="frame" x="-2" y="191" width="175" height="18"/>
<buttonCell key="cell" type="check" title="Allow upload and editing" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="vOP-1k-c75">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2TN-96-apv">
<rect key="frame" x="-2" y="170" width="117" height="18"/>
<buttonCell key="cell" type="check" title="Hide download" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="6Eu-NS-uZ7">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ylD-hi-7Oq">
<rect key="frame" x="-2" y="149" width="132" height="18"/>
<buttonCell key="cell" type="check" title="Password protect" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="MWC-hf-0pc">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="passwordCheckboxAction:" target="EXb-m8-yzj" id="QMn-RM-jdf"/>
</connections>
</button>
<secureTextField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pkv-9L-nhv">
<rect key="frame" x="0.0" y="123" width="460" height="22"/>
<secureTextFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Enter a new password" usesSingleLineMode="YES" bezelStyle="round" id="hcA-we-oYG">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</secureTextFieldCell>
</secureTextField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="HaL-4Z-csA">
<rect key="frame" x="-2" y="101" width="117" height="18"/>
<buttonCell key="cell" type="check" title="Expiration date" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="r1a-iX-8Xo">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="expirationDateCheckboxAction:" target="EXb-m8-yzj" id="KHG-U0-rsG"/>
</connections>
</button>
<datePicker verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="46m-5F-ME5">
<rect key="frame" x="0.0" y="73" width="463" height="28"/>
<datePickerCell key="cell" borderStyle="bezel" alignment="left" useCurrentDate="YES" id="EHU-Gu-Dfh">
<font key="font" metaFont="system"/>
<date key="date" timeIntervalSinceReferenceDate="734115148.45631897">
<!--2024-04-06 16:52:28 +0000-->
</date>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
</datePickerCell>
</datePicker>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="c10-ub-U31">
<rect key="frame" x="-2" y="51" width="132" height="18"/>
<buttonCell key="cell" type="check" title="Note for recipient" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="oLA-Nu-fzO">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="noteForRecipientCheckboxAction:" target="EXb-m8-yzj" id="j7a-Tc-uiu"/>
</connections>
</button>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="io6-Kg-fLl">
<rect key="frame" x="0.0" y="25" width="460" height="22"/>
<textFieldCell key="cell" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Note for the recipient" bezelStyle="round" id="z5W-BH-NnM">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<stackView distribution="fillEqually" orientation="horizontal" alignment="bottom" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mWb-eX-nfh">
<rect key="frame" x="0.0" y="0.0" width="460" height="20"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bmf-yY-Y7V">
<rect key="frame" x="-7" y="-7" width="240" height="32"/>
<buttonCell key="cell" type="push" title="Delete" bezelStyle="rounded" image="trash.fill" catalog="system" imagePosition="leading" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Kb4-Qg-9Ag">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<color key="bezelColor" name="systemRedColor" catalog="System" colorSpace="catalog"/>
<connections>
<action selector="delete:" target="EXb-m8-yzj" id="PZq-SH-QVa"/>
</connections>
</button>
<button horizontalHuggingPriority="249" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PaV-TL-cbq">
<rect key="frame" x="227" y="-7" width="240" height="32"/>
<buttonCell key="cell" type="push" title="Save" bezelStyle="rounded" image="arrow.up.square.fill" catalog="system" imagePosition="trailing" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="O1I-T0-iRC">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<color key="bezelColor" name="AccentColor"/>
<connections>
<action selector="save:" target="EXb-m8-yzj" id="O4N-dj-SlN"/>
</connections>
</button>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
</subviews>
<constraints>
<constraint firstItem="46m-5F-ME5" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="5LB-gI-S8e"/>
<constraint firstItem="io6-Kg-fLl" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="94h-0I-pw6"/>
<constraint firstAttribute="trailing" secondItem="pkv-9L-nhv" secondAttribute="trailing" id="IFt-uJ-etl"/>
<constraint firstAttribute="trailing" secondItem="CXW-ZO-B2f" secondAttribute="trailing" id="Uvk-cg-D23"/>
<constraint firstAttribute="trailing" secondItem="aRB-vw-b4r" secondAttribute="trailing" id="Zp9-vZ-oxU"/>
<constraint firstItem="pkv-9L-nhv" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="bFe-9X-gFR"/>
<constraint firstItem="AWy-Qo-wHH" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="dyr-SA-Kmy"/>
<constraint firstAttribute="trailing" secondItem="46m-5F-ME5" secondAttribute="trailing" id="gV6-Jx-VH4"/>
<constraint firstAttribute="bottom" secondItem="mWb-eX-nfh" secondAttribute="bottom" id="iLe-Zw-oO2"/>
<constraint firstAttribute="trailing" secondItem="io6-Kg-fLl" secondAttribute="trailing" id="mkY-UE-SAy"/>
<constraint firstItem="AWy-Qo-wHH" firstAttribute="top" secondItem="T4D-9c-PyA" secondAttribute="top" id="ndl-52-MnV"/>
<constraint firstItem="CXW-ZO-B2f" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="oWL-JR-HTk"/>
<constraint firstItem="mWb-eX-nfh" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="wiX-kw-Ohy"/>
<constraint firstItem="aRB-vw-b4r" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="xPY-Ll-QCz"/>
<constraint firstAttribute="trailing" secondItem="mWb-eX-nfh" secondAttribute="trailing" id="yUj-je-uXE"/>
<constraint firstAttribute="trailing" secondItem="AWy-Qo-wHH" secondAttribute="trailing" id="zxC-eX-HrE"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
</subviews>
<constraints>
<constraint firstItem="T4D-9c-PyA" firstAttribute="leading" secondItem="EXb-m8-yzj" secondAttribute="leading" constant="10" id="Dhe-wL-ygv"/>
<constraint firstItem="T4D-9c-PyA" firstAttribute="top" secondItem="EXb-m8-yzj" secondAttribute="top" constant="10" id="JrC-o6-So3"/>
<constraint firstAttribute="trailing" secondItem="T4D-9c-PyA" secondAttribute="trailing" constant="10" id="gs6-zf-D6h"/>
<constraint firstAttribute="bottom" secondItem="T4D-9c-PyA" secondAttribute="bottom" constant="10" id="r5C-pm-lv4"/>
</constraints>
<connections>
<outlet property="circleShare" destination="yDE-lS-rJZ" id="yHV-25-RJd"/>
<outlet property="deleteButton" destination="Bmf-yY-Y7V" id="Wd0-LR-DV5"/>
<outlet property="emailShareMenuItem" destination="5DB-JD-Ij0" id="LD5-P8-V2l"/>
<outlet property="expirationDateCheckbox" destination="HaL-4Z-csA" id="vIf-I8-e3i"/>
<outlet property="expirationDatePicker" destination="46m-5F-ME5" id="eGk-fe-IIf"/>
<outlet property="federatedCloudShareMenuItem" destination="RZP-ME-baz" id="quL-N2-y1z"/>
<outlet property="groupShareMenuItem" destination="bnp-aV-ZvE" id="8iU-IP-YXK"/>
<outlet property="hideDownloadCheckbox" destination="2TN-96-apv" id="OPr-4x-aiK"/>
<outlet property="labelTextField" destination="CXW-ZO-B2f" id="otQ-jh-Psr"/>
<outlet property="noteForRecipientCheckbox" destination="c10-ub-U31" id="aG6-4P-cBv"/>
<outlet property="noteTextField" destination="io6-Kg-fLl" id="JKm-A1-SqR"/>
<outlet property="optionsTitleTextField" destination="AWy-Qo-wHH" id="BjX-oW-0Lp"/>
<outlet property="passwordProtectCheckbox" destination="ylD-hi-7Oq" id="qdw-aF-uh2"/>
<outlet property="passwordSecureField" destination="pkv-9L-nhv" id="992-i5-CPF"/>
<outlet property="publicLinkShareMenuItem" destination="JhA-rv-1xy" id="usv-L6-M7k"/>
<outlet property="saveButton" destination="PaV-TL-cbq" id="OvF-Le-oQj"/>
<outlet property="shareRecipientTextField" destination="f8k-Ae-oQc" id="bfc-Vn-Zu9"/>
<outlet property="shareTypePicker" destination="aRB-vw-b4r" id="Dfx-Dw-zEM"/>
<outlet property="talkConversationShare" destination="aHo-Mr-vTn" id="hxF-qw-VQO"/>
<outlet property="uploadEditPermissionCheckbox" destination="luZ-Vf-V24" id="ojW-WP-98U"/>
<outlet property="userShareMenuItem" destination="CpL-qc-lAA" id="R2q-dc-og5"/>
</connections>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="EXb-m8-yzj" secondAttribute="trailing" id="9yJ-jE-Iin"/>
<constraint firstAttribute="bottom" secondItem="EXb-m8-yzj" secondAttribute="bottom" id="IDB-la-fxl"/>
<constraint firstItem="EXb-m8-yzj" firstAttribute="leading" secondItem="91w-SP-6sl" secondAttribute="leading" id="imf-Yj-oUg"/>
</constraints>
<holdingPriorities>
<real value="250"/>
<real value="250"/>
</holdingPriorities>
</splitView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="K3D-6U-Cbr">
<rect key="frame" x="8" y="474" width="484" height="31"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="No shares" id="byC-34-Vtu">
<font key="font" textStyle="largeTitle" name=".SFNS-Regular"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<visualEffectView blendingMode="withinWindow" material="HUDWindow" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="1ud-mC-gQV">
<rect key="frame" x="10" y="339" width="480" height="300"/>
<subviews>
<progressIndicator wantsLayer="YES" maxValue="100" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="acb-Yu-Zpm">
<rect key="frame" x="224" y="134" width="32" height="32"/>
</progressIndicator>
</subviews>
<constraints>
<constraint firstItem="acb-Yu-Zpm" firstAttribute="centerY" secondItem="1ud-mC-gQV" secondAttribute="centerY" id="Dhf-yv-2Wr"/>
<constraint firstItem="acb-Yu-Zpm" firstAttribute="centerX" secondItem="1ud-mC-gQV" secondAttribute="centerX" id="Kzb-iw-xCx"/>
</constraints>
</visualEffectView>
</subviews>
<constraints>
<constraint firstItem="1ud-mC-gQV" firstAttribute="bottom" secondItem="ZzY-1Z-3xa" secondAttribute="bottom" id="HTR-Xt-ACu"/>
<constraint firstItem="1ud-mC-gQV" firstAttribute="top" secondItem="ZzY-1Z-3xa" secondAttribute="top" id="Hjv-Wk-Rk1"/>
<constraint firstItem="91w-SP-6sl" firstAttribute="top" secondItem="Jw6-da-U8j" secondAttribute="top" constant="10" id="JFe-jp-e9y"/>
<constraint firstAttribute="width" constant="500" id="KBX-aG-ZDU"/>
<constraint firstItem="1ud-mC-gQV" firstAttribute="trailing" secondItem="ZzY-1Z-3xa" secondAttribute="trailing" id="Kpk-6o-ao7"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="380" id="LND-hR-7Co"/>
<constraint firstAttribute="trailing" secondItem="91w-SP-6sl" secondAttribute="trailing" constant="10" id="XrV-hl-kxC"/>
<constraint firstAttribute="bottom" secondItem="91w-SP-6sl" secondAttribute="bottom" constant="10" id="Z4B-85-NR5"/>
<constraint firstItem="1ud-mC-gQV" firstAttribute="height" secondItem="ZzY-1Z-3xa" secondAttribute="height" id="dfF-9z-iG9"/>
<constraint firstItem="91w-SP-6sl" firstAttribute="leading" secondItem="Jw6-da-U8j" secondAttribute="leading" constant="10" id="elm-Nc-tc0"/>
<constraint firstItem="1ud-mC-gQV" firstAttribute="leading" secondItem="ZzY-1Z-3xa" secondAttribute="leading" id="kSz-tV-EFM"/>
<constraint firstItem="K3D-6U-Cbr" firstAttribute="leading" secondItem="ZzY-1Z-3xa" secondAttribute="leading" id="lut-va-B3T"/>
<constraint firstItem="K3D-6U-Cbr" firstAttribute="trailing" secondItem="ZzY-1Z-3xa" secondAttribute="trailing" id="vRC-IM-ovt"/>
<constraint firstItem="K3D-6U-Cbr" firstAttribute="centerY" secondItem="ZzY-1Z-3xa" secondAttribute="centerY" id="wFD-ph-Bsp"/>
</constraints>
<point key="canvasLocation" x="-270" y="79"/>
</view>
</objects>
<resources>
<image name="arrow.up.square.fill" catalog="system" width="15" height="14"/>
<image name="doc" catalog="system" width="14" height="16"/>
<image name="plus" catalog="system" width="18" height="17"/>
<image name="trash.fill" catalog="system" width="15" height="17"/>
<image name="xmark.circle.fill" catalog="system" width="20" height="20"/>
<namedColor name="AccentColor">
<color red="0.0" green="0.46000000000000002" blue="0.89000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>

View file

@ -0,0 +1,17 @@
//
// ShareViewDataSourceUIDelegate.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 28/2/24.
//
import Foundation
import NextcloudKit
protocol ShareViewDataSourceUIDelegate {
func fetchStarted()
func fetchFinished()
func hideOptions(_ sender: Any)
func showOptions(share: NKShare)
func showError(_ errorString: String)
}

View file

@ -0,0 +1,60 @@
//
// ShareeSuggestionsDataSource.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 2/4/24.
//
import Foundation
import NextcloudKit
import OSLog
import SuggestionsTextFieldKit
class ShareeSuggestionsDataSource: SuggestionsDataSource {
let kit: NextcloudKit
var suggestions: [Suggestion] = []
var inputString: String = "" {
didSet { Task { await updateSuggestions() } }
}
init(kit: NextcloudKit) {
self.kit = kit
}
private func updateSuggestions() async {
let sharees = await fetchSharees(search: inputString)
Logger.shareeDataSource.info("Fetched \(sharees.count, privacy: .public) sharees.")
suggestions = suggestionsFromSharees(sharees)
NotificationCenter.default.post(name: SuggestionsChangedNotificationName, object: self)
}
private func fetchSharees(search: String) async -> [NKSharee] {
Logger.shareeDataSource.debug("Searching sharees with: \(search, privacy: .public)")
return await withCheckedContinuation { continuation in
kit.searchSharees(
search: inputString,
page: 1,
perPage: 20,
completion: { account, sharees, data, error in
defer { continuation.resume(returning: sharees ?? []) }
guard error == .success else {
Logger.shareeDataSource.error(
"Error fetching sharees: \(error.description, privacy: .public)"
)
return
}
}
)
}
}
private func suggestionsFromSharees(_ sharees: [NKSharee]) -> [Suggestion] {
return sharees.map {
Suggestion(
imageName: "person.fill",
displayText: $0.label.isEmpty ? $0.name : $0.label,
data: $0
)
}
}
}

View file

@ -11,6 +11,7 @@
5307A6E82965DAD8001E0C6A /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6E72965DAD8001E0C6A /* NextcloudKit */; };
5307A6EB2965DB8D001E0C6A /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6EA2965DB8D001E0C6A /* RealmSwift */; };
5307A6F229675346001E0C6A /* NextcloudFilesDatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5307A6F129675346001E0C6A /* NextcloudFilesDatabaseManager.swift */; };
531522822B8E01C6002E31BE /* ShareTableItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 531522812B8E01C6002E31BE /* ShareTableItemView.xib */; };
5318AD9129BF42FB00CBB71C /* NextcloudItemMetadataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9029BF42FB00CBB71C /* NextcloudItemMetadataTable.swift */; };
5318AD9529BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9429BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift */; };
5318AD9729BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9629BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift */; };
@ -20,9 +21,19 @@
5352B36829DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36729DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift */; };
5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */; };
5352E85B29B7BFE6002CE85C /* Progress+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352E85A29B7BFE6002CE85C /* Progress+Extensions.swift */; };
5358F2B92BAA0F5300E3C729 /* NextcloudCapabilitiesKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */; };
535AE30E29C0A2CC0042A9BA /* Logger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535AE30D29C0A2CC0042A9BA /* Logger+Extensions.swift */; };
53651E442BBC0CA300ECAC29 /* SuggestionsTextFieldKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53651E432BBC0CA300ECAC29 /* SuggestionsTextFieldKit */; };
53651E462BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53651E452BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift */; };
536EFBF7295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */; };
536EFC36295E3C1100F4CB13 /* NextcloudAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536EFC35295E3C1100F4CB13 /* NextcloudAccount.swift */; };
5374FD442B95EE1400C78D54 /* ShareController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5374FD432B95EE1400C78D54 /* ShareController.swift */; };
5376307D2B85E2ED0026BFAB /* Logger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5376307C2B85E2ED0026BFAB /* Logger+Extensions.swift */; };
537630912B85F4980026BFAB /* ShareViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 537630902B85F4980026BFAB /* ShareViewController.xib */; };
537630932B85F4B00026BFAB /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630922B85F4B00026BFAB /* ShareViewController.swift */; };
537630952B860D560026BFAB /* FPUIExtensionServiceSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630942B860D560026BFAB /* FPUIExtensionServiceSource.swift */; };
537630972B860D920026BFAB /* FPUIExtensionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630962B860D920026BFAB /* FPUIExtensionService.swift */; };
537630982B8612F00026BFAB /* FPUIExtensionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630962B860D920026BFAB /* FPUIExtensionService.swift */; };
538E396A27F4765000FA63D5 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 538E396927F4765000FA63D5 /* UniformTypeIdentifiers.framework */; };
538E396D27F4765000FA63D5 /* FileProviderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E396C27F4765000FA63D5 /* FileProviderExtension.swift */; };
538E396F27F4765000FA63D5 /* FileProviderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E396E27F4765000FA63D5 /* FileProviderItem.swift */; };
@ -39,11 +50,19 @@
53903D352956184400D0B308 /* LocalSocketClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 539158B127BE891500816F56 /* LocalSocketClient.h */; settings = {ATTRIBUTES = (Public, ); }; };
53903D37295618A400D0B308 /* LineProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 53903D36295618A400D0B308 /* LineProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
539158AC27BE71A900816F56 /* FinderSyncSocketLineProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 539158AB27BE71A900816F56 /* FinderSyncSocketLineProcessor.m */; };
53B979812B84C81F002DA742 /* DocumentActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53B979802B84C81F002DA742 /* DocumentActionViewController.swift */; };
53D056312970594F00988392 /* LocalFilesUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D056302970594F00988392 /* LocalFilesUtils.swift */; };
53D666612B70C9A70042C03D /* FileProviderConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D666602B70C9A70042C03D /* FileProviderConfig.swift */; };
53ED472029C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED471F29C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift */; };
53ED472829C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED472729C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift */; };
53ED473029C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */; };
53FE14502B8E0658006C4193 /* ShareTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */; };
53FE14542B8E1219006C4193 /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53FE14532B8E1219006C4193 /* NextcloudKit */; };
53FE14552B8E28E9006C4193 /* NextcloudAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536EFC35295E3C1100F4CB13 /* NextcloudAccount.swift */; };
53FE14592B8E3F6C006C4193 /* ShareTableItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE14582B8E3F6C006C4193 /* ShareTableItemView.swift */; };
53FE145B2B8F1305006C4193 /* NKShare+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE145A2B8F1305006C4193 /* NKShare+Extensions.swift */; };
53FE14652B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE14642B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift */; };
53FE14672B8F78B6006C4193 /* ShareOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE14662B8F78B6006C4193 /* ShareOptionsView.swift */; };
C2B573BA1B1CD91E00303B36 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573B91B1CD91E00303B36 /* main.m */; };
C2B573D21B1CD94B00303B36 /* main.m in Resources */ = {isa = PBXBuildFile; fileRef = C2B573B91B1CD91E00303B36 /* main.m */; };
C2B573DE1B1CD9CE00303B36 /* FinderSync.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573DD1B1CD9CE00303B36 /* FinderSync.m */; };
@ -143,6 +162,7 @@
/* Begin PBXFileReference section */
5307A6F129675346001E0C6A /* NextcloudFilesDatabaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudFilesDatabaseManager.swift; sourceTree = "<group>"; };
531522812B8E01C6002E31BE /* ShareTableItemView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShareTableItemView.xib; sourceTree = "<group>"; };
5318AD9029BF42FB00CBB71C /* NextcloudItemMetadataTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudItemMetadataTable.swift; sourceTree = "<group>"; };
5318AD9429BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudLocalFileMetadataTable.swift; sourceTree = "<group>"; };
5318AD9629BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderMaterialisedEnumerationObserver.swift; sourceTree = "<group>"; };
@ -155,8 +175,16 @@
5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+Thumbnailing.swift"; sourceTree = "<group>"; };
5352E85A29B7BFE6002CE85C /* Progress+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+Extensions.swift"; sourceTree = "<group>"; };
535AE30D29C0A2CC0042A9BA /* Logger+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Extensions.swift"; sourceTree = "<group>"; };
53651E452BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareeSuggestionsDataSource.swift; sourceTree = "<group>"; };
536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderSocketLineProcessor.swift; sourceTree = "<group>"; };
536EFC35295E3C1100F4CB13 /* NextcloudAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudAccount.swift; sourceTree = "<group>"; };
5374FD432B95EE1400C78D54 /* ShareController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareController.swift; sourceTree = "<group>"; };
5376307C2B85E2ED0026BFAB /* Logger+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Extensions.swift"; sourceTree = "<group>"; };
5376307E2B85E5650026BFAB /* FileProviderUIExt.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = FileProviderUIExt.entitlements; sourceTree = "<group>"; };
537630902B85F4980026BFAB /* ShareViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareViewController.xib; sourceTree = "<group>"; };
537630922B85F4B00026BFAB /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
537630942B860D560026BFAB /* FPUIExtensionServiceSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUIExtensionServiceSource.swift; sourceTree = "<group>"; };
537630962B860D920026BFAB /* FPUIExtensionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUIExtensionService.swift; sourceTree = "<group>"; };
538E396727F4765000FA63D5 /* FileProviderExt.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FileProviderExt.appex; sourceTree = BUILT_PRODUCTS_DIR; };
538E396927F4765000FA63D5 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
538E396C27F4765000FA63D5 /* FileProviderExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderExtension.swift; sourceTree = "<group>"; };
@ -172,11 +200,20 @@
539158AB27BE71A900816F56 /* FinderSyncSocketLineProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FinderSyncSocketLineProcessor.m; sourceTree = "<group>"; };
539158B127BE891500816F56 /* LocalSocketClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocalSocketClient.h; sourceTree = "<group>"; };
539158B227BEC98A00816F56 /* LocalSocketClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LocalSocketClient.m; sourceTree = "<group>"; };
53B9797E2B84C81F002DA742 /* FileProviderUIExt.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FileProviderUIExt.appex; sourceTree = BUILT_PRODUCTS_DIR; };
53B979802B84C81F002DA742 /* DocumentActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentActionViewController.swift; sourceTree = "<group>"; };
53B979852B84C81F002DA742 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
53D056302970594F00988392 /* LocalFilesUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFilesUtils.swift; sourceTree = "<group>"; };
53D666602B70C9A70042C03D /* FileProviderConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderConfig.swift; sourceTree = "<group>"; };
53ED471F29C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderEnumerator+SyncEngine.swift"; sourceTree = "<group>"; };
53ED472729C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NextcloudItemMetadataTable+NKFile.swift"; sourceTree = "<group>"; };
53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+ClientInterface.swift"; sourceTree = "<group>"; };
53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTableViewDataSource.swift; sourceTree = "<group>"; };
53FE14572B8E3A7C006C4193 /* FileProviderUIExtRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FileProviderUIExtRelease.entitlements; sourceTree = "<group>"; };
53FE14582B8E3F6C006C4193 /* ShareTableItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTableItemView.swift; sourceTree = "<group>"; };
53FE145A2B8F1305006C4193 /* NKShare+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NKShare+Extensions.swift"; sourceTree = "<group>"; };
53FE14642B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewDataSourceUIDelegate.swift; sourceTree = "<group>"; };
53FE14662B8F78B6006C4193 /* ShareOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareOptionsView.swift; sourceTree = "<group>"; };
C2B573B11B1CD91E00303B36 /* desktopclient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = desktopclient.app; sourceTree = BUILT_PRODUCTS_DIR; };
C2B573B51B1CD91E00303B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C2B573B91B1CD91E00303B36 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
@ -211,6 +248,16 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
53B9797B2B84C81F002DA742 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5358F2B92BAA0F5300E3C729 /* NextcloudCapabilitiesKit in Frameworks */,
53651E442BBC0CA300ECAC29 /* SuggestionsTextFieldKit in Frameworks */,
53FE14542B8E1219006C4193 /* NextcloudKit in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C2B573AE1B1CD91E00303B36 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -249,6 +296,8 @@
children = (
5350E4E72B0C514400F276CB /* ClientCommunicationProtocol.h */,
5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */,
537630962B860D920026BFAB /* FPUIExtensionService.swift */,
537630942B860D560026BFAB /* FPUIExtensionServiceSource.swift */,
);
path = Services;
sourceTree = "<group>";
@ -263,6 +312,15 @@
path = Extensions;
sourceTree = "<group>";
};
5376307B2B85E2E00026BFAB /* Extensions */ = {
isa = PBXGroup;
children = (
5376307C2B85E2ED0026BFAB /* Logger+Extensions.swift */,
53FE145A2B8F1305006C4193 /* NKShare+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
538E396827F4765000FA63D5 /* Frameworks */ = {
isa = PBXGroup;
children = (
@ -306,12 +364,34 @@
path = NCDesktopClientSocketKit;
sourceTree = "<group>";
};
53B9797F2B84C81F002DA742 /* FileProviderUIExt */ = {
isa = PBXGroup;
children = (
5376307B2B85E2E00026BFAB /* Extensions */,
53B979802B84C81F002DA742 /* DocumentActionViewController.swift */,
5374FD432B95EE1400C78D54 /* ShareController.swift */,
53651E452BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift */,
53FE14662B8F78B6006C4193 /* ShareOptionsView.swift */,
53FE14582B8E3F6C006C4193 /* ShareTableItemView.swift */,
531522812B8E01C6002E31BE /* ShareTableItemView.xib */,
53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */,
537630922B85F4B00026BFAB /* ShareViewController.swift */,
537630902B85F4980026BFAB /* ShareViewController.xib */,
53FE14642B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift */,
5376307E2B85E5650026BFAB /* FileProviderUIExt.entitlements */,
53FE14572B8E3A7C006C4193 /* FileProviderUIExtRelease.entitlements */,
53B979852B84C81F002DA742 /* Info.plist */,
);
path = FileProviderUIExt;
sourceTree = "<group>";
};
C2B573941B1CD88000303B36 = {
isa = PBXGroup;
children = (
C2B573B31B1CD91E00303B36 /* desktopclient */,
C2B573D81B1CD9CE00303B36 /* FinderSyncExt */,
538E396B27F4765000FA63D5 /* FileProviderExt */,
53B9797F2B84C81F002DA742 /* FileProviderUIExt */,
53903D0D2956164F00D0B308 /* NCDesktopClientSocketKit */,
538E396827F4765000FA63D5 /* Frameworks */,
C2B573B21B1CD91E00303B36 /* Products */,
@ -325,6 +405,7 @@
C2B573D71B1CD9CE00303B36 /* FinderSyncExt.appex */,
538E396727F4765000FA63D5 /* FileProviderExt.appex */,
53903D0C2956164F00D0B308 /* NCDesktopClientSocketKit.framework */,
53B9797E2B84C81F002DA742 /* FileProviderUIExt.appex */,
);
name = Products;
sourceTree = "<group>";
@ -430,6 +511,29 @@
productReference = 53903D0C2956164F00D0B308 /* NCDesktopClientSocketKit.framework */;
productType = "com.apple.product-type.framework";
};
53B9797D2B84C81F002DA742 /* FileProviderUIExt */ = {
isa = PBXNativeTarget;
buildConfigurationList = 53B979882B84C820002DA742 /* Build configuration list for PBXNativeTarget "FileProviderUIExt" */;
buildPhases = (
53B9797A2B84C81F002DA742 /* Sources */,
53B9797B2B84C81F002DA742 /* Frameworks */,
53B9797C2B84C81F002DA742 /* Resources */,
);
buildRules = (
);
dependencies = (
53FE14522B8E1213006C4193 /* PBXTargetDependency */,
);
name = FileProviderUIExt;
packageProductDependencies = (
53FE14532B8E1219006C4193 /* NextcloudKit */,
5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */,
53651E432BBC0CA300ECAC29 /* SuggestionsTextFieldKit */,
);
productName = FileProviderUIExt;
productReference = 53B9797E2B84C81F002DA742 /* FileProviderUIExt.appex */;
productType = "com.apple.product-type.app-extension";
};
C2B573B01B1CD91E00303B36 /* desktopclient */ = {
isa = PBXNativeTarget;
buildConfigurationList = C2B573CC1B1CD91E00303B36 /* Build configuration list for PBXNativeTarget "desktopclient" */;
@ -482,7 +586,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1420;
LastSwiftUpdateCheck = 1520;
LastUpgradeCheck = 1240;
TargetAttributes = {
538E396627F4765000FA63D5 = {
@ -492,6 +596,9 @@
CreatedOnToolsVersion = 14.2;
ProvisioningStyle = Manual;
};
53B9797D2B84C81F002DA742 = {
CreatedOnToolsVersion = 15.2;
};
C2B573B01B1CD91E00303B36 = {
CreatedOnToolsVersion = 6.3.1;
DevelopmentTeam = 9B5WD74GWJ;
@ -522,6 +629,8 @@
packageReferences = (
5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */,
5307A6E92965DB57001E0C6A /* XCRemoteSwiftPackageReference "realm-swift" */,
5358F2B72BAA045E00E3C729 /* XCRemoteSwiftPackageReference "NextcloudCapabilitiesKit" */,
53651E422BBC0C7F00ECAC29 /* XCRemoteSwiftPackageReference "SuggestionsTextFieldKit" */,
);
productRefGroup = C2B573B21B1CD91E00303B36 /* Products */;
projectDirPath = "";
@ -530,6 +639,7 @@
C2B573B01B1CD91E00303B36 /* desktopclient */,
C2B573D61B1CD9CE00303B36 /* FinderSyncExt */,
538E396627F4765000FA63D5 /* FileProviderExt */,
53B9797D2B84C81F002DA742 /* FileProviderUIExt */,
53903D0B2956164F00D0B308 /* NCDesktopClientSocketKit */,
);
};
@ -550,6 +660,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
53B9797C2B84C81F002DA742 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
531522822B8E01C6002E31BE /* ShareTableItemView.xib in Resources */,
537630912B85F4980026BFAB /* ShareViewController.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C2B573AF1B1CD91E00303B36 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -603,9 +722,11 @@
53ED472029C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift in Sources */,
5318AD9929BF58D000CBB71C /* NKError+Extensions.swift in Sources */,
53ED472829C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift in Sources */,
537630972B860D920026BFAB /* FPUIExtensionService.swift in Sources */,
5318AD9529BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift in Sources */,
535AE30E29C0A2CC0042A9BA /* Logger+Extensions.swift in Sources */,
5307A6F229675346001E0C6A /* NextcloudFilesDatabaseManager.swift in Sources */,
537630952B860D560026BFAB /* FPUIExtensionServiceSource.swift in Sources */,
53D056312970594F00988392 /* LocalFilesUtils.swift in Sources */,
538E396F27F4765000FA63D5 /* FileProviderItem.swift in Sources */,
5352B36829DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift in Sources */,
@ -626,6 +747,25 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
53B9797A2B84C81F002DA742 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
537630932B85F4B00026BFAB /* ShareViewController.swift in Sources */,
53FE14672B8F78B6006C4193 /* ShareOptionsView.swift in Sources */,
53651E462BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift in Sources */,
53FE14652B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift in Sources */,
53B979812B84C81F002DA742 /* DocumentActionViewController.swift in Sources */,
5374FD442B95EE1400C78D54 /* ShareController.swift in Sources */,
53FE145B2B8F1305006C4193 /* NKShare+Extensions.swift in Sources */,
53FE14592B8E3F6C006C4193 /* ShareTableItemView.swift in Sources */,
53FE14552B8E28E9006C4193 /* NextcloudAccount.swift in Sources */,
5376307D2B85E2ED0026BFAB /* Logger+Extensions.swift in Sources */,
53FE14502B8E0658006C4193 /* ShareTableViewDataSource.swift in Sources */,
537630982B8612F00026BFAB /* FPUIExtensionService.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C2B573AD1B1CD91E00303B36 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -666,6 +806,10 @@
target = 53903D0B2956164F00D0B308 /* NCDesktopClientSocketKit */;
targetProxy = 53903D322956173F00D0B308 /* PBXContainerItemProxy */;
};
53FE14522B8E1213006C4193 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = 53FE14512B8E1213006C4193 /* NextcloudKit */;
};
C2B573E01B1CD9CE00303B36 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = C2B573D61B1CD9CE00303B36 /* FinderSyncExt */;
@ -910,6 +1054,130 @@
};
name = Release;
};
53B979862B84C81F002DA742 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = FileProviderUIExt/FileProviderUIExt.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = FileProviderUIExt/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = FileProviderUIExt;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_OUTPUT_FORMAT = "same-as-input";
INFOPLIST_PREPROCESS = NO;
IPHONEOS_DEPLOYMENT_TARGET = 17.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(OC_APPLICATION_REV_DOMAIN).$(PRODUCT_NAME)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = macOS;
};
name = Debug;
};
53B979872B84C81F002DA742 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = FileProviderUIExt/FileProviderUIExtRelease.entitlements;
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = FileProviderUIExt/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = FileProviderUIExt;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_OUTPUT_FORMAT = "same-as-input";
INFOPLIST_PREPROCESS = NO;
IPHONEOS_DEPLOYMENT_TARGET = 17.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(OC_APPLICATION_REV_DOMAIN).$(PRODUCT_NAME)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = macOS;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
C2B573991B1CD88000303B36 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -1216,6 +1484,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
53B979882B84C820002DA742 /* Build configuration list for PBXNativeTarget "FileProviderUIExt" */ = {
isa = XCConfigurationList;
buildConfigurations = (
53B979862B84C81F002DA742 /* Debug */,
53B979872B84C81F002DA742 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C2B573981B1CD88000303B36 /* Build configuration list for PBXProject "NextcloudIntegration" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@ -1250,8 +1527,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/nextcloud/NextcloudKit.git";
requirement = {
branch = develop;
kind = branch;
kind = upToNextMajorVersion;
minimumVersion = 2.5.9;
};
};
5307A6E92965DB57001E0C6A /* XCRemoteSwiftPackageReference "realm-swift" */ = {
@ -1262,6 +1539,22 @@
version = 10.33.0;
};
};
5358F2B72BAA045E00E3C729 /* XCRemoteSwiftPackageReference "NextcloudCapabilitiesKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/claucambra/NextcloudCapabilitiesKit.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.0.0;
};
};
53651E422BBC0C7F00ECAC29 /* XCRemoteSwiftPackageReference "SuggestionsTextFieldKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/claucambra/SuggestionsTextFieldKit.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@ -1280,6 +1573,26 @@
package = 5307A6E92965DB57001E0C6A /* XCRemoteSwiftPackageReference "realm-swift" */;
productName = RealmSwift;
};
5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */ = {
isa = XCSwiftPackageProductDependency;
package = 5358F2B72BAA045E00E3C729 /* XCRemoteSwiftPackageReference "NextcloudCapabilitiesKit" */;
productName = NextcloudCapabilitiesKit;
};
53651E432BBC0CA300ECAC29 /* SuggestionsTextFieldKit */ = {
isa = XCSwiftPackageProductDependency;
package = 53651E422BBC0C7F00ECAC29 /* XCRemoteSwiftPackageReference "SuggestionsTextFieldKit" */;
productName = SuggestionsTextFieldKit;
};
53FE14512B8E1213006C4193 /* NextcloudKit */ = {
isa = XCSwiftPackageProductDependency;
package = 5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */;
productName = NextcloudKit;
};
53FE14532B8E1219006C4193 /* NextcloudKit */ = {
isa = XCSwiftPackageProductDependency;
package = 5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */;
productName = NextcloudKit;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = C2B573951B1CD88000303B36 /* Project object */;