Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: move resources to bundle #1

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions Filestack.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -259,11 +259,13 @@
4599415226BBF6780039BF8F /* CustomPickerUploadController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPickerUploadController.swift; sourceTree = "<group>"; };
45A2497426BC3A4B0078785D /* SourceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceProvider.swift; sourceTree = "<group>"; };
45A2497626BC3A5E0078785D /* SourceProviderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceProviderDelegate.swift; sourceTree = "<group>"; };
45BFF67A23290B1E00944028 /* Filestack.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Filestack.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
45BFF67A23290B1E00944028 /* Filestack.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Filestack.podspec; sourceTree = "<group>"; };
45BFF67B23290B1E00944028 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
45BFF67C23290B1F00944028 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
45BFF67D23290B1F00944028 /* VERSION */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = VERSION; sourceTree = "<group>"; };
45CF056625D5744E0090966A /* PickerBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerBehavior.swift; sourceTree = "<group>"; };
96E4CEA32860E1BD00E0DC00 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
96E4CEA42860E1BD00E0DC00 /* Package.resolved */ = {isa = PBXFileReference; lastKnownFileType = text; path = Package.resolved; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -757,6 +759,8 @@
457084F51F975C6B00991340 = {
isa = PBXGroup;
children = (
96E4CEA42860E1BD00E0DC00 /* Package.resolved */,
96E4CEA32860E1BD00E0DC00 /* Package.swift */,
45BFF67B23290B1E00944028 /* LICENSE */,
45BFF67D23290B1F00944028 /* VERSION */,
45BFF67C23290B1F00944028 /* CHANGELOG.md */,
Expand Down Expand Up @@ -1227,8 +1231,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/filestack/filestack-swift.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.8.0;
kind = exactVersion;
version = 2.8.0;
};
};
/* End XCRemoteSwiftPackageReference section */
Expand Down
112 changes: 64 additions & 48 deletions Sources/Filestack/Public/Models/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,34 @@ private typealias CompletionHandler = (_ response: CloudResponse, _ safariError:
/// The `Client` class provides an unified API to upload files and manage cloud contents using Filestack REST APIs.
@objc(FSFilestackClient) public class Client: NSObject {
// MARK: - Properties

/// An API key obtained from the [Developer Portal](http://dev.filestack.com).
@objc public let apiKey: String

/// A `Security` object. `nil` by default.
@objc public let security: Security?

/// A `Config` object.
@objc public let config: Config

/// The Filestack SDK client used for uploads and transformations.
@objc public let sdkClient: FilestackSDK.Client

// MARK: - Private Properties

private let cloudService = CloudService()

private var lastToken: String?
private var resumeCloudRequestNotificationObserver: NSObjectProtocol!
private var safariAuthSession: AnyObject?

private lazy var authCallbackURL: URL? = {
guard let scheme = config.callbackURLScheme else { return nil }
return URL(string: "\(scheme)://Filestack")
}()

// MARK: - Lifecyle Functions

/// Default initializer.
///
/// - Parameter apiKey: An API key obtained from the Developer Portal.
Expand All @@ -57,12 +57,12 @@ private typealias CompletionHandler = (_ response: CloudResponse, _ safariError:
self.lastToken = token
self.sdkClient = FilestackSDK.Client(apiKey: apiKey, security: security)
self.config = config ?? Config()

super.init()
}

// MARK: - Public Functions

/// Returns an instance of a `PickerNavigationController` that will allow the user to interactively pick files from
/// a local or cloud source and upload them to a given location.
///
Expand All @@ -71,12 +71,21 @@ private typealias CompletionHandler = (_ response: CloudResponse, _ safariError:
///
/// - Returns: A `PickerNavigationController` that may be presented using `present(_:animated:)` from your view controller.
@objc public func picker(storeOptions: StorageOptions = .defaults) -> PickerNavigationController {
let storyboard = UIStoryboard(name: "Picker", bundle: bundle)
let frameworkBundle = Bundle(for: Self.self)
let bundleURL = frameworkBundle.resourceURL?.appendingPathComponent("Filestack.bundle")
let resourceBundle = Bundle(url: bundleURL!)

#if SWIFT_PACKAGE
let storyboard = UIStoryboard(name: "Picker", bundle: Bundle.module)
#else
let storyboard = UIStoryboard(name: "Picker", bundle: resourceBundle)
#endif

let scene = PickerNavigationScene(client: self, storeOptions: storeOptions)

return storyboard.instantiateViewController(for: scene)
}

/// Uploads a single `Uploadable` to a given storage location.
///
/// Currently the only storage location supported is Amazon S3.
Expand Down Expand Up @@ -106,7 +115,7 @@ private typealias CompletionHandler = (_ response: CloudResponse, _ safariError:
uploadProgress: uploadProgress,
completionHandler: completionHandler)
}

/// Uploads an array of `Uploadable` items to a given storage location.
///
/// Currently the only storage location supported is Amazon S3.
Expand Down Expand Up @@ -138,7 +147,7 @@ private typealias CompletionHandler = (_ response: CloudResponse, _ safariError:
uploadProgress: uploadProgress,
completionHandler: completionHandler)
}

/// Allows interactively picking file(s) from a local source (e.g. camera, photo library or documents) and,
/// optionally, uploads the file(s) to Filestack.
///
Expand All @@ -157,12 +166,12 @@ private typealias CompletionHandler = (_ response: CloudResponse, _ safariError:
pickCompletionHandler: (([URL]) -> Void)? = nil,
uploadCompletionHandler: (([JSONResponse]) -> Void)? = nil) -> Cancellable & Monitorizable {
let uploader: (DeferredAdd & Uploader)?

switch behavior {
case let .uploadAndStore(options):
options.startImmediately = false
options.deleteTemporaryFilesAfterUpload = false

// Setup uploader.
uploader = sdkClient.upload(options: options) { responses in
uploadCompletionHandler?(responses)
Expand All @@ -171,10 +180,10 @@ private typealias CompletionHandler = (_ response: CloudResponse, _ safariError:
// No uploader.
uploader = nil
}

let uploadController: (Cancellable & Monitorizable & Startable)
var uploadController: (Cancellable & Monitorizable & Startable)
let sourceType: UIImagePickerController.SourceType?

switch source {
case .camera:
sourceType = .camera
Expand All @@ -183,7 +192,7 @@ private typealias CompletionHandler = (_ response: CloudResponse, _ safariError:
default:
sourceType = nil
}

if let sourceProvider = source.sourceProvider {
uploadController = CustomPickerUploadController(uploader: uploader,
viewController: presentingViewController,
Expand All @@ -202,17 +211,24 @@ private typealias CompletionHandler = (_ response: CloudResponse, _ safariError:
config: config,
completionBlock: pickCompletionHandler)
}

PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
// Start upload...
DispatchQueue.main.async { uploadController.start() }

if sourceType == .photoLibrary {
PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
DispatchQueue.main.async { uploadController.start() }
} else {
DispatchQueue.main.async {
uploadController = PhotoLibraryPermission(presentedViewController: presentingViewController)
uploadController.start() }
}
}
} else {
DispatchQueue.main.async { uploadController.start() }
}

return uploadController
}

/// Lists the content of a cloud provider at a given path. Results are paginated (see `pageToken` below.)
///
/// - Parameter provider: The cloud provider to use (e.g. Dropbox, GoogleDrive, S3)
Expand All @@ -234,15 +250,15 @@ private typealias CompletionHandler = (_ response: CloudResponse, _ safariError:
guard let authCallbackURL = authCallbackURL else {
fatalError("Please make sure your config's `callbackURLScheme` is present.")
}

let request = FolderListRequest(authCallbackURL: authCallbackURL,
apiKey: apiKey,
security: security,
token: lastToken,
pageToken: pageToken,
provider: provider,
path: path)

perform(request: request) { response, safariError in
switch (response, safariError) {
case (let response as FolderListResponse, nil):
Expand All @@ -251,10 +267,10 @@ private typealias CompletionHandler = (_ response: CloudResponse, _ safariError:
completionHandler(FolderListResponse(error: error))
}
}

return request
}

/// Stores a file from a given cloud provider and path at the desired store location.
///
/// - Parameter provider: The cloud provider to use (e.g. Dropbox, GoogleDrive, S3)
Expand All @@ -279,33 +295,33 @@ private typealias CompletionHandler = (_ response: CloudResponse, _ safariError:
provider: provider,
path: path,
storeOptions: storeOptions)

perform(request: request) { response, _ in
guard let response = response as? StoreResponse else { return }
completionHandler(response)
}

return request
}

/// Logs out the user from a given provider.
///
/// - Parameter provider: The `CloudProvider` to logout from.
/// - Parameter completionHandler: Adds a handler to be called once the request has completed. The response will
/// either contain an error (on failure) or nothing at all (on success.)
@objc public func logout(provider: CloudProvider, completionHandler: @escaping LogoutCompletionHandler) {
guard let token = lastToken else { return }

let logoutRequest = LogoutRequest(provider: provider, apiKey: apiKey, token: token)

logoutRequest.perform(cloudService: cloudService, completionBlock: completionHandler)
}

// MARK: - Internal Functions

func prefetch(completionBlock: @escaping PrefetchCompletionHandler) {
let prefetchRequest = PrefetchRequest(apiKey: apiKey)

prefetchRequest.perform(cloudService: cloudService, completionBlock: completionBlock)
}
}
Expand All @@ -320,19 +336,19 @@ private extension Client {
// Store last token
self.lastToken = token
}

guard let authURL = response.authURL else {
// Already authenticated, call completion block and return.
completionBlock(response, nil)
return
}

// Request authentication.
let session = SFAuthenticationSession(url: authURL,
callbackURLScheme: self.config.callbackURLScheme) { url, error in
// Remove strong reference, so object can be deallocated.
self.safariAuthSession = nil

if let safariError = error {
completionBlock(response, safariError)
} else if let url = url, url == self.authCallbackURL {
Expand All @@ -341,10 +357,10 @@ private extension Client {
completionBlock(response, ClientError.authenticationFailed)
}
}

// Keep a strong reference to the auth session.
self.safariAuthSession = session

DispatchQueue.main.async {
session.start()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ final class MonitorViewController: UIViewController {
let button = UIButton()

button.setTitle("Cancel", for: .normal)
button.setTitleColor(.systemRed, for: .normal)
button.tintColor = .systemRed
button.addTarget(self, action: #selector(cancel), for: .touchUpInside)

return button
Expand All @@ -66,8 +68,15 @@ final class MonitorViewController: UIViewController {

required init(progressable: Cancellable & Monitorizable) {
self.progressable = progressable

super.init(nibName: nil, bundle: nil)
let frameworkBundle = Bundle(for: Self.self)
let bundleURL = frameworkBundle.resourceURL?.appendingPathComponent("Filestack.bundle")
let resourceBundle = Bundle(url: bundleURL!)
#if SWIFT_PACKAGE
super.init(nibName: nil, bundle: Bundle.module)
#else
super.init(nibName: nil, bundle: resourceBundle)
#endif

}

required init?(coder: NSCoder) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// PhotoLibraryPermission.swift
//
//
// Created by Sergii Polishchuk on 31.08.2022.
//

import UIKit
import FilestackSDK
import Foundation

class PhotoLibraryPermission: NSObject, Cancellable, Monitorizable, Startable {
let presentedViewController: UIViewController

private let trackingProgress = TrackingProgress()
var progress: Progress { trackingProgress }

init(presentedViewController: UIViewController) {
self.presentedViewController = presentedViewController
}

/// Add `Startable` conformance.
@discardableResult
func start() -> Bool {
let alert = UIAlertController(title: "Permission Needed",
message: "The application does not have the permission to access your photos or files. Please change it in Settings",
preferredStyle: .alert)

alert.addAction(UIAlertAction(title: "Settings", style: .default, handler: { _ in
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))

presentedViewController.present(alert, animated: true)
return true
}

/// Add `Cancellable` conformance.
@discardableResult
func cancel() -> Bool {
return true
}
}
Loading