├── .gitignore ├── .swift-version ├── .swiftformat ├── .travis.yml ├── CHANGELOG.md ├── Demo ├── FilestackDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── FilestackDemo.xcscheme └── FilestackDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── AppIcon.png │ │ ├── AppIcon_iPadApp_76.png │ │ ├── AppIcon_iPadApp_76@2x.png │ │ ├── AppIcon_iPadProApp_83.5@2x.png │ │ ├── AppIcon_iPadSettings_29.png │ │ ├── AppIcon_iPadSettings_29@2x.png │ │ ├── AppIcon_iPadSpotlight_40.png │ │ ├── AppIcon_iPadSpotlight_40@2x.png │ │ ├── AppIcon_iPhoneApp_60@2x.png │ │ ├── AppIcon_iPhoneApp_60@3x.png │ │ ├── AppIcon_iPhoneSettings_29.png │ │ ├── AppIcon_iPhoneSettings_29@2x.png │ │ ├── AppIcon_iPhoneSettings_29@3x.png │ │ ├── AppIcon_iPhoneSpotlight_40@2x.png │ │ ├── AppIcon_iPhoneSpotlight_40@3x.png │ │ └── Contents.json │ ├── Contents.json │ └── icon-custom-source.imageset │ │ ├── Contents.json │ │ ├── icon-unsplash.png │ │ └── icon-unsplash@2x.png │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Custom Source Providers │ ├── Cells │ │ └── CustomCell.swift │ └── View Controllers │ │ └── MyCustomSourceProvider.swift │ ├── Demo Content │ ├── demo1.jpg │ ├── demo2.jpg │ ├── demo3.jpg │ ├── demo4.jpg │ └── demo5.jpg │ ├── Extensions │ └── UIViewController+PresentAlert.swift │ ├── Info.plist │ └── ViewController.swift ├── Filestack.podspec ├── Filestack.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ ├── WorkspaceSettings.xcsettings │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ └── Filestack.xcscheme ├── Filestack.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ ├── WorkspaceSettings.xcsettings │ └── swiftpm │ └── Package.resolved ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── Filestack │ ├── Filestack.h │ ├── Info.plist │ ├── Internal │ ├── Bundle.swift │ ├── Constants.swift │ ├── Extensions │ │ ├── AVAsset+Export.swift │ │ ├── Array+SafeIndex.swift │ │ ├── Data+JSON.swift │ │ ├── ImageEdition │ │ │ ├── CGPoint+Distance.swift │ │ │ ├── CGRect+Scale.swift │ │ │ ├── UIImage+CIImageTransformations.swift │ │ │ ├── UIImage+Rect.swift │ │ │ └── UIImage+Sanitize.swift │ │ ├── ImageURLExportPreset+asImagePickerControllerImageURLExportPreset.swift │ │ ├── Math+Clamp.swift │ │ ├── String+UTI.swift │ │ ├── UICollectionView+Reusable.swift │ │ ├── UIColor+Bundle.swift │ │ ├── UIColor+Predefined.swift │ │ ├── UIImage+Bundle.swift │ │ ├── UIImage+Export.swift │ │ ├── UIImage+HEIC.swift │ │ ├── UIImage+Resized.swift │ │ ├── UIImage+Write.swift │ │ ├── UIView+Constraints.swift │ │ ├── URL+Copy.swift │ │ ├── URL+IsDirectory.swift │ │ ├── URL+Move.swift │ │ └── URLSession+FilestackDefault.swift │ ├── Extractors │ │ ├── URLExtractor.swift │ │ └── UploadableExtractor.swift │ ├── Formatters │ │ └── DurationNumberFormatter.swift │ ├── Operations │ │ └── AssetURLExtractorOperation.swift │ ├── Protocols │ │ └── CloudRequest.swift │ ├── Requests │ │ ├── FolderListRequest.swift │ │ ├── LogoutRequest.swift │ │ ├── PrefetchRequest.swift │ │ └── StoreRequest.swift │ ├── Services │ │ └── CloudService.swift │ └── TrackingProgress.swift │ ├── Public │ ├── CompletionHandlers.swift │ ├── Enums │ │ ├── ClientError.swift │ │ ├── CloudProvider.swift │ │ ├── ImageURLExportPreset.swift │ │ ├── PhotosPickerFilter.swift │ │ └── PickerBehavior.swift │ ├── Extensions │ │ ├── Client+Deprecated.swift │ │ └── Client+ObjC.swift │ ├── Models │ │ ├── Client.swift │ │ └── Config.swift │ └── Responses │ │ ├── CloudResponse.swift │ │ ├── FolderListResponse.swift │ │ ├── LogoutResponse.swift │ │ ├── PrefetchResponse.swift │ │ └── StoreResponse.swift │ ├── Resources │ ├── Colors.xcassets │ │ ├── Contents.json │ │ └── SelectionCellBorderColor.colorset │ │ │ └── Contents.json │ ├── Icons.xcassets │ │ ├── Contents.json │ │ ├── clear-pattern.imageset │ │ │ ├── Contents.json │ │ │ └── clear-pattern.png │ │ ├── file.imageset │ │ │ ├── Contents.json │ │ │ ├── file.png │ │ │ └── file@2x.png │ │ ├── icon-box.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-box.png │ │ │ └── icon-box@2x.png │ │ ├── icon-camera.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-camera.png │ │ │ └── icon-camera@2x.png │ │ ├── icon-circle.imageset │ │ │ ├── Contents.json │ │ │ └── circle_icon.png │ │ ├── icon-clouddrive.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-clouddrive.png │ │ │ └── icon-clouddrive@2x.png │ │ ├── icon-crop.imageset │ │ │ ├── Contents.json │ │ │ └── crop_icon.png │ │ ├── icon-customsource.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-customsource.png │ │ │ └── icon-customsource@2x.png │ │ ├── icon-documents.imageset │ │ │ ├── Contents.json │ │ │ ├── documents.png │ │ │ └── documents@2x.png │ │ ├── icon-dropbox.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-dropbox.png │ │ │ └── icon-dropbox@2x.png │ │ ├── icon-edit.imageset │ │ │ ├── Contents.json │ │ │ └── icon-pencil.png │ │ ├── icon-facebook.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-facebook.png │ │ │ └── icon-facebook@2x.png │ │ ├── icon-file-image.imageset │ │ │ ├── Contents.json │ │ │ └── icon-image.png │ │ ├── icon-file-pdf.imageset │ │ │ ├── Contents.json │ │ │ └── icon-file-pdf.png │ │ ├── icon-file-unknown.imageset │ │ │ ├── Contents.json │ │ │ └── icon-file-unknown.png │ │ ├── icon-file-video.imageset │ │ │ ├── Contents.json │ │ │ └── icon-video-camera.png │ │ ├── icon-github.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-github.png │ │ │ └── icon-github@2x.png │ │ ├── icon-gmail.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-gmail.png │ │ │ └── icon-gmail@2x.png │ │ ├── icon-googledrive.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-googledrive.png │ │ │ └── icon-googledrive@2x.png │ │ ├── icon-grid.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-grid.png │ │ │ ├── icon-grid@2x.png │ │ │ └── icon-grid@3x.png │ │ ├── icon-image.imageset │ │ │ ├── Contents.json │ │ │ └── icon-image.png │ │ ├── icon-instagram.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-instagram.png │ │ │ └── icon-instagram@2x.png │ │ ├── icon-list.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-list.png │ │ │ ├── icon-list@2x.png │ │ │ └── icon-list@3x.png │ │ ├── icon-logout.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-logout.png │ │ │ ├── icon-logout@2x.png │ │ │ └── icon-logout@3x.png │ │ ├── icon-onedrive.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-skydrive.png │ │ │ └── icon-skydrive@2x.png │ │ ├── icon-photolibrary.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-albums.png │ │ │ └── icon-albums@2x.png │ │ ├── icon-picasa.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-picasa.png │ │ │ └── icon-picasa@2x.png │ │ ├── icon-redo.imageset │ │ │ ├── Contents.json │ │ │ └── redo_icon.png │ │ ├── icon-rotate.imageset │ │ │ ├── Contents.json │ │ │ └── rotate_icon.png │ │ ├── icon-selected.imageset │ │ │ ├── Contents.json │ │ │ ├── photoIcon-1.png │ │ │ ├── photoIcon-2.png │ │ │ └── photoIcon.png │ │ ├── icon-tick.imageset │ │ │ ├── Contents.json │ │ │ └── checkmark-solid.png │ │ ├── icon-undo.imageset │ │ │ ├── Contents.json │ │ │ └── undo_icon.png │ │ ├── icon-unsplash.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-unsplash.png │ │ │ └── icon-unsplash@2x.png │ │ ├── icon-upload.imageset │ │ │ ├── Contents.json │ │ │ └── icon_upload.png │ │ └── placeholder.imageset │ │ │ ├── Contents.json │ │ │ └── placeholder.png │ ├── PhotoPicker.storyboard │ └── Picker.storyboard │ ├── UI │ ├── Internal │ │ ├── Collection View Cells │ │ │ ├── ActivityIndicatorCollectionViewCell.swift │ │ │ └── CloudItemCollectionViewCell.swift │ │ ├── Controllers │ │ │ ├── CloudSourceCollectionViewController.swift │ │ │ ├── CloudSourceTabBarController.swift │ │ │ ├── CloudSourceTableViewController.swift │ │ │ ├── CustomPickerUploadController.swift │ │ │ ├── DocumentPickerUploadController.swift │ │ │ ├── ImagePickerUploadController.swift │ │ │ ├── MonitorViewController.swift │ │ │ ├── SourceTableViewController.swift │ │ │ └── URLPickerUploadController.swift │ │ ├── Enums │ │ │ └── CloudSourceViewType.swift │ │ ├── Extensions │ │ │ ├── Scene+Defaults.swift │ │ │ ├── Storyboard+Scenes.swift │ │ │ ├── UIImage+Squared.swift │ │ │ └── UserDefaults+State.swift │ │ ├── FlowLayouts │ │ │ └── CollectionViewFlowLayout.swift │ │ ├── Models │ │ │ └── CloudItem.swift │ │ ├── PhotoEditor │ │ │ ├── EditionController │ │ │ │ ├── EditorToolbar.swift │ │ │ │ ├── Enums │ │ │ │ │ └── ImageEditorCommand.swift │ │ │ │ ├── Handlers │ │ │ │ │ ├── CircleGesturesHandler.swift │ │ │ │ │ └── CropGesturesHandler.swift │ │ │ │ ├── ImageEditor.swift │ │ │ │ ├── Layers │ │ │ │ │ ├── CircleLayer.swift │ │ │ │ │ └── CropLayer.swift │ │ │ │ ├── ViewController │ │ │ │ │ ├── EditorViewController+EditDataSource.swift │ │ │ │ │ ├── EditorViewController+ToolbarDelegate.swift │ │ │ │ │ ├── EditorViewController+ViewSetup.swift │ │ │ │ │ └── EditorViewController.swift │ │ │ │ └── Views │ │ │ │ │ └── ImageEditorView.swift │ │ │ └── SelectionList │ │ │ │ ├── SelectableElement.swift │ │ │ │ ├── SelectionCell.swift │ │ │ │ ├── SelectionListViewController+FlowLayout.swift │ │ │ │ ├── SelectionListViewController+UICollectionView.swift │ │ │ │ ├── SelectionListViewController.swift │ │ │ │ └── Uploadable.swift │ │ ├── PhotoPicker │ │ │ ├── AlbumList │ │ │ │ ├── AlbumCell.swift │ │ │ │ └── AlbumListViewController.swift │ │ │ ├── AssetCollection │ │ │ │ ├── AssetCell.swift │ │ │ │ └── AssetCollectionViewController.swift │ │ │ ├── PhotoAlbumRepository.swift │ │ │ ├── PhotoPickerController.swift │ │ │ └── PhotosExtensions.swift │ │ ├── Protocols │ │ │ ├── CellDescriptibleSource.swift │ │ │ ├── CloudSourceDataSource.swift │ │ │ ├── CloudSourceDataSourceConsumer.swift │ │ │ └── Scene.swift │ │ ├── Scenes │ │ │ ├── CloudSourceBarTabScene.swift │ │ │ └── PickerNavigationScene.swift │ │ └── Table View Cells │ │ │ ├── ActivityIndicatorTableViewCell.swift │ │ │ └── CloudItemTableViewCell.swift │ └── Public │ │ ├── Controllers │ │ └── PickerNavigationController.swift │ │ ├── Enums │ │ └── LocalProvider.swift │ │ ├── Models │ │ ├── CloudSource.swift │ │ ├── LocalSource.swift │ │ └── Stylizer.swift │ │ └── Protocols │ │ ├── SourceProvider.swift │ │ ├── SourceProviderDelegate.swift │ │ └── StylizerDelegate.swift │ └── VERSION ├── VERSION └── bin ├── apply-swiftformat.sh ├── deploy-docs.sh ├── generate-and-deploy-docs.sh ├── generate-docs.sh ├── set-buildnumber.sh └── set-version.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | .swiftpm/xcode 20 | 21 | ## Other 22 | *.xccheckout 23 | *.moved-aside 24 | *.xcuserstate 25 | *.xcscmblueprint 26 | .idea 27 | compile_commands.json 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | 33 | # Swift Package Manager 34 | # 35 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 36 | /.build 37 | /Packages 38 | 39 | # CocoaPods 40 | # 41 | # We recommend against adding the Pods directory to your .gitignore. However 42 | # you should judge for yourself, the pros and cons are mentioned at: 43 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 44 | # 45 | Pods/ 46 | 47 | # Carthage 48 | # 49 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 50 | # Carthage/Checkouts 51 | 52 | Carthage/ 53 | # Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | 65 | .DS_Store 66 | 67 | # Jazzy Docs 68 | docs/ 69 | 70 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | # file options 2 | 3 | --exclude Carthage 4 | 5 | # format options 6 | 7 | --allman false 8 | --binarygrouping 4,8 9 | --commas always 10 | --comments indent 11 | --decimalgrouping 3,6 12 | --elseposition same-line 13 | --empty void 14 | --exponentcase lowercase 15 | --exponentgrouping disabled 16 | --fractiongrouping disabled 17 | --header ignore 18 | --hexgrouping 4,8 19 | --hexliteralcase uppercase 20 | --ifdef indent 21 | --indent 4 22 | --indentcase false 23 | --importgrouping testable-bottom 24 | --linebreaks lf 25 | --octalgrouping 4,8 26 | --operatorfunc spaced 27 | --patternlet hoist 28 | --ranges spaced 29 | --self remove 30 | --semicolons inline 31 | --stripunusedargs always 32 | --trimwhitespace always 33 | --wraparguments preserve 34 | --wrapcollections preserve 35 | 36 | # rules 37 | 38 | --enable isEmpty 39 | --enable modifierOrder 40 | --enable spaceInsideComments 41 | --enable linebreakAtEndOfFile 42 | --enable typeSugar 43 | --disable redundantSelf -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9 3 | branches: 4 | only: 5 | - ios-sdk 6 | env: 7 | global: 8 | - LC_CTYPE=en_US.UTF-8 9 | - LANG=en_US.UTF-8 10 | - IOS_FRAMEWORK_SCHEME="Filestack" 11 | - NSUnbufferedIO=YES 12 | matrix: 13 | - DESTINATION="OS=11.0,name=iPhone 8 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" 14 | - DESTINATION="OS=10.3.1,name=iPhone 7 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" 15 | - DESTINATION="OS=9.3,name=iPhone 6" SCHEME="$IOS_FRAMEWORK_SCHEME" 16 | - DESTINATION="OS=9.0,name=iPhone 5" SCHEME="$IOS_FRAMEWORK_SCHEME" 17 | before_install: 18 | - brew update 19 | - brew outdated carthage || brew upgrade carthage 20 | - brew outdated xctool || brew upgrade xctool 21 | - carthage bootstrap --no-build --use-submodules --verbose 22 | script: 23 | - set -o pipefail 24 | - xcodebuild -version 25 | - xcodebuild -showsdks 26 | - carthage version 27 | 28 | # Build Framework in Debug and Run Tests 29 | - xcodebuild -scheme "$SCHEME" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES clean build build-for-testing | xcpretty; 30 | - travis_retry xcodebuild -scheme "$SCHEME" -destination "$DESTINATION" -configuration Debug test-without-building | xcpretty; 31 | 32 | after_success: 33 | - sleep 5 34 | -------------------------------------------------------------------------------- /Demo/FilestackDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/FilestackDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/FilestackDemo.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Demo/FilestackDemo.xcodeproj/xcshareddata/xcschemes/FilestackDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadApp_76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadApp_76.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadApp_76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadApp_76@2x.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadProApp_83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadProApp_83.5@2x.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadSettings_29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadSettings_29.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadSettings_29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadSettings_29@2x.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadSpotlight_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadSpotlight_40.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadSpotlight_40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPadSpotlight_40@2x.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneApp_60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneApp_60@2x.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneApp_60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneApp_60@3x.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneSettings_29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneSettings_29.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneSettings_29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneSettings_29@2x.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneSettings_29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneSettings_29@3x.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneSpotlight_40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneSpotlight_40@2x.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneSpotlight_40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/AppIcon_iPhoneSpotlight_40@3x.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "AppIcon_iPhoneSettings_29.png", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "AppIcon_iPhoneSettings_29@2x.png", 23 | "scale" : "2x" 24 | }, 25 | { 26 | "size" : "29x29", 27 | "idiom" : "iphone", 28 | "filename" : "AppIcon_iPhoneSettings_29@3x.png", 29 | "scale" : "3x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "AppIcon_iPhoneSpotlight_40@2x.png", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "size" : "40x40", 39 | "idiom" : "iphone", 40 | "filename" : "AppIcon_iPhoneSpotlight_40@3x.png", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "AppIcon_iPhoneApp_60@2x.png", 47 | "scale" : "2x" 48 | }, 49 | { 50 | "size" : "60x60", 51 | "idiom" : "iphone", 52 | "filename" : "AppIcon_iPhoneApp_60@3x.png", 53 | "scale" : "3x" 54 | }, 55 | { 56 | "idiom" : "ipad", 57 | "size" : "20x20", 58 | "scale" : "1x" 59 | }, 60 | { 61 | "idiom" : "ipad", 62 | "size" : "20x20", 63 | "scale" : "2x" 64 | }, 65 | { 66 | "size" : "29x29", 67 | "idiom" : "ipad", 68 | "filename" : "AppIcon_iPadSettings_29.png", 69 | "scale" : "1x" 70 | }, 71 | { 72 | "size" : "29x29", 73 | "idiom" : "ipad", 74 | "filename" : "AppIcon_iPadSettings_29@2x.png", 75 | "scale" : "2x" 76 | }, 77 | { 78 | "size" : "40x40", 79 | "idiom" : "ipad", 80 | "filename" : "AppIcon_iPadSpotlight_40.png", 81 | "scale" : "1x" 82 | }, 83 | { 84 | "size" : "40x40", 85 | "idiom" : "ipad", 86 | "filename" : "AppIcon_iPadSpotlight_40@2x.png", 87 | "scale" : "2x" 88 | }, 89 | { 90 | "size" : "76x76", 91 | "idiom" : "ipad", 92 | "filename" : "AppIcon_iPadApp_76.png", 93 | "scale" : "1x" 94 | }, 95 | { 96 | "size" : "76x76", 97 | "idiom" : "ipad", 98 | "filename" : "AppIcon_iPadApp_76@2x.png", 99 | "scale" : "2x" 100 | }, 101 | { 102 | "size" : "83.5x83.5", 103 | "idiom" : "ipad", 104 | "filename" : "AppIcon_iPadProApp_83.5@2x.png", 105 | "scale" : "2x" 106 | }, 107 | { 108 | "size" : "1024x1024", 109 | "idiom" : "ios-marketing", 110 | "filename" : "AppIcon.png", 111 | "scale" : "1x" 112 | } 113 | ], 114 | "info" : { 115 | "version" : 1, 116 | "author" : "xcode" 117 | } 118 | } -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/icon-custom-source.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon-unsplash.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "icon-unsplash@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | }, 22 | "properties" : { 23 | "template-rendering-intent" : "template" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/icon-custom-source.imageset/icon-unsplash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/icon-custom-source.imageset/icon-unsplash.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Assets.xcassets/icon-custom-source.imageset/icon-unsplash@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Assets.xcassets/icon-custom-source.imageset/icon-unsplash@2x.png -------------------------------------------------------------------------------- /Demo/FilestackDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Demo/FilestackDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Demo/FilestackDemo/Custom Source Providers/Cells/CustomCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomCell.swift 3 | // CustomCell 4 | // 5 | // Created by Ruben Nine on 5/8/21. 6 | // Copyright © 2021 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CustomCell: UICollectionViewCell { 12 | let imageView = UIImageView() 13 | 14 | override init(frame: CGRect) { 15 | super.init(frame: frame) 16 | setupView() 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | } 23 | 24 | extension CustomCell { 25 | func setupView() { 26 | selectedBackgroundView = UIView() 27 | selectedBackgroundView?.backgroundColor = UIColor.systemOrange.withAlphaComponent(0.33) 28 | selectedBackgroundView?.layer.cornerRadius = 9 29 | selectedBackgroundView?.clipsToBounds = true 30 | selectedBackgroundView?.layer.borderColor = UIColor.white.cgColor 31 | selectedBackgroundView?.layer.borderWidth = 2 32 | 33 | imageView.translatesAutoresizingMaskIntoConstraints = false 34 | imageView.layer.cornerRadius = 9 35 | imageView.contentMode = .scaleAspectFit 36 | imageView.clipsToBounds = true 37 | 38 | let backgroundView = UIView() 39 | backgroundView.addSubview(imageView) 40 | 41 | imageView.topAnchor.constraint(equalTo: backgroundView.topAnchor).isActive = true 42 | imageView.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor).isActive = true 43 | imageView.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor).isActive = true 44 | imageView.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor).isActive = true 45 | 46 | self.backgroundView = backgroundView 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Demo/FilestackDemo/Demo Content/demo1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Demo Content/demo1.jpg -------------------------------------------------------------------------------- /Demo/FilestackDemo/Demo Content/demo2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Demo Content/demo2.jpg -------------------------------------------------------------------------------- /Demo/FilestackDemo/Demo Content/demo3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Demo Content/demo3.jpg -------------------------------------------------------------------------------- /Demo/FilestackDemo/Demo Content/demo4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Demo Content/demo4.jpg -------------------------------------------------------------------------------- /Demo/FilestackDemo/Demo Content/demo5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Demo/FilestackDemo/Demo Content/demo5.jpg -------------------------------------------------------------------------------- /Demo/FilestackDemo/Extensions/UIViewController+PresentAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+PresentAlert.swift 3 | // FilestackDemo 4 | // 5 | // Created by Ruben Nine on 16/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIViewController { 12 | func presentAlert(titled title: String, message: String) { 13 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 14 | alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) 15 | 16 | self.present(alert, animated: false, completion: nil) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demo/FilestackDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleURLTypes 20 | 21 | 22 | CFBundleURLName 23 | com.filestack.FilestackDemo 24 | CFBundleURLSchemes 25 | 26 | filestackdemo 27 | 28 | 29 | 30 | CFBundleVersion 31 | 1 32 | LSRequiresIPhoneOS 33 | 34 | NSCameraUsageDescription 35 | The demo needs access to the camera. 36 | NSMicrophoneUsageDescription 37 | The demo needs access to the microphone for video recording. 38 | NSPhotoLibraryUsageDescription 39 | This demo needs access to the photo library in order to be able to pick pictures from it. 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIMainStoryboardFile 43 | Main 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UISupportedInterfaceOrientations~ipad 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationPortraitUpsideDown 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationLandscapeRight 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /Filestack.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'Filestack' 3 | spec.version = File.read('./VERSION') 4 | spec.license = { :type => 'Apache License, Version 2.0"', :file => "LICENSE" } 5 | spec.homepage = 'https://github.com/filestack/filestack-ios' 6 | spec.authors = { 'Filestack' => 'ios@filestack.com' } 7 | spec.summary = 'Official iOS SDK for Filestack.' 8 | spec.source = { :git => 'https://github.com/filestack/filestack-ios.git', :tag => spec.version } 9 | 10 | spec.ios.deployment_target = '14.0' 11 | 12 | spec.source_files = 'Sources/Filestack/**/*.{h,swift}' 13 | spec.resources = ["Sources/Filestack/Resources/*.{storyboard,xcassets}"] 14 | spec.public_header_files = 'Sources/**/*.h' 15 | 16 | spec.swift_versions = [4.2, 5.2] 17 | 18 | spec.dependency 'FilestackSDK', '~> 2.8.0' 19 | spec.dependency 'ZIPFoundation', '0.9.19' 20 | end 21 | -------------------------------------------------------------------------------- /Filestack.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Filestack.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Filestack.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Filestack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "FilestackSDK", 6 | "repositoryURL": "https://github.com/filestack/filestack-swift.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "5ba0ac9ca2fb53ccf18335b1654b8cc600785b66", 10 | "version": "2.8.0" 11 | } 12 | }, 13 | { 14 | "package": "OHHTTPStubs", 15 | "repositoryURL": "https://github.com/AliSoftware/OHHTTPStubs.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "e92b5a5746ef16add2a1424f1fc19529d9a75cde", 19 | "version": "9.0.0" 20 | } 21 | }, 22 | { 23 | "package": "ZIPFoundation", 24 | "repositoryURL": "https://github.com/weichsel/ZIPFoundation", 25 | "state": { 26 | "branch": null, 27 | "revision": "cf10bbff6ac3b873e97b36b9784c79866a051a8e", 28 | "version": "0.9.12" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /Filestack.xcodeproj/xcshareddata/xcschemes/Filestack.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 53 | 59 | 60 | 61 | 62 | 68 | 69 | 75 | 76 | 77 | 78 | 80 | 81 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /Filestack.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Filestack.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Filestack.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Filestack.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "FilestackSDK", 6 | "repositoryURL": "https://github.com/filestack/filestack-swift.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "5ba0ac9ca2fb53ccf18335b1654b8cc600785b66", 10 | "version": "2.8.0" 11 | } 12 | }, 13 | { 14 | "package": "OHHTTPStubs", 15 | "repositoryURL": "https://github.com/AliSoftware/OHHTTPStubs.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "12f19662426d0434d6c330c6974d53e2eb10ecd9", 19 | "version": "9.1.0" 20 | } 21 | }, 22 | { 23 | "package": "ZIPFoundation", 24 | "repositoryURL": "https://github.com/weichsel/ZIPFoundation", 25 | "state": { 26 | "branch": null, 27 | "revision": "cf10bbff6ac3b873e97b36b9784c79866a051a8e", 28 | "version": "0.9.12" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015–2019 Filestack (https://www.filestack.com/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "FilestackSDK", 6 | "repositoryURL": "https://github.com/filestack/filestack-swift", 7 | "state": { 8 | "branch": null, 9 | "revision": "5ba0ac9ca2fb53ccf18335b1654b8cc600785b66", 10 | "version": "2.8.0" 11 | } 12 | }, 13 | { 14 | "package": "ZIPFoundation", 15 | "repositoryURL": "https://github.com/weichsel/ZIPFoundation.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "cf10bbff6ac3b873e97b36b9784c79866a051a8e", 19 | "version": "0.9.17" 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Filestack", 8 | platforms: [.iOS(.v11)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, and make them visible to other packages. 11 | .library( 12 | name: "Filestack", 13 | targets: ["Filestack"] 14 | ), 15 | ], 16 | dependencies: [ 17 | .package(name: "FilestackSDK", url: "https://github.com/filestack/filestack-swift", .upToNextMajor(from: Version(2, 7, 0))), 18 | .package(url: "https://github.com/weichsel/ZIPFoundation.git", .upToNextMajor(from: Version(0, 9, 0))) 19 | ], 20 | targets: [ 21 | .target( 22 | name: "Filestack", 23 | dependencies: ["FilestackSDK", "ZIPFoundation"], 24 | exclude: ["Filestack.h", "Info.plist"], 25 | resources: [ 26 | .copy("VERSION") 27 | ] 28 | ) 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /Sources/Filestack/Filestack.h: -------------------------------------------------------------------------------- 1 | // 2 | // Filestack.h 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 10/18/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Filestack. 12 | FOUNDATION_EXPORT double FilestackVersionNumber; 13 | 14 | //! Project version string for Filestack. 15 | FOUNDATION_EXPORT const unsigned char FilestackVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/Filestack/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Bundle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 13/10/2020. 6 | // Copyright © 2020 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | private class BundleFinder {} 12 | 13 | /// Returns the bundle that is associated to this module (supports SPM.) 14 | let bundle: Bundle = { 15 | #if SWIFT_PACKAGE 16 | return Bundle.module 17 | #else 18 | return Bundle(for: BundleFinder.self) 19 | #endif 20 | }() 21 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 10/24/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Constants { 12 | static let cloudURL = URL(string: "https://cloud.filestackapi.com")! 13 | static let validHTTPResponseCodes = Array(200 ..< 300) 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/AVAsset+Export.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AVAsset+Export.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 7/11/19. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import AVKit 10 | 11 | extension AVAsset { 12 | func videoExportSession(using preset: String) -> AVAssetExportSession? { 13 | let export = AVAssetExportSession(asset: self, presetName: preferredVideoPreset(using: preset)) 14 | let tempDirURL = FileManager.default.temporaryDirectory 15 | 16 | export?.outputURL = tempDirURL.appendingPathComponent(UUID().uuidString).appendingPathExtension("mov") 17 | export?.outputFileType = .mov 18 | 19 | return export 20 | } 21 | 22 | private func preferredVideoPreset(using preset: String) -> String { 23 | let compatiblePresets = AVAssetExportSession.exportPresets(compatibleWith: self) 24 | 25 | if compatiblePresets.contains(preset) { 26 | return preset 27 | } 28 | 29 | return AVAssetExportPresetPassthrough 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/Array+SafeIndex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+SafeIndex.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/13/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array { 12 | // Originally written by Erica Sadun, and Mike Ash 13 | // Source: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/ 14 | subscript(safe index: UInt) -> Element? { 15 | return Int(index) < count ? self[Int(index)] : nil 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/Data+JSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+JSON.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/8/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Data { 12 | func parseJSON() -> [String: Any]? { 13 | return (try? JSONSerialization.jsonObject(with: self)) as? [String: Any] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/ImageEdition/CGPoint+Distance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint+Distance.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 09/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension CGPoint { 12 | enum Metric { 13 | case euclidean 14 | case manhattan 15 | case maximum 16 | } 17 | 18 | func distance(to point: CGPoint, metric: Metric = .euclidean) -> CGFloat { 19 | switch metric { 20 | case .euclidean: return euclideanDistance(to: point) 21 | case .manhattan: return manhattanDistance(to: point) 22 | case .maximum: return maximumDistance(to: point) 23 | } 24 | } 25 | 26 | func euclideanDistance(to point: CGPoint) -> CGFloat { 27 | return sqrt(pow(x - point.x, 2) + pow(y - point.y, 2)) 28 | } 29 | 30 | func manhattanDistance(to point: CGPoint) -> CGFloat { 31 | return abs(x - point.x) + abs(y - point.y) 32 | } 33 | 34 | func maximumDistance(to point: CGPoint) -> CGFloat { 35 | return max(abs(x - point.x), abs(y - point.y)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/ImageEdition/CGRect+Scale.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect+Scale.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 02/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension CGRect { 12 | func scaled(by scale: CGFloat) -> CGRect { 13 | return applying(CGAffineTransform(scaleX: scale, y: scale)) 14 | } 15 | } 16 | 17 | extension CGPoint { 18 | func movedBy(x: CGFloat = 0, y: CGFloat = 0) -> CGPoint { 19 | return applying(CGAffineTransform(translationX: x, y: y)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/ImageEdition/UIImage+Rect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Rect.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 02/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | var cgRect: CGRect { 13 | return CGRect(origin: .zero, size: size) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/ImageEdition/UIImage+Sanitize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageEditor+Sanitize.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 28/06/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: Sanitize 12 | 13 | extension UIImage { 14 | var sanitized: UIImage? { 15 | guard 16 | let imageRef = cgImage, 17 | let colorSpace = imageRef.colorSpace, 18 | let context = CGContext(data: nil, 19 | width: imageRef.width, 20 | height: imageRef.height, 21 | bitsPerComponent: imageRef.bitsPerComponent, 22 | bytesPerRow: imageRef.bytesPerRow, 23 | space: colorSpace, 24 | bitmapInfo: imageRef.bitmapInfo.rawValue) else { return nil } 25 | context.draw(imageRef, in: CGRect(x: 0, y: 0, width: CGFloat(imageRef.width), height: CGFloat(imageRef.height))) 26 | guard let sanitizedRef = context.makeImage() else { return nil } 27 | return UIImage(cgImage: sanitizedRef, scale: scale, orientation: .up) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/ImageURLExportPreset+asImagePickerControllerImageURLExportPreset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageURLExportPreset+asImagePickerControllerImageURLExportPreset.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension ImageURLExportPreset { 12 | var asImagePickerControllerImageURLExportPreset: UIImagePickerController.ImageURLExportPreset { 13 | switch self { 14 | case .compatible: 15 | return .compatible 16 | case .current: 17 | return .current 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/Math+Clamp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Math+Clamp.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 10/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func clamp(_ element: T, min minimum: T, max maximum: T) -> T where T: Comparable { 12 | return min(maximum, max(element, minimum)) 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/String+UTI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+UTI.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 7/1/19. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MobileCoreServices.UTType 11 | 12 | extension String { 13 | var UTI: CFString? { 14 | var ext = (self as NSString).pathExtension 15 | 16 | if ext.isEmpty { 17 | ext = "txt" 18 | } 19 | 20 | guard let utiRef = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext as CFString, nil) else { return nil } 21 | 22 | let uti = utiRef.takeUnretainedValue() 23 | utiRef.release() 24 | 25 | return uti 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/UICollectionView+Reusable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+Reusable.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 26/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private extension UICollectionViewCell { 12 | class var reuseIdentifier: String { 13 | return String(describing: self) 14 | } 15 | } 16 | 17 | extension UICollectionView { 18 | func register(_ cell: Cell.Type) { 19 | register(cell, forCellWithReuseIdentifier: cell.reuseIdentifier) 20 | } 21 | 22 | func reuse(_ cell: Cell.Type, for indexPath: IndexPath) -> Cell? { 23 | let reusable = dequeueReusableCell(withReuseIdentifier: cell.reuseIdentifier, for: indexPath) 24 | return reusable as? Cell 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/UIColor+Bundle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Bundle.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 17/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | static func fromFilestackBundle(_ name: String) -> UIColor? { 13 | return UIColor(named: name, in: bundle, compatibleWith: nil) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/UIColor+Predefined.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+predefined.swift 3 | // Filestack 4 | // 5 | // Created by Mihály Papp on 10/08/2018. 6 | // Copyright © 2018 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | static var appleBlue: UIColor { 13 | return UIColor(red: 0, green: 122 / 255, blue: 1, alpha: 1) 14 | } 15 | 16 | static var appleTableSeparator: UIColor { 17 | return UIColor(red: 224 / 255, green: 224 / 255, blue: 224 / 255, alpha: 1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/UIImage+Bundle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Bundle.swift 3 | // Filestack 4 | // 5 | // Created by Mihály Papp on 30/07/2018. 6 | // Copyright © 2018 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | static func fromFilestackBundle(_ name: String) -> UIImage { 13 | return UIImage(named: name, in: bundle, compatibleWith: nil) ?? UIImage() 14 | } 15 | 16 | static func templatedFilestackImage(_ name: String) -> UIImage { 17 | return fromFilestackBundle(name).withRenderingMode(.alwaysTemplate) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/UIImage+Export.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Export.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 7/11/19. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | func exportHEICImage(to destinationURL: URL, quality: Float) -> Bool { 13 | guard let imageData = heicRepresentation(quality: quality) else { return false } 14 | 15 | return export(data: imageData, to: destinationURL) 16 | } 17 | 18 | func exportJPGImage(to destinationURL: URL, quality: Float) -> Bool { 19 | guard let imageData = jpegData(compressionQuality: CGFloat(quality)) else { return false } 20 | 21 | return export(data: imageData, to: destinationURL) 22 | } 23 | 24 | // MARK: - Private Functions 25 | 26 | private func export(data: Data, to destinationURL: URL) -> Bool { 27 | do { 28 | try data.write(to: destinationURL) 29 | return true 30 | } catch { 31 | return false 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/UIImage+HEIC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+HEIC.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/20/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import AVFoundation 10 | import UIKit 11 | 12 | extension UIImage { 13 | func heicRepresentation(quality: Float) -> Data? { 14 | var imageData: Data? 15 | let destinationData = NSMutableData() 16 | 17 | guard 18 | let cgImage = cgImage, 19 | let destination = CGImageDestinationCreateWithData(destinationData, AVFileType.heic as CFString, 1, nil) 20 | else { 21 | return nil 22 | } 23 | 24 | let options: [CFString: Any] = [ 25 | kCGImageDestinationLossyCompressionQuality: quality, 26 | kCGImagePropertyOrientation: cgImageOrientation.rawValue 27 | ] 28 | 29 | CGImageDestinationAddImage(destination, cgImage, options as CFDictionary) 30 | CGImageDestinationFinalize(destination) 31 | 32 | imageData = destinationData as Data 33 | 34 | return imageData 35 | } 36 | } 37 | 38 | extension UIImage { 39 | var cgImageOrientation: CGImagePropertyOrientation { .init(imageOrientation) } 40 | } 41 | 42 | extension CGImagePropertyOrientation { 43 | init(_ uiOrientation: UIImage.Orientation) { 44 | switch uiOrientation { 45 | case .up: self = .up 46 | case .upMirrored: self = .upMirrored 47 | case .down: self = .down 48 | case .downMirrored: self = .downMirrored 49 | case .left: self = .left 50 | case .leftMirrored: self = .leftMirrored 51 | case .right: self = .right 52 | case .rightMirrored: self = .rightMirrored 53 | @unknown default: 54 | fatalError() 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/UIImage+Resized.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Resized.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 7/11/19. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | func resized(for size: CGSize) -> UIImage? { 13 | let renderer = UIGraphicsImageRenderer(size: size) 14 | 15 | return renderer.image { _ in 16 | draw(in: CGRect(origin: .zero, size: size)) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/UIImage+Write.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+write.swift 3 | // Filestack 4 | // 5 | // Created by Mihály Papp on 31/07/2018. 6 | // Copyright © 2018 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | func with(written text: String, atPoint point: CGPoint) -> UIImage { 13 | let textSize = min(size.height, size.width) / 20 14 | let textColor = UIColor.white 15 | let textFont = UIFont(name: "Helvetica Bold", size: textSize)! 16 | 17 | UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) 18 | 19 | let textFontAttributes: [NSAttributedString.Key: Any] = [.font: textFont, .foregroundColor: textColor] 20 | draw(in: CGRect(origin: CGPoint.zero, size: size)) 21 | 22 | let rect = CGRect(origin: point, size: size) 23 | text.draw(in: rect, withAttributes: textFontAttributes) 24 | 25 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 26 | UIGraphicsEndImageContext() 27 | 28 | return newImage ?? self 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/UIView+Constraints.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Constraints.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 05/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | func fill(with subview: UIView, 13 | connectingEdges: [NSLayoutConstraint.Attribute] = [.top, .bottom, .left, .right], 14 | inset: CGFloat = 0, 15 | withSafeAreaRespecting useSafeArea: Bool = false) { 16 | subview.translatesAutoresizingMaskIntoConstraints = false 17 | 18 | if !subviews.contains(subview) { 19 | addSubview(subview) 20 | } 21 | 22 | connect(edges: connectingEdges, of: subview, inset: inset, withSafeAreaRespecting: useSafeArea) 23 | } 24 | 25 | func connect(edges: [NSLayoutConstraint.Attribute], 26 | of subview: UIView, 27 | inset: CGFloat = 0, 28 | withSafeAreaRespecting useSafeArea: Bool = false) { 29 | guard subviews.contains(subview) else { return } 30 | 31 | let primaryItem = useSafeArea ? safeAreaLayoutGuide : self 32 | 33 | for edge in edges { 34 | let reversedEdges: [NSLayoutConstraint.Attribute] = [.top, .left, .topMargin, .leftMargin] 35 | let offset = reversedEdges.contains(edge) ? -inset : inset 36 | 37 | NSLayoutConstraint(item: primaryItem, attribute: edge, relatedBy: .equal, 38 | toItem: subview, attribute: edge, multiplier: 1, constant: offset).isActive = true 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/URL+Copy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+Copy.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 7/15/19. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL { 12 | func copy(to destinationURL: URL) -> Bool { 13 | let fm = FileManager.default 14 | 15 | do { 16 | try fm.copyItem(at: self, to: destinationURL) 17 | return true 18 | } catch { 19 | return false 20 | } 21 | } 22 | 23 | func copyIntoTemporaryLocation() -> URL? { 24 | let fm = FileManager.default 25 | 26 | let destinationURL = fm.temporaryDirectory 27 | .appendingPathComponent(UUID().uuidString) 28 | .appendingPathExtension(pathExtension) 29 | 30 | if copy(to: destinationURL) { 31 | return destinationURL 32 | } else { 33 | return nil 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/URL+IsDirectory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+IsDirectory.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 17/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL { 12 | var isDirectory: Bool { 13 | var isDirectory: ObjCBool = false 14 | FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) 15 | 16 | return isDirectory.boolValue 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/URL+Move.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+Move.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 15/10/2020. 6 | // Copyright © 2020 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL { 12 | func move(to destinationURL: URL) -> Bool { 13 | let fm = FileManager.default 14 | 15 | do { 16 | try fm.moveItem(at: self, to: destinationURL) 17 | return true 18 | } catch { 19 | return false 20 | } 21 | } 22 | 23 | func moveIntoTemporaryLocation() -> URL? { 24 | let fm = FileManager.default 25 | 26 | let destinationURL = fm.temporaryDirectory 27 | .appendingPathComponent(UUID().uuidString) 28 | .appendingPathExtension(pathExtension) 29 | 30 | if move(to: destinationURL) { 31 | return destinationURL 32 | } else { 33 | return nil 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extensions/URLSession+FilestackDefault.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLSession+FilestackDefault.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 10/24/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if SWIFT_PACKAGE 12 | #else 13 | private class BundleFinder {} 14 | #endif 15 | 16 | extension URLSession { 17 | static var filestackDefault: URLSession { 18 | let configuration = URLSessionConfiguration.default 19 | 20 | configuration.isDiscretionary = false 21 | configuration.shouldUseExtendedBackgroundIdleMode = true 22 | configuration.httpMaximumConnectionsPerHost = 20 23 | configuration.httpShouldUsePipelining = true 24 | configuration.httpAdditionalHeaders = customHTTPHeaders 25 | 26 | return URLSession(configuration: configuration) 27 | } 28 | 29 | func jsonRequest(_ url: URL, payload: [String: Any], method: String = "POST") -> URLRequest { 30 | var request = URLRequest(url: url) 31 | 32 | request.setValue("application/json", forHTTPHeaderField: "Accept") 33 | request.setValue("application/json", forHTTPHeaderField: "Content-Type") 34 | request.httpMethod = method 35 | request.httpBody = try? JSONSerialization.data(withJSONObject: payload, options: []) 36 | 37 | return request 38 | } 39 | } 40 | 41 | // MARK: - Private Functions 42 | 43 | private extension URLSession { 44 | static var customHTTPHeaders: [String: String] { 45 | var defaultHeaders: [String: String] = [:] 46 | 47 | defaultHeaders["User-Agent"] = "filestack-ios \(shortVersionString)" 48 | defaultHeaders["Filestack-Source"] = "Swift-\(shortVersionString)" 49 | 50 | return defaultHeaders 51 | } 52 | 53 | static var shortVersionString: String { 54 | #if SWIFT_PACKAGE 55 | if let url = Bundle.module.url(forResource: "VERSION", withExtension: nil), 56 | let data = try? Data(contentsOf: url), 57 | let version = String(data: data, encoding: .utf8)?.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 58 | { 59 | return version 60 | } 61 | #else 62 | if let info = Bundle(for: BundleFinder.self).infoDictionary, 63 | let version = info["CFBundleShortVersionString"] as? String { 64 | return version 65 | } 66 | #endif 67 | 68 | return "0.0.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Extractors/UploadableExtractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UploadableExtractor.swift 3 | // Filestack 4 | // 5 | // Created by Mihály Papp on 02/08/2018. 6 | // Copyright © 2018 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Photos 11 | 12 | class UploadableExtractor { 13 | // MARK: - Private Properties 14 | 15 | private lazy var imageManager = PHCachingImageManager.default() 16 | 17 | private lazy var videoRequestOptions: PHVideoRequestOptions = { 18 | let options = PHVideoRequestOptions() 19 | 20 | options.version = PHVideoRequestOptionsVersion.current 21 | options.deliveryMode = PHVideoRequestOptionsDeliveryMode.fastFormat 22 | 23 | return options 24 | }() 25 | } 26 | 27 | // MARK: - Internal Functions 28 | 29 | extension UploadableExtractor { 30 | func fetchUploadable(using asset: PHAsset, completion: @escaping (Uploadable?, PHImageRequestID) -> Void) -> PHImageRequestID? { 31 | switch asset.mediaType { 32 | case .image: return fetchImage(for: asset, completion: completion) 33 | case .video: return fetchVideo(for: asset, completion: completion) 34 | case .unknown, .audio: fallthrough 35 | @unknown default: return nil 36 | } 37 | } 38 | 39 | func cancelFetch(using requestID: PHImageRequestID) { 40 | PHImageManager.default().cancelImageRequest(requestID) 41 | } 42 | } 43 | 44 | // MARK: - Private Functions 45 | 46 | private extension UploadableExtractor { 47 | func fetchImage(for asset: PHAsset, completion: @escaping (Uploadable?, PHImageRequestID) -> Void) -> PHImageRequestID { 48 | return asset.fetchImage(forSize: PHImageManagerMaximumSize) { image, requestID in 49 | completion(image, requestID) 50 | } 51 | } 52 | 53 | func fetchVideo(for asset: PHAsset, completion: @escaping (Uploadable?, PHImageRequestID) -> Void) -> PHImageRequestID { 54 | var requestID: PHImageRequestID! 55 | 56 | requestID = imageManager.requestAVAsset(forVideo: asset, options: videoRequestOptions) { avAsset, _, _ in 57 | completion(avAsset, requestID) 58 | } 59 | 60 | return requestID 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Formatters/DurationNumberFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DurationNumberFormatter.swift 3 | // Filestack 4 | // 5 | // Created by Mihály Papp on 02/08/2018. 6 | // Copyright © 2018 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class DurationFormatter: NumberFormatter, @unchecked Sendable { 12 | func string(from seconds: Double) -> String { 13 | let hours = Int(seconds / 3600) 14 | let minutes = Int(seconds.truncatingRemainder(dividingBy: 3600) / 60) 15 | let seconds = Int(seconds.truncatingRemainder(dividingBy: 60)) 16 | if hours > 0 { 17 | return String(format: "%i:%02i:%02i", hours, minutes, seconds) 18 | } else { 19 | return String(format: "%i:%02i", minutes, seconds) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Operations/AssetURLExtractorOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetURLExtractorOperation.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 08/07/2020. 6 | // Copyright © 2020 Filestack. All rights reserved. 7 | // 8 | 9 | import FilestackSDK 10 | import Photos 11 | import UIKit 12 | 13 | class AssetURLExtractorOperation: BaseOperation<[URL]>, ProgressReporting, @unchecked Sendable { 14 | // MARK: - Internal Properties 15 | 16 | let assets: [PHAsset] 17 | 18 | // MARK: - Private Properties 19 | 20 | private let config: Config 21 | 22 | private(set) lazy var progress: Progress = { 23 | let progress = Progress(totalUnitCount: Int64(assets.count)) 24 | 25 | progress.localizedDescription = "Processing \(assets.count) file(s)…" 26 | 27 | return progress 28 | }() 29 | 30 | private var imageRequestIDs: [PHImageRequestID] = [] 31 | private var assetExportSessions: [AVAssetExportSession] = [] 32 | 33 | private lazy var uploadableExtractor = UploadableExtractor() 34 | 35 | private lazy var urlExtractor: URLExtractor = { 36 | URLExtractor(imageExportPreset: config.imageURLExportPreset, 37 | videoExportPreset: config.videoExportPreset, 38 | cameraImageQuality: config.imageExportQuality) 39 | }() 40 | 41 | // MARK: - Lifecycle 42 | 43 | init(assets: [PHAsset], config: Config) { 44 | self.assets = assets 45 | self.config = config 46 | } 47 | 48 | // MARK: - Overrides 49 | 50 | override func main() { 51 | extract() 52 | } 53 | 54 | override func cancel() { 55 | super.cancel() 56 | 57 | for requestID in imageRequestIDs { 58 | PHImageManager.default().cancelImageRequest(requestID) 59 | } 60 | 61 | imageRequestIDs.removeAll() 62 | 63 | for exportSession in assetExportSessions { 64 | exportSession.cancelExport() 65 | } 66 | 67 | assetExportSessions.removeAll() 68 | } 69 | } 70 | 71 | // MARK: - Private Functions 72 | 73 | private extension AssetURLExtractorOperation { 74 | func extract() { 75 | var urls: [URL] = [] 76 | var completed: Int = 0 77 | 78 | let markProgress: (URL?, PHImageRequestID) -> () = { url, id in 79 | completed += 1 80 | 81 | self.imageRequestIDs.removeAll { $0 == id } 82 | self.progress.completedUnitCount = Int64(completed) 83 | 84 | if let url = url { 85 | urls.append(url) 86 | } 87 | 88 | if self.imageRequestIDs.count == 0, !self.isCancelled { 89 | self.finish(with: .success(urls)) 90 | } 91 | } 92 | 93 | for asset in assets { 94 | let requestID: PHImageRequestID? = uploadableExtractor.fetchUploadable(using: asset) { (uploadable, id) in 95 | guard !self.isCancelled else { return } 96 | 97 | switch uploadable { 98 | case let image as UIImage: 99 | if let url = self.urlExtractor.fetchURL(image: image) { 100 | markProgress(url, id) 101 | } else { 102 | markProgress(nil, id) 103 | } 104 | case let video as AVAsset: 105 | let exportSession = self.urlExtractor.fetchVideoURL(of: video) { url in 106 | markProgress(url, id) 107 | } 108 | 109 | if let exportSession = exportSession { 110 | self.assetExportSessions.append(exportSession) 111 | } 112 | default: 113 | break 114 | } 115 | } 116 | 117 | if let requestID = requestID { 118 | imageRequestIDs.append(requestID) 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Protocols/CloudRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloudRequest.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 10/25/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import FilestackSDK 10 | import Foundation 11 | 12 | typealias CloudRequestCompletion = (_ appRedirectURL: URL?, _ response: CloudResponse) -> Void 13 | 14 | protocol CloudRequest { 15 | var token: String? { get } 16 | var provider: CloudProvider { get } 17 | var apiKey: String { get } 18 | var security: Security? { get } 19 | 20 | @discardableResult 21 | func perform(cloudService: CloudService, completionBlock: @escaping CloudRequestCompletion) -> URLSessionDataTask 22 | 23 | func getResults(from json: [String: Any]) -> [String: Any]? 24 | } 25 | 26 | extension CloudRequest { 27 | func getResults(from json: [String: Any]) -> [String: Any]? { 28 | return json[provider.description] as? [String: Any] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Requests/FolderListRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FolderListRequest.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 10/25/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import FilestackSDK 10 | import Foundation 11 | 12 | final class FolderListRequest: CloudRequest, Cancellable { 13 | // MARK: - Properties 14 | 15 | let authCallbackURL: URL 16 | let apiKey: String 17 | let security: Security? 18 | let pageToken: String? 19 | let provider: CloudProvider 20 | let path: String 21 | 22 | private(set) var token: String? 23 | private weak var dataTask: URLSessionDataTask? 24 | 25 | // MARK: - Lifecyle Functions 26 | 27 | init(authCallbackURL: URL, 28 | apiKey: String, 29 | security: Security? = nil, 30 | token: String? = nil, 31 | pageToken: String? = nil, 32 | provider: CloudProvider, 33 | path: String) { 34 | self.authCallbackURL = authCallbackURL 35 | self.apiKey = apiKey 36 | self.security = security 37 | self.token = token 38 | self.pageToken = pageToken 39 | self.provider = provider 40 | self.path = path 41 | } 42 | 43 | // MARK: - Cancellable Protocol Implementation 44 | 45 | @discardableResult func cancel() -> Bool { 46 | guard let dataTask = dataTask else { return false } 47 | dataTask.cancel() 48 | 49 | return true 50 | } 51 | 52 | // MARK: - Internal Functions 53 | 54 | func perform(cloudService: CloudService, completionBlock: @escaping CloudRequestCompletion) -> URLSessionDataTask { 55 | let request = cloudService.folderListRequest(provider: provider, 56 | path: path, 57 | authCallbackURL: authCallbackURL, 58 | apiKey: apiKey, 59 | security: security, 60 | token: token, 61 | pageToken: pageToken) 62 | 63 | let task = URLSession.filestackDefault.dataTask(with: request) { (data, response, error) in 64 | // Parse JSON, or return early with error if unable to parse. 65 | guard let data = data, let json = data.parseJSON() else { 66 | let response = FolderListResponse(error: error) 67 | 68 | DispatchQueue.main.async { completionBlock(nil, response) } 69 | 70 | return 71 | } 72 | 73 | // Store any token we receive so we can use it next time. 74 | self.token = json["token"] as? String 75 | 76 | if let authURL = self.getAuthURL(from: json) { 77 | // Auth is required — redirect to authentication URL 78 | let response = FolderListResponse(authURL: authURL) 79 | 80 | DispatchQueue.main.async { completionBlock(self.authCallbackURL, response) } 81 | } else if let results = self.getResults(from: json) { 82 | // Results received — return response with contents, and, optionally next token 83 | let contents = results["contents"] as? [[String: Any]] 84 | let nextToken: String? = self.token(from: results["next"] as? String) 85 | let response = FolderListResponse(contents: contents, nextToken: nextToken, error: error) 86 | 87 | DispatchQueue.main.async { completionBlock(nil, response) } 88 | } else { 89 | let response = FolderListResponse(contents: nil, nextToken: nil, error: error) 90 | 91 | DispatchQueue.main.async { completionBlock(nil, response) } 92 | } 93 | } 94 | 95 | dataTask = task 96 | 97 | task.resume() 98 | 99 | return task 100 | } 101 | 102 | private func token(from string: String?) -> String? { 103 | guard let string = string, !string.isEmpty else { return nil } 104 | return string 105 | } 106 | 107 | // MARK: - Private Functions 108 | 109 | func getAuthURL(from json: [String: Any]) -> URL? { 110 | guard let providerJSON = json[provider.description] as? [String: Any] else { return nil } 111 | guard let authJSON = providerJSON["auth"] as? [String: Any] else { return nil } 112 | guard let redirectURLString = authJSON["redirect_url"] as? String else { return nil } 113 | 114 | return URL(string: redirectURLString) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Requests/LogoutRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogoutRequest.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/21/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class LogoutRequest { 12 | // MARK: - Properties 13 | 14 | let provider: CloudProvider 15 | let apiKey: String 16 | let token: String 17 | 18 | // MARK: - Lifecyle Functions 19 | 20 | init(provider: CloudProvider, apiKey: String, token: String) { 21 | self.provider = provider 22 | self.apiKey = apiKey 23 | self.token = token 24 | } 25 | 26 | // MARK: - Internal Functions 27 | 28 | func perform(cloudService: CloudService, completionBlock: @escaping LogoutCompletionHandler) { 29 | let request = cloudService.logoutRequest(provider: provider, apiKey: apiKey, token: token) 30 | 31 | let task = URLSession.filestackDefault.dataTask(with: request) { (data, response, error) in 32 | let response = LogoutResponse(error: error) 33 | 34 | completionBlock(response) 35 | } 36 | 37 | task.resume() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Requests/PrefetchRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrefetchRequest.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/8/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class PrefetchRequest { 12 | // MARK: - Properties 13 | 14 | let apiKey: String 15 | 16 | // MARK: - Lifecyle Functions 17 | 18 | init(apiKey: String) { 19 | self.apiKey = apiKey 20 | } 21 | 22 | // MARK: - Internal Functions 23 | 24 | func perform(cloudService: CloudService, completionBlock: @escaping PrefetchCompletionHandler) { 25 | let request = cloudService.prefetchRequest(apiKey: apiKey) 26 | 27 | let task = URLSession.filestackDefault.dataTask(with: request) { (data, response, error) in 28 | // Parse JSON, or return early with error if unable to parse. 29 | guard let data = data, let json = data.parseJSON() else { 30 | let response = PrefetchResponse(error: error) 31 | 32 | DispatchQueue.main.async { completionBlock(response) } 33 | 34 | return 35 | } 36 | 37 | // Results received — return response with contents 38 | let response = PrefetchResponse(contents: json, error: error) 39 | 40 | DispatchQueue.main.async { completionBlock(response) } 41 | } 42 | 43 | task.resume() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Requests/StoreRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoreRequest.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 10/27/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import FilestackSDK 10 | import Foundation 11 | 12 | final class StoreRequest: CloudRequest, Cancellable, Monitorizable { 13 | // MARK: - Properties 14 | 15 | let apiKey: String 16 | let security: Security? 17 | let provider: CloudProvider 18 | let path: String 19 | let storeOptions: StorageOptions 20 | 21 | private(set) var token: String? 22 | private weak var dataTask: URLSessionDataTask? 23 | 24 | let progress: Progress = { 25 | let progress = Progress(totalUnitCount: 0) 26 | 27 | progress.localizedDescription = "Storing file in storage location…" 28 | progress.localizedAdditionalDescription = "" 29 | 30 | return progress 31 | }() 32 | 33 | // MARK: - Lifecyle Functions 34 | 35 | init(apiKey: String, 36 | security: Security? = nil, 37 | token: String? = nil, 38 | provider: CloudProvider, 39 | path: String, 40 | storeOptions: StorageOptions) { 41 | self.apiKey = apiKey 42 | self.security = security 43 | self.token = token 44 | self.provider = provider 45 | self.path = path 46 | self.storeOptions = storeOptions 47 | } 48 | 49 | // MARK: - Cancellable Protocol Implementation 50 | 51 | @discardableResult func cancel() -> Bool { 52 | guard let dataTask = dataTask else { return false } 53 | dataTask.cancel() 54 | 55 | return true 56 | } 57 | 58 | // MARK: - Internal Functions 59 | 60 | func perform(cloudService: CloudService, completionBlock: @escaping CloudRequestCompletion) -> URLSessionDataTask { 61 | let request = cloudService.storeRequest(provider: provider, 62 | path: path, 63 | apiKey: apiKey, 64 | security: security, 65 | token: token, 66 | storeOptions: storeOptions) 67 | 68 | let task = URLSession.filestackDefault.dataTask(with: request) { (data, response, error) in 69 | // Parse JSON, or return early with error if unable to parse. 70 | guard let data = data, let json = data.parseJSON() else { 71 | let response = StoreResponse(error: error) 72 | 73 | DispatchQueue.main.async { completionBlock(nil, response) } 74 | 75 | return 76 | } 77 | 78 | // Store any token we receive so we can use it next time. 79 | self.token = json["token"] as? String 80 | 81 | if let results = self.getResults(from: json) { 82 | // Results received — return response with contents 83 | let response = StoreResponse(contents: results, error: error) 84 | 85 | DispatchQueue.main.async { completionBlock(nil, response) } 86 | } 87 | } 88 | 89 | dataTask = task 90 | 91 | task.resume() 92 | 93 | return task 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/Services/CloudService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloudService.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 10/24/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import FilestackSDK 10 | import Foundation 11 | 12 | class CloudService { 13 | let session = URLSession.filestackDefault 14 | let baseURL = Constants.cloudURL 15 | 16 | func folderListRequest(provider: CloudProvider, 17 | path: String, 18 | authCallbackURL: URL, 19 | apiKey: String, 20 | security: Security? = nil, 21 | token: String? = nil, 22 | pageToken: String? = nil) -> URLRequest { 23 | let url = baseURL.appendingPathComponent("folder/list") 24 | 25 | var params: [String: Any] = [ 26 | "apikey": apiKey, 27 | "appurl": authCallbackURL.absoluteString, 28 | "flow": "mobile", 29 | ] 30 | 31 | if let token = token { 32 | params["token"] = token 33 | } 34 | 35 | if let pageToken = pageToken { 36 | params["clouds"] = [ 37 | provider.description: [ 38 | "path": path, 39 | "next": pageToken, 40 | ], 41 | ] 42 | } else { 43 | params["clouds"] = [ 44 | provider.description: [ 45 | "path": path, 46 | ], 47 | ] 48 | } 49 | 50 | if let security = security { 51 | params["policy"] = security.encodedPolicy 52 | params["signature"] = security.signature 53 | } 54 | 55 | return session.jsonRequest(url, payload: params) 56 | } 57 | 58 | func storeRequest(provider: CloudProvider, 59 | path: String, 60 | apiKey: String, 61 | security: Security? = nil, 62 | token: String? = nil, 63 | storeOptions: StorageOptions) -> URLRequest { 64 | let url = baseURL.appendingPathComponent("store/") 65 | 66 | var params: [String: Any] = [ 67 | "apikey": apiKey, 68 | "flow": "mobile", 69 | "clouds": [ 70 | provider.description: [ 71 | "path": path, 72 | "store": storeOptions.asDictionary(), 73 | ], 74 | ] 75 | ] 76 | 77 | if let token = token { 78 | params["token"] = token 79 | } 80 | 81 | if let security = security { 82 | params["policy"] = security.encodedPolicy 83 | params["signature"] = security.signature 84 | } 85 | 86 | return session.jsonRequest(url, payload: params) 87 | } 88 | 89 | func prefetchRequest(apiKey: String) -> URLRequest { 90 | let url = baseURL.appendingPathComponent("prefetch") 91 | let params: [String: Any] = ["apikey": apiKey] 92 | 93 | return session.jsonRequest(url, payload: params) 94 | } 95 | 96 | func logoutRequest(provider: CloudProvider, apiKey: String, token: String) -> URLRequest { 97 | let url = baseURL.appendingPathComponent("auth/logout") 98 | 99 | let params: [String: Any] = [ 100 | "apikey": apiKey, 101 | "token": token, 102 | "flow": "mobile", 103 | "clouds": [ 104 | provider.description: [:], 105 | ], 106 | ] 107 | 108 | return session.jsonRequest(url, payload: params) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/Filestack/Internal/TrackingProgress.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackingProgress.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 07/07/2020. 6 | // Copyright © 2020 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class TrackingProgress: Progress, @unchecked Sendable { 12 | // MARK: - Private Properties 13 | 14 | private let lockQueue = DispatchQueue(label: "com.filestack.FilestackSDK.tracking-progress-lock-queue") 15 | 16 | private var observers: [NSKeyValueObservation] = [] 17 | 18 | private var _tracked: Progress? { 19 | didSet { 20 | removeObservers() 21 | 22 | guard let progress = _tracked else { return } 23 | 24 | kind = progress.kind 25 | fileOperationKind = progress.fileOperationKind 26 | fileTotalCount = progress.fileTotalCount 27 | fileCompletedCount = progress.fileCompletedCount 28 | fileURL = progress.fileURL 29 | totalUnitCount = progress.totalUnitCount 30 | completedUnitCount = progress.completedUnitCount 31 | 32 | setupObservers() 33 | } 34 | } 35 | 36 | // MARK: - Lifecycle 37 | 38 | required init(tracked progress: Progress? = nil) { 39 | super.init(parent: nil, userInfo: nil) 40 | 41 | if let tracked = tracked { 42 | update(tracked: tracked) 43 | } 44 | } 45 | } 46 | 47 | // MARK: - Computed Properties 48 | 49 | private extension TrackingProgress { 50 | var tracked: Progress? { 51 | get { lockQueue.sync { _tracked } } 52 | set { lockQueue.sync { _tracked = newValue } } 53 | } 54 | } 55 | 56 | // MARK: - Internal Functions 57 | 58 | extension TrackingProgress { 59 | func update(tracked progress: Progress?, delay: Double = 0) { 60 | let delay: Double = tracked == nil ? 0 : delay // ignore `delay` argument if there's currently no tracked progress. 61 | 62 | DispatchQueue.main.asyncAfter(deadline: .now() + delay) { 63 | guard !self.isCancelled else { return } 64 | self.tracked = progress 65 | } 66 | } 67 | 68 | override func cancel() { 69 | super.cancel() 70 | 71 | tracked = nil 72 | } 73 | } 74 | 75 | // MARK: - Progress Overrides 76 | 77 | extension TrackingProgress { 78 | override var localizedDescription: String! { 79 | get { tracked?.localizedDescription ?? super.localizedDescription } 80 | set { /* */ } 81 | } 82 | 83 | override var localizedAdditionalDescription: String! { 84 | get { tracked?.localizedAdditionalDescription ?? super.localizedAdditionalDescription } 85 | set { /* */ } 86 | } 87 | } 88 | 89 | // MARK: - Private Functions 90 | 91 | private extension TrackingProgress { 92 | func setupObservers() { 93 | guard let tracked = _tracked else { return } 94 | 95 | observers.append(tracked.observe(\.totalUnitCount, options: [.new]) { (progress, change) in 96 | self.totalUnitCount = progress.totalUnitCount 97 | }) 98 | 99 | observers.append(tracked.observe(\.completedUnitCount, options: [.new]) { (progress, change) in 100 | self.completedUnitCount = progress.completedUnitCount 101 | }) 102 | } 103 | 104 | func removeObservers() { 105 | observers.removeAll() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Sources/Filestack/Public/CompletionHandlers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompletionHandlers.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// :nodoc: 12 | public typealias FolderListCompletionHandler = (_ response: FolderListResponse) -> Void 13 | 14 | /// :nodoc: 15 | public typealias StoreCompletionHandler = (_ response: StoreResponse) -> Void 16 | 17 | /// :nodoc: 18 | public typealias LogoutCompletionHandler = (_ response: LogoutResponse) -> Void 19 | 20 | /// :nodoc: 21 | public typealias PrefetchCompletionHandler = (_ response: PrefetchResponse) -> Void 22 | -------------------------------------------------------------------------------- /Sources/Filestack/Public/Enums/ClientError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Errors.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 16/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// A specific kind of `Error` that may be returned by the `Client`. 12 | public enum ClientError: Error { 13 | /// Authentication failed. 14 | case authenticationFailed 15 | } 16 | 17 | extension ClientError: LocalizedError { 18 | public var errorDescription: String? { 19 | switch self { 20 | case .authenticationFailed: 21 | return NSLocalizedString("Unable to authenticate.", comment: "") 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Filestack/Public/Enums/CloudProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloudProvider.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 10/26/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Represents a cloud provider. 12 | @objc(FSCloudProvider) public enum CloudProvider: UInt, CustomStringConvertible { 13 | /// Facebook 14 | case facebook 15 | 16 | /// Instagram 17 | case instagram 18 | 19 | /// Google Drive 20 | case googleDrive 21 | 22 | /// Dropbox 23 | case dropbox 24 | 25 | /// Box 26 | case box 27 | 28 | /// GitHub 29 | case gitHub 30 | 31 | /// Gmail 32 | case gmail 33 | 34 | /// Google Photos 35 | case googlePhotos 36 | 37 | /// OneDrive 38 | case oneDrive 39 | 40 | /// Amazon Drive 41 | case amazonDrive 42 | 43 | /// Unsplash 44 | case unsplash 45 | 46 | /// Custom Source 47 | case customSource 48 | } 49 | 50 | extension CloudProvider { 51 | /// :nodoc: 52 | public var searchBased: Bool { 53 | switch self { 54 | case .unsplash: 55 | return true 56 | default: 57 | return false 58 | } 59 | } 60 | 61 | /// :nodoc: 62 | var viewType: CloudSourceViewType? { 63 | switch self { 64 | case .unsplash: 65 | return .grid 66 | default: 67 | return nil 68 | } 69 | } 70 | } 71 | 72 | extension CloudProvider { 73 | /// :nodoc: 74 | public var description: String { 75 | switch self { 76 | case .facebook: 77 | return "facebook" 78 | case .instagram: 79 | return "instagram" 80 | case .googleDrive: 81 | return "googledrive" 82 | case .dropbox: 83 | return "dropbox" 84 | case .box: 85 | return "box" 86 | case .gitHub: 87 | return "github" 88 | case .gmail: 89 | return "gmail" 90 | case .googlePhotos: 91 | return "picasa" 92 | case .oneDrive: 93 | return "onedrive" 94 | case .amazonDrive: 95 | return "clouddrive" 96 | case .unsplash: 97 | return "unsplash" 98 | case .customSource: 99 | return "customsource" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Sources/Filestack/Public/Enums/ImageURLExportPreset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageURLExportPreset.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/22/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Represents an image URL export preset. 12 | @objc(FSImageURLExportPreset) public enum ImageURLExportPreset: Int { 13 | /// A preset for converting HEIF formatted images to JPEG. 14 | case compatible 15 | 16 | /// A preset for passing image data as-is to the client. 17 | case current 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Filestack/Public/Enums/PhotosPickerFilter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotosPickerFilter.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 15/10/2020. 6 | // Copyright © 2020 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PhotosUI 11 | 12 | /// Represents a cloud provider. 13 | @objc(FSPhotosPickerFilter) public enum PhotosPickerFilter: UInt { 14 | /// The filter for images. 15 | case images 16 | 17 | /// The filter for live photos. 18 | case livePhotos 19 | 20 | /// The filter for videos. 21 | case videos 22 | } 23 | 24 | @available(iOS 14.0, *) 25 | extension PhotosPickerFilter { 26 | var asPHFilter: PHPickerFilter { 27 | switch self { 28 | case .images: 29 | return .images 30 | case .livePhotos: 31 | return .livePhotos 32 | case .videos: 33 | return .videos 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Filestack/Public/Enums/PickerBehavior.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PickerBehavior.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/2/21. 6 | // Copyright © 2021 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import FilestackSDK 11 | 12 | /// Represents the picker's pick behavior. 13 | public enum PickerBehavior: Equatable { 14 | /// After finishing picking, local files are uploaded and cloud files are stored at the store destination. 15 | case uploadAndStore(uploadOptions: UploadOptions) 16 | 17 | /// After finishing picking, only cloud files are stored at the store destination. 18 | case storeOnly 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Filestack/Public/Responses/CloudResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloudResponse.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// :nodoc: 12 | @objc(FSCloudResponse) public protocol CloudResponse { 13 | @objc var error: Error? { get } 14 | @objc var authURL: URL? { get } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Filestack/Public/Responses/FolderListResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FolderListResponse.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This class represents a response obtained from a folder list request. 12 | @objc(FSFolderListResponse) public class FolderListResponse: NSObject, CloudResponse { 13 | // MARK: - Properties 14 | 15 | /// The contents payload as an array of dictionaries, where each dictionary represents an entry in the cloud. 16 | @objc public let contents: [[String: Any]]? 17 | 18 | /// A next token used for pagination purposes. Optional. 19 | @objc public let nextToken: String? 20 | 21 | /// A redirect URL to a cloud provider's OAuth page. Typically this is only required internally. 22 | @objc public let authURL: URL? 23 | 24 | /// An error response. Optional. 25 | @objc public let error: Error? 26 | 27 | // MARK: - Lifecyle Functions 28 | 29 | init(contents: [[String: Any]]? = nil, 30 | nextToken: String? = nil, 31 | authURL: URL? = nil, 32 | error: Error? = nil) { 33 | self.contents = contents 34 | self.nextToken = nextToken 35 | self.authURL = authURL 36 | self.error = error 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Filestack/Public/Responses/LogoutResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogoutResponse.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This class represents a response obtained from a logout request. 12 | @objc(FSLogoutResponse) public class LogoutResponse: NSObject { 13 | // MARK: - Properties 14 | 15 | /// An error response. Optional. 16 | @objc public let error: Error? 17 | 18 | // MARK: - Lifecyle Functions 19 | 20 | init(error: Error? = nil) { 21 | self.error = error 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Filestack/Public/Responses/PrefetchResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrefetchResponse.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// :nodoc: 12 | @objc(FSPrefetchResponse) public class PrefetchResponse: NSObject { 13 | // MARK: - Properties 14 | 15 | @objc public let contents: [String: Any]? 16 | @objc public let error: Error? 17 | 18 | // MARK: - Lifecyle Functions 19 | 20 | init(contents: [String: Any]? = nil, error: Error? = nil) { 21 | self.contents = contents 22 | self.error = error 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Filestack/Public/Responses/StoreResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoreResponse.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This class represents a response obtained from a store request. 12 | @objc(FSStoreResponse) public class StoreResponse: NSObject, CloudResponse { 13 | // MARK: - Properties 14 | 15 | /// The contents payload as a dictionary containing details about the operation response. 16 | @objc public let contents: [String: Any]? 17 | 18 | /// A redirect URL to a cloud provider's OAuth page. Typically this is only required internally. 19 | @objc public let authURL: URL? = nil 20 | 21 | /// An error response. Optional. 22 | @objc public let error: Error? 23 | 24 | // MARK: - Lifecyle Functions 25 | 26 | init(contents: [String: Any]? = nil, error: Error? = nil) { 27 | self.contents = contents 28 | self.error = error 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Colors.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Colors.xcassets/SelectionCellBorderColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.792", 13 | "alpha" : "1.000", 14 | "blue" : "0.847", 15 | "green" : "0.808" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.400", 31 | "alpha" : "1.000", 32 | "blue" : "0.455", 33 | "green" : "0.416" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/clear-pattern.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "clear-pattern.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/clear-pattern.imageset/clear-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/clear-pattern.imageset/clear-pattern.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/file.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "file.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "file@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/file.imageset/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/file.imageset/file.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/file.imageset/file@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/file.imageset/file@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-box.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-box.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-box@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-box.imageset/icon-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-box.imageset/icon-box.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-box.imageset/icon-box@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-box.imageset/icon-box@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-camera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-camera.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-camera@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-camera.imageset/icon-camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-camera.imageset/icon-camera.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-camera.imageset/icon-camera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-camera.imageset/icon-camera@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "circle_icon.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-circle.imageset/circle_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-circle.imageset/circle_icon.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-clouddrive.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-clouddrive.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-clouddrive@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-clouddrive.imageset/icon-clouddrive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-clouddrive.imageset/icon-clouddrive.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-clouddrive.imageset/icon-clouddrive@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-clouddrive.imageset/icon-clouddrive@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-crop.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "crop_icon.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-crop.imageset/crop_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-crop.imageset/crop_icon.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-customsource.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-customsource.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-customsource@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-customsource.imageset/icon-customsource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-customsource.imageset/icon-customsource.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-customsource.imageset/icon-customsource@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-customsource.imageset/icon-customsource@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-documents.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "documents.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "documents@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-documents.imageset/documents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-documents.imageset/documents.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-documents.imageset/documents@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-documents.imageset/documents@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-dropbox.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-dropbox.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-dropbox@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-dropbox.imageset/icon-dropbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-dropbox.imageset/icon-dropbox.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-dropbox.imageset/icon-dropbox@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-dropbox.imageset/icon-dropbox@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-edit.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "icon-pencil.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-edit.imageset/icon-pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-edit.imageset/icon-pencil.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-facebook.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-facebook.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-facebook@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-facebook.imageset/icon-facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-facebook.imageset/icon-facebook.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-facebook.imageset/icon-facebook@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-facebook.imageset/icon-facebook@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-file-image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "icon-image.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-file-image.imageset/icon-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-file-image.imageset/icon-image.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-file-pdf.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "icon-file-pdf.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-file-pdf.imageset/icon-file-pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-file-pdf.imageset/icon-file-pdf.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-file-unknown.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "icon-file-unknown.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-file-unknown.imageset/icon-file-unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-file-unknown.imageset/icon-file-unknown.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-file-video.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "icon-video-camera.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-file-video.imageset/icon-video-camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-file-video.imageset/icon-video-camera.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-github.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-github.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-github@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-github.imageset/icon-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-github.imageset/icon-github.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-github.imageset/icon-github@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-github.imageset/icon-github@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-gmail.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-gmail.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-gmail@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-gmail.imageset/icon-gmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-gmail.imageset/icon-gmail.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-gmail.imageset/icon-gmail@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-gmail.imageset/icon-gmail@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-googledrive.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-googledrive.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-googledrive@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-googledrive.imageset/icon-googledrive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-googledrive.imageset/icon-googledrive.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-googledrive.imageset/icon-googledrive@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-googledrive.imageset/icon-googledrive@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-grid.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-grid.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-grid@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon-grid@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-grid.imageset/icon-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-grid.imageset/icon-grid.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-grid.imageset/icon-grid@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-grid.imageset/icon-grid@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-grid.imageset/icon-grid@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-grid.imageset/icon-grid@3x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "icon-image.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-image.imageset/icon-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-image.imageset/icon-image.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-instagram.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-instagram.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-instagram@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-instagram.imageset/icon-instagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-instagram.imageset/icon-instagram.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-instagram.imageset/icon-instagram@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-instagram.imageset/icon-instagram@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-list.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-list.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-list@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon-list@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-list.imageset/icon-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-list.imageset/icon-list.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-list.imageset/icon-list@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-list.imageset/icon-list@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-list.imageset/icon-list@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-list.imageset/icon-list@3x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-logout.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-logout.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-logout@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon-logout@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-logout.imageset/icon-logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-logout.imageset/icon-logout.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-logout.imageset/icon-logout@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-logout.imageset/icon-logout@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-logout.imageset/icon-logout@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-logout.imageset/icon-logout@3x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-onedrive.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-skydrive.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-skydrive@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-onedrive.imageset/icon-skydrive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-onedrive.imageset/icon-skydrive.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-onedrive.imageset/icon-skydrive@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-onedrive.imageset/icon-skydrive@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-photolibrary.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-albums.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-albums@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-photolibrary.imageset/icon-albums.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-photolibrary.imageset/icon-albums.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-photolibrary.imageset/icon-albums@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-photolibrary.imageset/icon-albums@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-picasa.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-picasa.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-picasa@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-picasa.imageset/icon-picasa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-picasa.imageset/icon-picasa.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-picasa.imageset/icon-picasa@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-picasa.imageset/icon-picasa@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-redo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "redo_icon.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-redo.imageset/redo_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-redo.imageset/redo_icon.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-rotate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "rotate_icon.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-rotate.imageset/rotate_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-rotate.imageset/rotate_icon.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "photoIcon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "photoIcon-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "photoIcon-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-selected.imageset/photoIcon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-selected.imageset/photoIcon-1.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-selected.imageset/photoIcon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-selected.imageset/photoIcon-2.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-selected.imageset/photoIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-selected.imageset/photoIcon.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-tick.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "checkmark-solid.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-tick.imageset/checkmark-solid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-tick.imageset/checkmark-solid.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-undo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "undo_icon.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-undo.imageset/undo_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-undo.imageset/undo_icon.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-unsplash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon-unsplash.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "icon-unsplash@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-unsplash.imageset/icon-unsplash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-unsplash.imageset/icon-unsplash.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-unsplash.imageset/icon-unsplash@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-unsplash.imageset/icon-unsplash@2x.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-upload.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "icon_upload.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/icon-upload.imageset/icon_upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/icon-upload.imageset/icon_upload.png -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "placeholder.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/Filestack/Resources/Icons.xcassets/placeholder.imageset/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filestack/filestack-ios/e1d9ce15e3e0bc904d5323f9670388df0d36a4a0/Sources/Filestack/Resources/Icons.xcassets/placeholder.imageset/placeholder.png -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Collection View Cells/ActivityIndicatorCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActivityIndicatorCollectionViewCell.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/17/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ActivityIndicatorCollectionViewCell: UICollectionViewCell { 12 | @IBOutlet var activityIndicator: UIActivityIndicatorView! 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Collection View Cells/CloudItemCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloudItemCollectionViewCell.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/17/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CloudItemCollectionViewCell: UICollectionViewCell { 12 | @IBOutlet var imageView: UIImageView! 13 | @IBOutlet var label: UILabel! 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Controllers/CustomPickerUploadController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomPickerUploadController.swift 3 | // CustomPickerUploadController 4 | // 5 | // Created by Ruben Nine on 26/7/21. 6 | // Copyright © 2021 Filestack. All rights reserved. 7 | // 8 | 9 | import FilestackSDK 10 | import UIKit 11 | 12 | class CustomPickerUploadController: URLPickerUploadController { 13 | let navigationVC: UINavigationController 14 | let provider: SourceProvider 15 | 16 | init(uploader: (Uploader & DeferredAdd)?, 17 | viewController: UIViewController, 18 | provider: SourceProvider, 19 | config: Config, 20 | context: Any? = nil, 21 | completionBlock: (([URL]) -> Void)? = nil) { 22 | 23 | self.navigationVC = UINavigationController(rootViewController: provider) 24 | self.provider = provider 25 | 26 | super.init(uploader: uploader, viewController: viewController, presentedViewController: navigationVC, config: config, completionBlock: completionBlock) 27 | 28 | provider.sourceProviderDelegate = self 29 | } 30 | } 31 | 32 | extension CustomPickerUploadController: SourceProviderDelegate { 33 | func sourceProviderPicked(urls: [URL]) { 34 | upload(urls: urls) 35 | } 36 | 37 | func sourceProviderCancelled() { 38 | cancel() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Controllers/DocumentPickerUploadController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DocumentPickerUploadController.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 12/1/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import FilestackSDK 10 | import UIKit 11 | import UniformTypeIdentifiers 12 | 13 | class DocumentPickerUploadController: URLPickerUploadController { 14 | let picker: UIDocumentPickerViewController 15 | 16 | init(uploader: (Uploader & DeferredAdd)?, 17 | viewController: UIViewController, 18 | config: Config, 19 | completionBlock: (([URL]) -> Void)? = nil) { 20 | let allowedContentTypes = config.documentPickerAllowedUTIs.compactMap { UTIString in 21 | if let contentType = UTType(UTIString) { 22 | return contentType 23 | } else { 24 | return nil 25 | } 26 | } 27 | self.picker = UIDocumentPickerViewController(forOpeningContentTypes: allowedContentTypes.isEmpty ? [.item] : allowedContentTypes, asCopy: true) 28 | super.init(uploader: uploader, 29 | viewController: viewController, 30 | presentedViewController: picker, 31 | config: config, 32 | completionBlock: completionBlock) 33 | 34 | self.picker.delegate = self 35 | } 36 | } 37 | 38 | extension DocumentPickerUploadController: UIDocumentPickerDelegate { 39 | // called if the user dismisses the document picker without selecting a document (using the Cancel button) 40 | func documentPickerWasCancelled(_: UIDocumentPickerViewController) { 41 | cancel() 42 | } 43 | 44 | // Required 45 | func documentPicker(_: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { 46 | completionBlock?(urls) 47 | 48 | if uploader != nil { 49 | upload(urls: urls) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Enums/CloudSourceViewType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloudSourceViewType.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/17/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum CloudSourceViewType: Int { 12 | case list = 0 13 | case grid = 1 14 | } 15 | 16 | extension CloudSourceViewType { 17 | var iconName: String { 18 | switch self { 19 | case .list: 20 | return "icon-list" 21 | case .grid: 22 | return "icon-grid" 23 | } 24 | } 25 | 26 | func toggle() -> CloudSourceViewType { 27 | switch self { 28 | case .list: 29 | return .grid 30 | case .grid: 31 | return .list 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Extensions/Scene+Defaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Scene+Defaults.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/13/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Scene { 12 | var identifier: String { 13 | return String(describing: ViewController.self) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Extensions/Storyboard+Scenes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Storyboard+ instantiateViewController.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/13/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIStoryboard { 12 | func instantiateViewController(for scene: S) -> S.ViewController { 13 | guard let viewController = instantiateViewController(withIdentifier: scene.identifier) as? S.ViewController else { 14 | fatalError("expected view controller with identifier '\(scene.identifier)' to be of type '\(String(describing: S.ViewController.self))'") 15 | } 16 | 17 | scene.configureViewController(viewController) 18 | 19 | return viewController 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Extensions/UIImage+Squared.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Squared.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/14/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | var isPortrait: Bool { return size.height > size.width } 13 | var isLandscape: Bool { return size.width > size.height } 14 | var breadth: CGFloat { return min(size.width, size.height) } 15 | var breadthSize: CGSize { return CGSize(width: breadth, height: breadth) } 16 | var breadthRect: CGRect { return CGRect(origin: .zero, size: breadthSize) } 17 | 18 | var squared: UIImage? { 19 | if size.width == size.height { 20 | // Already square. Return self 21 | return self 22 | } 23 | 24 | UIGraphicsBeginImageContextWithOptions(breadthSize, false, scale) 25 | defer { UIGraphicsEndImageContext() } 26 | 27 | let cropX = isLandscape ? floor((size.width - size.height) / 2) : 0 28 | let cropY = isPortrait ? floor((size.height - size.width) / 2) : 0 29 | let cropRect = CGRect(origin: CGPoint(x: cropX, y: cropY), size: breadthSize) 30 | 31 | guard let cgImage = cgImage?.cropping(to: cropRect) else { return nil } 32 | 33 | UIImage(cgImage: cgImage).draw(in: breadthRect) 34 | 35 | return UIGraphicsGetImageFromCurrentImageContext() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Extensions/UserDefaults+State.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaults+State.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/17/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | private extension String { 12 | static let cloudSourceViewType = "FSCloudSourceViewType" 13 | } 14 | 15 | extension UserDefaults { 16 | func cloudSourceViewType() -> CloudSourceViewType? { 17 | return CloudSourceViewType(rawValue: integer(forKey: .cloudSourceViewType)) 18 | } 19 | 20 | func set(cloudSourceViewType: CloudSourceViewType) { 21 | setValue(cloudSourceViewType.rawValue, forKeyPath: .cloudSourceViewType) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/FlowLayouts/CollectionViewFlowLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewFlowLayout.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 17/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CollectionViewFlowLayout: UICollectionViewFlowLayout { 12 | /// The default implementation of this method returns false. 13 | /// Subclasses can override it and return an appropriate value 14 | /// based on whether changes in the bounds of the collection 15 | /// view require changes to the layout of cells and supplementary views. 16 | /// If the bounds of the collection view change and this method returns true, 17 | /// the collection view invalidates the layout by calling the invalidateLayout(with:) method. 18 | override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { 19 | return (self.collectionView?.bounds ?? newBounds) == newBounds 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Models/CloudItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloudItem.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/10/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct CloudItem { 12 | let isFolder: Bool 13 | let name: String 14 | let path: String 15 | let thumbnailURL: URL 16 | 17 | init?(dictionary: [String: Any]) { 18 | guard let isFolder = dictionary["folder"] as? Bool, 19 | let name = dictionary["name"] as? String, 20 | let path = dictionary["path"] as? String, 21 | let thumbnailURLString = dictionary["thumbnail"] as? String, 22 | let thumbnailURL = URL(string: thumbnailURLString) else { 23 | return nil 24 | } 25 | 26 | self.isFolder = isFolder 27 | self.name = name 28 | 29 | if isFolder { 30 | // Ensure items representing folders contain a trailing slash. 31 | // Sometimes, results from some providers (e.g. using GitHub) do not include it. 32 | self.path = path.last == "/" ? path : "\(path)/" 33 | } else { 34 | self.path = path 35 | } 36 | 37 | self.thumbnailURL = thumbnailURL 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoEditor/EditionController/Enums/ImageEditorCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageEditorCommand.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 7/4/19. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum ImageEditorCommand { 12 | case rotate(clockwise: Bool) 13 | case crop(insets: UIEdgeInsets) 14 | case circled(center: CGPoint, radius: CGFloat) 15 | case undo 16 | case redo 17 | case reset 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoEditor/EditionController/ImageEditor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageEditor.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 7/4/19. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit.UIImage 10 | 11 | class ImageEditor { 12 | private var editedImages: [UIImage] = [] 13 | private var undoneImages: [UIImage] = [] 14 | 15 | public let originalImage: UIImage 16 | 17 | public var editedImage: UIImage { 18 | return editedImages.last ?? originalImage 19 | } 20 | 21 | init?(image: UIImage) { 22 | guard let ciImageBackedCopy = image.ciImageBackedCopy() else { return nil } 23 | 24 | originalImage = ciImageBackedCopy 25 | } 26 | } 27 | 28 | // MARK: - Image Transform Commands 29 | 30 | extension ImageEditor { 31 | func rotate(clockwise: Bool) { 32 | if let rotatedImage = editedImage.rotated(clockwise: clockwise) { 33 | editedImages.append(rotatedImage) 34 | undoneImages.removeAll() 35 | } 36 | } 37 | 38 | func crop(insets: UIEdgeInsets) { 39 | if let croppedImage = editedImage.cropped(by: insets) { 40 | editedImages.append(croppedImage) 41 | undoneImages.removeAll() 42 | } 43 | } 44 | 45 | func cropCircled(center: CGPoint, radius: CGFloat) { 46 | if let cropCircledImage = editedImage.circled(center: center, radius: radius) { 47 | editedImages.append(cropCircledImage) 48 | undoneImages.removeAll() 49 | } 50 | } 51 | } 52 | 53 | // MARK: - Image Undo, Redo & Reset Commands 54 | 55 | extension ImageEditor { 56 | func undo() { 57 | if canUndo() { 58 | undoneImages.append(editedImages.removeLast()) 59 | } 60 | } 61 | 62 | func redo() { 63 | if canRedo() { 64 | editedImages.append(undoneImages.removeLast()) 65 | } 66 | } 67 | 68 | func canUndo() -> Bool { 69 | return !editedImages.isEmpty 70 | } 71 | 72 | func canRedo() -> Bool { 73 | return !undoneImages.isEmpty 74 | } 75 | 76 | func reset() { 77 | editedImages.removeAll() 78 | undoneImages.removeAll() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoEditor/EditionController/Layers/CircleLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CropLayer.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 12/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CircleLayer: CALayer { 12 | var imageFrame = CGRect.zero { 13 | didSet { 14 | updateSublayers() 15 | } 16 | } 17 | 18 | var circleRadius: CGFloat = 0 { 19 | didSet { 20 | updateSublayers() 21 | } 22 | } 23 | 24 | var circleCenter = CGPoint.zero { 25 | didSet { 26 | updateSublayers() 27 | } 28 | } 29 | 30 | override init() { 31 | super.init() 32 | addSublayer(outsideLayer) 33 | } 34 | 35 | required init?(coder _: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | 39 | private lazy var outsideLayer: CAShapeLayer = { 40 | let layer = CAShapeLayer() 41 | layer.path = outsidePath 42 | layer.fillRule = .evenOdd 43 | layer.backgroundColor = UIColor.black.cgColor 44 | layer.opacity = 0.7 45 | return layer 46 | }() 47 | } 48 | 49 | /// :nodoc: 50 | private extension CircleLayer { 51 | func updateSublayers() { 52 | outsideLayer.path = outsidePath 53 | } 54 | 55 | var outsidePath: CGPath { 56 | let origin = CGPoint(x: circleCenter.x - circleRadius, y: circleCenter.y - circleRadius) 57 | let rect = CGRect(origin: origin, size: CGSize(width: circleRadius * 2, height: circleRadius * 2)) 58 | let path = UIBezierPath(ovalIn: rect) 59 | path.append(UIBezierPath(rect: imageFrame)) 60 | return path.cgPath 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoEditor/EditionController/Layers/CropLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CropLayer.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 12/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CropLayer: CALayer { 12 | var imageFrame = CGRect.zero { 13 | didSet { 14 | updateSublayers() 15 | } 16 | } 17 | 18 | var cropRect = CGRect.zero { 19 | didSet { 20 | updateSublayers() 21 | } 22 | } 23 | 24 | override init() { 25 | super.init() 26 | addSublayer(outsideLayer) 27 | addSublayer(gridLayer) 28 | addSublayer(cornersLayer) 29 | } 30 | 31 | required init?(coder _: NSCoder) { 32 | fatalError("init(coder:) has not been implemented") 33 | } 34 | 35 | private lazy var outsideLayer: CAShapeLayer = { 36 | let layer = CAShapeLayer() 37 | layer.path = outsidePath 38 | layer.fillRule = .evenOdd 39 | layer.backgroundColor = UIColor.black.cgColor 40 | layer.opacity = 0.5 41 | return layer 42 | }() 43 | 44 | private lazy var gridLayer: CAShapeLayer = { 45 | let layer = CAShapeLayer() 46 | layer.path = gridPath 47 | layer.lineWidth = 0.5 48 | layer.strokeColor = UIColor.white.cgColor 49 | layer.backgroundColor = UIColor.clear.cgColor 50 | layer.fillColor = UIColor.clear.cgColor 51 | return layer 52 | }() 53 | 54 | private lazy var cornersLayer: CAShapeLayer = { 55 | let layer = CAShapeLayer() 56 | layer.path = cornersPath 57 | layer.lineWidth = 2 58 | layer.strokeColor = UIColor.white.cgColor 59 | layer.backgroundColor = UIColor.clear.cgColor 60 | layer.fillColor = UIColor.clear.cgColor 61 | return layer 62 | }() 63 | } 64 | 65 | /// :nodoc: 66 | private extension CropLayer { 67 | func updateSublayers() { 68 | outsideLayer.path = outsidePath 69 | gridLayer.path = gridPath 70 | cornersLayer.path = cornersPath 71 | } 72 | 73 | var outsidePath: CGPath { 74 | let path = UIBezierPath(rect: cropRect) 75 | path.append(UIBezierPath(rect: imageFrame)) 76 | return path.cgPath 77 | } 78 | 79 | var gridPath: CGPath { 80 | let gridWidth = cropRect.size.width / 3 81 | let gridHeight = cropRect.size.height / 3 82 | let path = UIBezierPath(rect: cropRect) 83 | path.move(to: cropRect.origin.movedBy(x: gridWidth)) 84 | path.addLine(to: path.currentPoint.movedBy(y: gridHeight * 3)) 85 | path.move(to: cropRect.origin.movedBy(x: gridWidth * 2)) 86 | path.addLine(to: path.currentPoint.movedBy(y: gridHeight * 3)) 87 | path.move(to: cropRect.origin.movedBy(y: gridHeight)) 88 | path.addLine(to: path.currentPoint.movedBy(x: gridWidth * 3)) 89 | path.move(to: cropRect.origin.movedBy(y: gridHeight * 2)) 90 | path.addLine(to: path.currentPoint.movedBy(x: gridWidth * 3)) 91 | return path.cgPath 92 | } 93 | 94 | var cornersPath: CGPath { 95 | let thickness: CGFloat = 2 96 | let lenght: CGFloat = 20 97 | let horizontalWidth = min(lenght, cropRect.size.width) + thickness 98 | let verticalWidth = min(lenght, cropRect.size.height) 99 | let margin = UIEdgeInsets(top: -thickness / 2, left: -thickness / 2, bottom: -thickness / 2, right: -thickness / 2) 100 | let outerRect = cropRect.inset(by: margin) 101 | let path = UIBezierPath() 102 | path.move(to: outerRect.origin.movedBy(y: verticalWidth)) 103 | path.addLine(to: path.currentPoint.movedBy(y: -verticalWidth)) 104 | path.addLine(to: path.currentPoint.movedBy(x: horizontalWidth)) 105 | path.move(to: outerRect.origin.movedBy(x: outerRect.size.width - horizontalWidth)) 106 | path.addLine(to: path.currentPoint.movedBy(x: horizontalWidth)) 107 | path.addLine(to: path.currentPoint.movedBy(y: verticalWidth)) 108 | path.move(to: outerRect.origin.movedBy(x: outerRect.size.width, y: outerRect.size.height - verticalWidth)) 109 | path.addLine(to: path.currentPoint.movedBy(y: verticalWidth)) 110 | path.addLine(to: path.currentPoint.movedBy(x: -horizontalWidth)) 111 | path.move(to: outerRect.origin.movedBy(y: outerRect.size.height - verticalWidth)) 112 | path.addLine(to: path.currentPoint.movedBy(y: verticalWidth)) 113 | path.addLine(to: path.currentPoint.movedBy(x: horizontalWidth)) 114 | return path.cgPath 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoEditor/EditionController/ViewController/EditorViewController+EditDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorViewController+EditDataSource.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 23/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import AVFoundation 10 | import UIKit 11 | 12 | protocol EditDataSource: AnyObject { 13 | var imageFrame: CGRect { get } 14 | var imageSize: CGSize { get } 15 | var imageOrigin: CGPoint { get } 16 | var imageActualSize: CGSize { get } 17 | } 18 | 19 | extension EditorViewController: EditDataSource { 20 | var imageFrame: CGRect { 21 | return AVMakeRect(aspectRatio: imageActualSize, insideRect: imageView.bounds) 22 | } 23 | 24 | var imageSize: CGSize { 25 | return imageFrame.size 26 | } 27 | 28 | var imageOrigin: CGPoint { 29 | return imageFrame.origin 30 | } 31 | 32 | var imageActualSize: CGSize { 33 | return editor?.editedImage.size ?? .zero 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoEditor/EditionController/ViewController/EditorViewController+ToolbarDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorViewController+ToolbarDelegate.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 23/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension EditorViewController: EditorToolbarDelegate { 12 | func cancelSelected() { 13 | dismiss(animated: true) { 14 | self.completion?(nil) 15 | } 16 | } 17 | 18 | func rotateSelected() { 19 | perform(command: .rotate(clockwise: false)) 20 | cropHandler.rotateCounterClockwise() 21 | circleHandler.rotateCounterClockwise() 22 | } 23 | 24 | func cropSelected() { 25 | switch editMode { 26 | case .crop: editMode = .none 27 | case .circle, .none: editMode = .crop 28 | } 29 | } 30 | 31 | func circleSelected() { 32 | switch editMode { 33 | case .circle: editMode = .none 34 | case .crop, .none: editMode = .circle 35 | } 36 | } 37 | 38 | func saveSelected() { 39 | switch editMode { 40 | case .crop: perform(command: .crop(insets: cropHandler.actualEdgeInsets)) 41 | case .circle: perform(command: .circled(center: circleHandler.actualCenter, radius: circleHandler.actualRadius)) 42 | case .none: return 43 | } 44 | 45 | editMode = .none 46 | } 47 | 48 | func doneSelected() { 49 | dismiss(animated: true) { 50 | let editedImage = self.editor?.editedImage.cgImageBackedCopy() 51 | self.editor = nil 52 | self.completion?(editedImage) 53 | } 54 | } 55 | 56 | func undoSelected() { 57 | perform(command: .undo) 58 | } 59 | 60 | func redoSelected() { 61 | perform(command: .redo) 62 | } 63 | 64 | // MARK: - Private Functions 65 | 66 | private func perform(command: ImageEditorCommand) { 67 | guard let editor = editor else { return } 68 | 69 | switch command { 70 | case let .rotate(clockwise): 71 | editor.rotate(clockwise: clockwise) 72 | case let .crop(insets): 73 | editor.crop(insets: insets) 74 | case let .circled(center, radius): 75 | editor.cropCircled(center: center, radius: radius) 76 | case .undo: 77 | editor.undo() 78 | case .redo: 79 | editor.redo() 80 | case .reset: 81 | editor.reset() 82 | } 83 | 84 | imageView.image = editor.editedImage 85 | editMode = .none 86 | cropHandler.reset() 87 | circleHandler.reset() 88 | topToolbar.setActions(showUndo: editor.canUndo(), showRedo: editor.canRedo()) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoEditor/EditionController/ViewController/EditorViewController+ViewSetup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorViewController+ViewSetup.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 23/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension EditorViewController { 12 | func setupGestureRecognizer() { 13 | panGestureRecognizer.delegate = self 14 | panGestureRecognizer.addTarget(self, action: #selector(handlePanGesture(recognizer:))) 15 | pinchGestureRecognizer.delegate = self 16 | pinchGestureRecognizer.addTarget(self, action: #selector(handlePinchGesture(recognizer:))) 17 | imageView.isUserInteractionEnabled = true 18 | imageView.addGestureRecognizer(panGestureRecognizer) 19 | imageView.addGestureRecognizer(pinchGestureRecognizer) 20 | } 21 | 22 | func setupView() { 23 | view.backgroundColor = backgroundColor 24 | bottomToolbar.editorDelegate = self 25 | topToolbar.editorDelegate = self 26 | setupImageView() 27 | setupPreview() 28 | connectViews() 29 | } 30 | } 31 | 32 | private extension EditorViewController { 33 | var backgroundColor: UIColor { 34 | return UIColor(white: 31 / 255, alpha: 1) 35 | } 36 | 37 | func setupImageView() { 38 | imageView.image = editor?.editedImage 39 | imageView.isOpaque = false 40 | imageView.contentMode = .redraw 41 | preview.addSubview(imageClearBackground) 42 | imageClearBackground.backgroundColor = UIColor(patternImage: .fromFilestackBundle("clear-pattern")) 43 | imageClearBackground.frame = imageFrame.applying(CGAffineTransform(translationX: 4, y: 4)) 44 | } 45 | 46 | func setupPreview() { 47 | preview.backgroundColor = backgroundColor 48 | } 49 | 50 | func connectViews() { 51 | connectBottomToolbar() 52 | connectTopToolbar() 53 | connectPreview() 54 | } 55 | 56 | func connectBottomToolbar() { 57 | view.fill(with: bottomToolbar, connectingEdges: [.bottom], withSafeAreaRespecting: true) 58 | view.fill(with: bottomToolbar, connectingEdges: [.left, .right], withSafeAreaRespecting: false) 59 | bottomToolbar.heightAnchor.constraint(equalToConstant: 44).isActive = true 60 | } 61 | 62 | func connectTopToolbar() { 63 | view.fill(with: topToolbar, connectingEdges: [.left, .right], withSafeAreaRespecting: false) 64 | topToolbar.bottomAnchor.constraint(equalTo: bottomToolbar.topAnchor).isActive = true 65 | topToolbar.heightAnchor.constraint(equalToConstant: 44).isActive = true 66 | } 67 | 68 | func connectPreview() { 69 | preview.fill(with: imageView, inset: 4) 70 | view.fill(with: preview, connectingEdges: [.top, .left, .right], withSafeAreaRespecting: true) 71 | preview.bottomAnchor.constraint(equalTo: topToolbar.topAnchor).isActive = true 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoEditor/EditionController/Views/ImageEditorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageEditorView.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 7/4/19. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImageEditorView: UIView { 12 | weak var image: UIImage? { 13 | didSet { 14 | cgBackedImage = image?.cgImageBackedCopy() 15 | setNeedsDisplay() 16 | } 17 | } 18 | 19 | // CIImage-backed UIImages are not rendered correctly in iOS 13 if they have some transformations 20 | // applied (e.g. cropping), so we use a CGImage-backed UIImage instead for rendering. 21 | private var cgBackedImage: UIImage? 22 | 23 | override func draw(_: CGRect) { 24 | guard let image = cgBackedImage else { return } 25 | 26 | // Clear background 27 | UIColor.clear.setFill() 28 | UIRectFill(bounds) 29 | 30 | // Calculate image draw rect 31 | let imageRect = CGRect(origin: CGPoint.zero, size: image.size) 32 | let ratio = max(imageRect.width / bounds.width, imageRect.height / bounds.height) 33 | let size = CGSize(width: (imageRect.width / ratio).rounded(), height: (imageRect.height / ratio).rounded()) 34 | let origin = CGPoint(x: (bounds.midX - (size.width / 2)).rounded(), y: (bounds.midY - (size.height / 2)).rounded()) 35 | let drawRect = CGRect(origin: origin, size: size) 36 | 37 | // Draw image 38 | image.draw(in: drawRect) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoEditor/SelectionList/SelectionListViewController+FlowLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectionListViewController+FlowLayout.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 24/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension SelectionListViewController: UICollectionViewDelegateFlowLayout { 12 | func collectionView(_: UICollectionView, 13 | layout _: UICollectionViewLayout, 14 | insetForSectionAt _: Int) -> UIEdgeInsets { 15 | return UIEdgeInsets(top: cellSpacing, left: cellSpacing, bottom: cellSpacing, right: cellSpacing) 16 | } 17 | 18 | func collectionView(_: UICollectionView, 19 | layout _: UICollectionViewLayout, 20 | sizeForItemAt _: IndexPath) -> CGSize { 21 | return cellSize 22 | } 23 | 24 | func collectionView(_: UICollectionView, 25 | layout _: UICollectionViewLayout, 26 | minimumLineSpacingForSectionAt _: Int) -> CGFloat { 27 | return cellSpacing 28 | } 29 | 30 | func collectionView(_: UICollectionView, 31 | layout _: UICollectionViewLayout, 32 | minimumInteritemSpacingForSectionAt _: Int) -> CGFloat { 33 | return cellSpacing 34 | } 35 | } 36 | 37 | private extension SelectionListViewController { 38 | var cellSize: CGSize { 39 | return CGSize(width: cellSide, height: cellSide) 40 | } 41 | 42 | var cellSide: CGFloat { 43 | let totalSpacing = cellSpacing * (columnsCount + 1) 44 | return (totalWidth - totalSpacing) / columnsCount 45 | } 46 | 47 | var totalWidth: CGFloat { 48 | return view.safeAreaLayoutGuide.layoutFrame.width 49 | } 50 | 51 | var columnsCount: CGFloat { 52 | return (totalWidth / targetSide).rounded(.down) 53 | } 54 | 55 | var targetSide: CGFloat { 56 | return 100.0 57 | } 58 | 59 | var cellSpacing: CGFloat { 60 | return 6 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoEditor/SelectionList/SelectionListViewController+UICollectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectionListViewController+UICollectionView.swift 3 | // EditImage 4 | // 5 | // Created by Mihály Papp on 26/07/2018. 6 | // Copyright © 2018 Mihály Papp. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension SelectionListViewController { 12 | override func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int { 13 | return numberOfCells 14 | } 15 | 16 | override func collectionView(_ collectionView: UICollectionView, 17 | cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 18 | guard let cell = collectionView.reuse(SelectionCell.self, for: indexPath) else { return UICollectionViewCell() } 19 | cellWasDisplayed(cell, on: indexPath.row) 20 | return cell 21 | } 22 | 23 | override func collectionView(_: UICollectionView, didSelectItemAt indexPath: IndexPath) { 24 | cellWasPressed(on: indexPath.row) 25 | } 26 | } 27 | 28 | // MARK: LongPress handling 29 | 30 | extension SelectionListViewController { 31 | override func collectionView(_: UICollectionView, 32 | shouldShowMenuForItemAt indexPath: IndexPath) -> Bool { 33 | cellWasLongPressed(on: indexPath.row) 34 | return true 35 | } 36 | 37 | override func collectionView(_: UICollectionView, 38 | canPerformAction _: Selector, 39 | forItemAt _: IndexPath, 40 | withSender _: Any?) -> Bool { 41 | return false 42 | } 43 | 44 | override func collectionView(_: UICollectionView, 45 | performAction _: Selector, 46 | forItemAt _: IndexPath, 47 | withSender _: Any?) { 48 | return 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoEditor/SelectionList/Uploadable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Uploadable.swift 3 | // Filestack 4 | // 5 | // Created by Mihály Papp on 30/07/2018. 6 | // Copyright © 2018 Filestack. All rights reserved. 7 | // 8 | 9 | import AVFoundation 10 | import UIKit 11 | 12 | protocol Uploadable: AnyObject { 13 | var isEditable: Bool { get } 14 | var associatedImage: UIImage { get } 15 | var typeIcon: UIImage { get } 16 | var additionalInfo: String? { get } 17 | } 18 | 19 | extension UIImage: Uploadable { 20 | var isEditable: Bool { true } 21 | var associatedImage: UIImage { self } 22 | var typeIcon: UIImage { UIImage.fromFilestackBundle("icon-image").withRenderingMode(.alwaysTemplate) } 23 | var additionalInfo: String? { nil } 24 | } 25 | 26 | extension AVAsset: Uploadable { 27 | var isEditable: Bool { false } 28 | 29 | var associatedImage: UIImage { 30 | let beginning = CMTime(seconds: 0, preferredTimescale: 1) 31 | do { 32 | let cgImage = try AVAssetImageGenerator(asset: self).copyCGImage(at: beginning, actualTime: nil) 33 | return UIImage(cgImage: cgImage) 34 | } catch _ { 35 | return UIImage() // TODO: return placeholder 36 | } 37 | } 38 | 39 | var typeIcon: UIImage { UIImage.fromFilestackBundle("icon-file-video").withRenderingMode(.alwaysTemplate) } 40 | 41 | var additionalInfo: String? { DurationFormatter().string(from: duration.seconds) } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoPicker/AlbumList/AlbumCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumListCell.swift 3 | // Filestack 4 | // 5 | // Created by Mihály Papp on 23/05/2018. 6 | // Copyright © 2018 Filestack. All rights reserved. 7 | // 8 | 9 | import Photos 10 | import UIKit 11 | 12 | class AlbumCell: UITableViewCell { 13 | @IBOutlet var coverImage: UIImageView! 14 | @IBOutlet var titleLabel: UILabel! 15 | 16 | private var requestIDs: [PHImageRequestID] = [] 17 | 18 | func configure(for album: Album) { 19 | selectionStyle = .none 20 | titleLabel.text = album.title 21 | coverImage.contentMode = .scaleAspectFill 22 | coverImage.clipsToBounds = true 23 | 24 | let requestID = album.elements.last?.fetchImage(forSize: coverImage.frame.size) { image, requestID in 25 | self.requestIDs.removeAll { $0 == requestID } 26 | 27 | DispatchQueue.main.async { self.coverImage.image = image } 28 | } 29 | 30 | if let requestID = requestID { 31 | requestIDs.append(requestID) 32 | } 33 | } 34 | 35 | deinit { 36 | for requestID in requestIDs { 37 | PHImageManager.default().cancelImageRequest(requestID) 38 | } 39 | 40 | requestIDs.removeAll() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoPicker/AlbumList/AlbumListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumListViewController.swift 3 | // Filestack 4 | // 5 | // Created by Mihály Papp on 23/05/2018. 6 | // Copyright © 2018 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AlbumListViewController: UITableViewController { 12 | weak var pickerController: PhotoPickerController? 13 | 14 | private var activityIndicator: UIActivityIndicatorView? 15 | private var albumList = [Album]() 16 | 17 | var repository: PhotoAlbumRepository? { 18 | return pickerController?.albumRepository 19 | } 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | setupView() 24 | } 25 | } 26 | 27 | private extension AlbumListViewController { 28 | func setupView() { 29 | setupNavigation() 30 | configureAndShowEmptyTableView() 31 | createAndStartLaodingView() 32 | fetchData() 33 | } 34 | 35 | func setupNavigation() { 36 | if let pickerController = pickerController { 37 | navigationItem.leftBarButtonItem = pickerController.cancelBarButton 38 | navigationItem.rightBarButtonItems = pickerController.rightBarItems 39 | } 40 | } 41 | } 42 | 43 | private extension AlbumListViewController { 44 | func set(_ albums: [Album]) { 45 | albumList = albums 46 | stopLoading() 47 | if !albumList.isEmpty { 48 | configureTableViewWithContent() 49 | } 50 | DispatchQueue.main.async { 51 | self.tableView.reloadData() 52 | } 53 | } 54 | 55 | func fetchData() { 56 | repository?.getAlbums { albums in self.set(albums) } 57 | } 58 | } 59 | 60 | // MARK: - Empty Table 61 | 62 | private extension AlbumListViewController { 63 | func configureAndShowEmptyTableView() { 64 | DispatchQueue.main.async { 65 | self.tableView.backgroundView = UIView(frame: self.tableView.frame) 66 | self.tableView.separatorStyle = .none 67 | } 68 | } 69 | 70 | func configureTableViewWithContent() { 71 | DispatchQueue.main.async { 72 | self.tableView.separatorStyle = .singleLine 73 | } 74 | } 75 | } 76 | 77 | // MARK: - Loading Indicator 78 | 79 | private extension AlbumListViewController { 80 | func createAndStartLaodingView() { 81 | DispatchQueue.main.async { 82 | let indicator = UIActivityIndicatorView(style: .medium) 83 | indicator.center = self.view.center 84 | indicator.startAnimating() 85 | self.view.addSubview(indicator) 86 | self.activityIndicator = indicator 87 | } 88 | } 89 | 90 | func stopLoading() { 91 | DispatchQueue.main.async { 92 | self.activityIndicator?.stopAnimating() 93 | self.activityIndicator?.removeFromSuperview() 94 | } 95 | } 96 | } 97 | 98 | // MARK: - TableView Delegate & DataSource 99 | 100 | extension AlbumListViewController { 101 | override func numberOfSections(in _: UITableView) -> Int { 102 | return 1 103 | } 104 | 105 | override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { 106 | return albumList.count 107 | } 108 | 109 | override func tableView(_ tableView: UITableView, 110 | cellForRowAt indexPath: IndexPath) -> UITableViewCell { 111 | guard let cell = tableView.dequeueReusableCell(withIdentifier: "AlbumCell") as? AlbumCell else { 112 | return UITableViewCell() 113 | } 114 | let album = albumList[indexPath.row] 115 | cell.configure(for: album) 116 | return cell 117 | } 118 | 119 | override func tableView(_: UITableView, viewForFooterInSection _: Int) -> UIView? { 120 | return UIView() 121 | } 122 | 123 | override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) { 124 | let album = albumList[indexPath.row] 125 | 126 | if let collectionView = pickerController?.assetCollection { 127 | collectionView.configure(with: album) 128 | navigationController?.pushViewController(collectionView, animated: true) 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoPicker/AssetCollection/AssetCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetCell.swift 3 | // Filestack 4 | // 5 | // Created by Mihály Papp on 04/06/2018. 6 | // Copyright © 2018 Filestack. All rights reserved. 7 | // 8 | 9 | import Photos 10 | import UIKit 11 | 12 | class AssetCell: UICollectionViewCell { 13 | @IBOutlet var image: UIImageView! 14 | @IBOutlet var selectedCheckbox: UIImageView! 15 | @IBOutlet var dimView: UIView! 16 | @IBOutlet var additionalInfoLabel: UILabel! 17 | private lazy var gradientLayer = CAGradientLayer() 18 | 19 | private var asset: PHAsset! 20 | private var requestIDs: [PHImageRequestID] = [] 21 | private let uploadableExtractor = UploadableExtractor() 22 | 23 | override func layoutSubviews() { 24 | super.layoutSubviews() 25 | gradientLayer.frame = image.frame 26 | } 27 | 28 | func configure(for asset: PHAsset, isSelected: Bool) { 29 | self.asset = asset 30 | 31 | requestIDs.append(asset.fetchImage(forSize: image.frame.size) { image, requestID in 32 | self.requestIDs.removeAll { $0 == requestID } 33 | 34 | DispatchQueue.main.async { 35 | self.configure(with: image) 36 | self.set(selected: isSelected) 37 | } 38 | }) 39 | 40 | if asset.mediaType == .video { 41 | if let requestID = (uploadableExtractor.fetchUploadable(using: asset) { uploadable, requestID in 42 | self.requestIDs.removeAll { $0 == requestID } 43 | 44 | DispatchQueue.main.async { 45 | self.additionalInfoLabel.text = uploadable?.additionalInfo 46 | self.setupGradientLayer() 47 | } 48 | }) { 49 | requestIDs.append(requestID) 50 | } 51 | } 52 | } 53 | 54 | deinit { 55 | for requestID in requestIDs { 56 | uploadableExtractor.cancelFetch(using: requestID) 57 | } 58 | 59 | requestIDs.removeAll() 60 | } 61 | 62 | func set(selected: Bool) { 63 | dimView.isHidden = !selected 64 | selectedCheckbox.isHidden = !selected 65 | } 66 | } 67 | 68 | private extension AssetCell { 69 | func configure(with image: UIImage?) { 70 | self.image.image = image 71 | selectedCheckbox.image = UIImage(named: "icon-selected", in: bundle, compatibleWith: nil) 72 | } 73 | 74 | func setupGradientLayer() { 75 | image.layer.insertSublayer(gradientLayer, at: 0) 76 | gradientLayer.colors = [UIColor.clear.cgColor, UIColor.clear.cgColor, UIColor(white: 0.55, alpha: 0.6).cgColor] 77 | gradientLayer.locations = [NSNumber(value: 0), NSNumber(value: 0.6), NSNumber(value: 1)] 78 | gradientLayer.startPoint = CGPoint(x: 1, y: 0) 79 | gradientLayer.endPoint = CGPoint(x: 0, y: 1) 80 | gradientLayer.frame = image.frame 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoPicker/PhotoAlbumRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoAlbumRepository.swift 3 | // Filestack 4 | // 5 | // Created by Mihály Papp on 29/05/2018. 6 | // Copyright © 2018 Filestack. All rights reserved. 7 | // 8 | 9 | import Photos 10 | 11 | struct Album { 12 | let title: String 13 | let elements: [PHAsset] 14 | } 15 | 16 | class PhotoAlbumRepository { 17 | private var cachedAlbums: [Album]? 18 | 19 | init() { 20 | fetchAndCacheAlbums(completion: nil) 21 | } 22 | 23 | func getAlbums(completion: @escaping ([Album]) -> Void) { 24 | if let cachedAlbums = cachedAlbums { 25 | completion(cachedAlbums) 26 | } 27 | fetchAndCacheAlbums(completion: completion) 28 | } 29 | 30 | private func fetchAndCacheAlbums(completion: (([Album]) -> Void)?) { 31 | DispatchQueue.global(qos: .default).async { 32 | let collections = PHAssetCollection.allCollections(types: [.smartAlbum, .album]) 33 | let allAlbums = collections.map { Album(title: $0.localizedTitle ?? "", elements: $0.allAssets) } 34 | let nonEmptyAlbums = allAlbums.filter { !$0.elements.isEmpty } 35 | self.cachedAlbums = nonEmptyAlbums 36 | completion?(nonEmptyAlbums) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoPicker/PhotoPickerController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoPickerController.swift 3 | // Filestack 4 | // 5 | // Created by Mihály Papp on 23/05/2018. 6 | // Copyright © 2018 Filestack. All rights reserved. 7 | // 8 | 9 | import Photos 10 | import UIKit 11 | 12 | protocol PhotoPickerControllerDelegate: AnyObject { 13 | func photoPickerControllerDidCancel(controller: UINavigationController) 14 | func photoPicker(controller: UINavigationController, didSelectAssets assets: [PHAsset]) 15 | } 16 | 17 | class PhotoPickerController { 18 | // MARK: - Internal Properties 19 | 20 | let albumRepository = PhotoAlbumRepository() 21 | let maximumSelectionAllowed: UInt 22 | var isMaximumLimitSet: Bool { maximumSelectionAllowed != Config.kMaximumSelectionNoLimit } 23 | var selectedAssets = Set() 24 | 25 | weak var delegate: PhotoPickerControllerDelegate? 26 | 27 | var assetCollection: AssetCollectionViewController { 28 | let vc = viewController(with: "AssetCollectionViewController") 29 | 30 | guard let assetCollection = vc as? AssetCollectionViewController else { 31 | fatalError("AssetCollectionViewController type is corrupted") 32 | } 33 | 34 | assetCollection.pickerController = self 35 | 36 | return assetCollection 37 | } 38 | 39 | lazy var navigation = UINavigationController(rootViewController: albumList) 40 | 41 | var cancelBarButton: UIBarButtonItem { 42 | return UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismissWithoutSelection)) 43 | } 44 | 45 | var rightBarItems: [UIBarButtonItem] { 46 | guard !selectedAssets.isEmpty else { return [] } 47 | 48 | return [selectionCountBarButton, doneBarButton] 49 | } 50 | 51 | var doneBarButton: UIBarButtonItem { 52 | return UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissWithSelection)) 53 | } 54 | 55 | var selectionCountBarButton: UIBarButtonItem { 56 | let maximum = isMaximumLimitSet ? "/\(maximumSelectionAllowed)" : "" 57 | let title = "(\(selectedAssets.count)\(maximum))" 58 | 59 | return UIBarButtonItem(title: title, style: .done, target: self, action: #selector(dismissWithSelection)) 60 | } 61 | 62 | // MARK: - Private Properties 63 | 64 | private lazy var albumList: AlbumListViewController = { 65 | let vc = viewController(with: "AlbumListViewController") 66 | 67 | guard let albumList = vc as? AlbumListViewController else { 68 | fatalError("AlbumListViewController type is corrupted") 69 | } 70 | 71 | albumList.pickerController = self 72 | 73 | return albumList 74 | }() 75 | 76 | 77 | // MARK: - Lifecycle 78 | 79 | init(maximumSelection: UInt) { 80 | maximumSelectionAllowed = maximumSelection 81 | 82 | albumRepository.getAlbums { _ in 83 | DispatchQueue.main.async { 84 | self.albumList.tableView.reloadData() 85 | } 86 | } 87 | } 88 | } 89 | 90 | // MARK: - Private Functions 91 | 92 | private extension PhotoPickerController { 93 | func viewController(with name: String) -> UIViewController { 94 | let storyboard = UIStoryboard(name: "PhotoPicker", bundle: bundle) 95 | 96 | return storyboard.instantiateViewController(withIdentifier: name) 97 | } 98 | } 99 | 100 | // MARK: - Navigation Bar Actions 101 | 102 | private extension PhotoPickerController { 103 | @objc func dismissWithSelection() { 104 | delegate?.photoPicker(controller: navigation, didSelectAssets: Array(selectedAssets)) 105 | } 106 | 107 | @objc func dismissWithoutSelection() { 108 | delegate?.photoPickerControllerDidCancel(controller: navigation) 109 | } 110 | } 111 | 112 | // MARK: - AssetSelectionDelegate Conformance 113 | 114 | extension PhotoPickerController: AssetSelectionDelegate { 115 | func add(asset: PHAsset) { 116 | selectedAssets.insert(asset) 117 | 118 | if maximumSelectionAllowed == 1 { 119 | dismissWithSelection() 120 | } else { 121 | updateNavBar() 122 | } 123 | } 124 | 125 | func remove(asset: PHAsset) { 126 | selectedAssets.remove(asset) 127 | updateNavBar() 128 | } 129 | 130 | func updateNavBar() { 131 | for vc in navigation.viewControllers { 132 | vc.navigationItem.rightBarButtonItems = rightBarItems 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/PhotoPicker/PhotosExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotosExtensions.swift 3 | // Filestack 4 | // 5 | // Created by Mihály Papp on 29/05/2018. 6 | // Copyright © 2018 Filestack. All rights reserved. 7 | // 8 | 9 | import Photos 10 | import UIKit 11 | 12 | // MARK: - PHAsset Public Properties 13 | 14 | extension PHAsset { 15 | func fetchImage(forSize size: CGSize, completion: @escaping (UIImage?, PHImageRequestID) -> Void) -> PHImageRequestID { 16 | let getMaximumSize = (size == PHImageManagerMaximumSize) 17 | let scaledSize = getMaximumSize ? PHImageManagerMaximumSize : adjustToScreenScale(size: size) 18 | let manager = PHImageManager.default() 19 | 20 | var requestID: PHImageRequestID! 21 | 22 | requestID = manager.requestImage(for: self, 23 | targetSize: scaledSize, 24 | contentMode: .aspectFit, 25 | options: requestOptions) { image, _ in 26 | completion(image, requestID) 27 | } 28 | 29 | return requestID 30 | } 31 | } 32 | 33 | // MARK: - PHAsset Computed Properties 34 | 35 | private extension PHAsset { 36 | var requestOptions: PHImageRequestOptions { 37 | let option = PHImageRequestOptions() 38 | 39 | option.deliveryMode = .highQualityFormat 40 | option.isNetworkAccessAllowed = true 41 | option.isSynchronous = false 42 | 43 | return option 44 | } 45 | } 46 | 47 | // MARK: - PHAsset Private Functions 48 | 49 | private extension PHAsset { 50 | func adjustToScreenScale(size: CGSize) -> CGSize { 51 | let scale = UIScreen.main.scale 52 | 53 | return CGSize(width: size.width * scale, height: size.height * scale) 54 | } 55 | } 56 | 57 | // MARK: - PHAssetCollection Functions 58 | 59 | extension PHAssetCollection { 60 | class func allCollections(types: [PHAssetCollectionType]) -> [PHAssetCollection] { 61 | var allCollections = [PHAssetCollection]() 62 | 63 | for type in types { 64 | let fetch = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil) 65 | 66 | fetch.enumerateObjects { collection, _, _ in allCollections.append(collection) } 67 | } 68 | 69 | return allCollections 70 | } 71 | } 72 | 73 | // MARK: - PHAssetCollection Computed Properties 74 | 75 | extension PHAssetCollection { 76 | var allAssets: [PHAsset] { 77 | var allAssets = [PHAsset]() 78 | let fetch = PHAsset.fetchAssets(in: self, options: nil) 79 | 80 | fetch.enumerateObjects { asset, _, _ in allAssets.append(asset) } 81 | 82 | return allAssets 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Protocols/CellDescriptibleSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellDescriptibleSource.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/7/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol CellDescriptibleSource { 12 | var iconImage: UIImage { get } 13 | var textDescription: String { get } 14 | } 15 | 16 | func == (lhs: CellDescriptibleSource, rhs: CellDescriptibleSource) -> Bool { 17 | return lhs.textDescription == rhs.textDescription 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Protocols/CloudSourceDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloudSourceDataSource.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/16/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import FilestackSDK 10 | import MobileCoreServices.UTType 11 | import UIKit 12 | 13 | protocol CloudSourceDataSource: AnyObject { 14 | var client: Client! { get } 15 | var storeOptions: StorageOptions! { get } 16 | var source: CloudSource! { get } 17 | var path: String! { get set } 18 | var nextPageToken: String? { get set } 19 | var items: [CloudItem]? { get } 20 | var thumbnailCache: NSCache { get } 21 | 22 | func store(item: CloudItem) 23 | func navigate(to item: CloudItem) 24 | func loadNextPage(completionHandler: @escaping (() -> Void)) 25 | func search(text: String, completionHandler: @escaping (() -> Void)) 26 | func refresh(completionHandler: @escaping (() -> Void)) 27 | func cacheThumbnail(for item: CloudItem, completionHandler: @escaping ((UIImage) -> Void)) 28 | } 29 | 30 | extension CloudSourceDataSource { 31 | // Based on user config, can `item` be selected? 32 | func canSelect(item: CloudItem) -> Bool { 33 | let config = client.config 34 | 35 | if item.isFolder || config.cloudSourceAllowedUTIs.isEmpty { 36 | return true 37 | } 38 | 39 | guard let uti = item.name.UTI else { return false } 40 | 41 | // Try to find at least an UTI in `cloudSourceAllowedUTIs` that comforms to our item's UTI, 42 | // or return false if none match. 43 | for allowedUTI in config.cloudSourceAllowedUTIs { 44 | if UTTypeConformsTo(uti, allowedUTI as CFString) { 45 | return true 46 | } 47 | } 48 | 49 | return false 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Protocols/CloudSourceDataSourceConsumer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloudSourceDataSourceConsumer.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/17/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol CloudSourceDataSourceConsumer { 12 | func dataSourceReceivedInitialResults(dataSource: CloudSourceDataSource) 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Protocols/Scene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Scene.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/13/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol Scene { 12 | associatedtype ViewController 13 | 14 | var identifier: String { get } 15 | 16 | func configureViewController(_ viewController: ViewController) 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Scenes/CloudSourceBarTabScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloudSourceBarTabScene.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import FilestackSDK 10 | import Foundation 11 | 12 | struct CloudSourceTabBarScene: Scene { 13 | let client: Client 14 | let storeOptions: StorageOptions 15 | let source: CloudSource 16 | 17 | var customSourceName: String? 18 | var path: String? 19 | var nextPageToken: String? 20 | var viewType: CloudSourceViewType 21 | 22 | func configureViewController(_ viewController: CloudSourceTabBarController) { 23 | // Inject the dependencies 24 | viewController.client = client 25 | viewController.storeOptions = storeOptions 26 | viewController.source = source 27 | viewController.customSourceName = customSourceName 28 | viewController.nextPageToken = nextPageToken 29 | viewController.path = path ?? "/" 30 | viewController.viewType = viewType 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Scenes/PickerNavigationScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PickerNavigationScene.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import FilestackSDK 10 | import Foundation 11 | 12 | struct PickerNavigationScene: Scene { 13 | let client: Client 14 | let storeOptions: StorageOptions 15 | 16 | func configureViewController(_ viewController: PickerNavigationController) { 17 | viewController.client = client 18 | viewController.storeOptions = storeOptions 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Table View Cells/ActivityIndicatorTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActivityIndicatorTableViewCell.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/13/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ActivityIndicatorTableViewCell: UITableViewCell { 12 | @IBOutlet var activityIndicator: UIActivityIndicatorView! 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Internal/Table View Cells/CloudItemTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloudItemTableViewCell.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/17/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CloudItemTableViewCell: UITableViewCell {} 12 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Public/Controllers/PickerNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationController.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/8/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import FilestackSDK 10 | import UIKit 11 | 12 | /// This class represents a navigation controller containing UI elements that allow picking files from local and cloud 13 | /// sources. 14 | @objc(FSPickerNavigationController) public class PickerNavigationController: UINavigationController { 15 | var client: Client! 16 | var storeOptions: StorageOptions! 17 | 18 | /// This setting determines what should happen after picking files (see `PickerBehavior` for more information.) 19 | /// 20 | /// The default value is `.uploadAndStore` 21 | public var behavior: PickerBehavior = .uploadAndStore(uploadOptions: .defaults) 22 | 23 | /// Stylizer used for changing default colors and fonts. 24 | @objc public lazy var stylizer = Stylizer(delegate: self) 25 | 26 | /// The picker delegate. Optional 27 | @objc public weak var pickerDelegate: PickerNavigationControllerDelegate? 28 | 29 | deinit { 30 | pickerDelegate?.pickerWasDismissed?(picker: self) 31 | } 32 | } 33 | 34 | /// This protocol contains the function signatures any `PickerNavigationController` delegate should conform to. 35 | @objc(FSPickerNavigationControllerDelegate) public protocol PickerNavigationControllerDelegate: AnyObject { 36 | /// Called when the picker finishes picking files originating from the local device. 37 | @objc func pickerPickedFiles(picker: PickerNavigationController, fileURLs: [URL]) 38 | 39 | /// Called when the picker finishes storing a file originating from a cloud source into the storage destination. 40 | @objc func pickerStoredFile(picker: PickerNavigationController, response: StoreResponse) 41 | 42 | /// Called when the picker finishes uploading files originating from the local device to the storage destination. Optional. 43 | @objc optional func pickerUploadedFiles(picker: PickerNavigationController, responses: [JSONResponse]) 44 | 45 | /// Called when the picker reports progress during a file or set of files being uploaded. Optional. 46 | @objc optional func pickerReportedUploadProgress(picker: PickerNavigationController, progress: Float) 47 | 48 | /// Called after the picker was dismissed. Optional. 49 | @objc optional func pickerWasDismissed(picker: PickerNavigationController) 50 | } 51 | 52 | extension PickerNavigationController: StylizerDelegate { 53 | /// :nodoc: 54 | public func updateStyle() { 55 | navigationBar.tintColor = stylizer.navBar.tintColor 56 | navigationBar.titleTextAttributes = [.foregroundColor: stylizer.navBar.titleColor] 57 | navigationBar.barStyle = stylizer.navBar.style 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Public/Enums/LocalProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalProvider.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Represents a local provider to be used by a `LocalSource`. 12 | @objc(FSLocalProvider) public enum LocalProvider: UInt { 13 | /// Camera 14 | case camera 15 | 16 | /// Photo Library 17 | case photoLibrary 18 | 19 | /// Documents 20 | case documents 21 | 22 | /// Custom Source 23 | case customSource 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Public/Models/LocalSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalSource.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/7/17. 6 | // Copyright © 2017 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import FilestackSDK 11 | 12 | /// Represents a type of local source to be used in the picker. 13 | @objc(FSLocalSource) public class LocalSource: NSObject, CellDescriptibleSource { 14 | let provider: LocalProvider 15 | let iconImage: UIImage 16 | let textDescription: String 17 | let sourceProvider: SourceProvider? 18 | 19 | /// Initializer for a `LocalSource` accepting a `LocalProvider`. 20 | /// 21 | /// - Parameter description: A `String` describing the local source. 22 | /// - Parameter image: An `UIImage` visually describing the local source. 23 | /// - Parameter provider: A `LocalProvider` that better represents the local source. 24 | @objc public init(description: String, image: UIImage, provider: LocalProvider) { 25 | self.textDescription = description 26 | self.iconImage = image 27 | self.provider = provider 28 | self.sourceProvider = nil 29 | } 30 | 31 | /// Initializer for a `LocalSource` accepting a `SourceProvider`. 32 | /// 33 | /// - Parameter description: A `String` describing the local source. 34 | /// - Parameter image: An `UIImage` visually describing the local source. 35 | /// - Parameter sourceProvider: A `SourceProvider` that presents a custom user-provided view controller. 36 | public init(description: String, image: UIImage, sourceProvider: SourceProvider) { 37 | self.textDescription = description 38 | self.iconImage = image 39 | self.provider = .customSource 40 | self.sourceProvider = sourceProvider 41 | } 42 | 43 | /// Camera 44 | @objc public static var camera = LocalSource(description: "Camera", 45 | image: .templatedFilestackImage("icon-camera"), 46 | provider: .camera) 47 | 48 | /// Photo Library 49 | @objc public static var photoLibrary = LocalSource(description: "Photo Library", 50 | image: .templatedFilestackImage("icon-photolibrary"), 51 | provider: .photoLibrary) 52 | 53 | /// Documents 54 | @objc public static var documents = LocalSource(description: "iOS Files", 55 | image: .templatedFilestackImage("icon-documents"), 56 | provider: .documents) 57 | 58 | /// Returns all the supported sources. 59 | @objc public static func all() -> [LocalSource] { 60 | return [.camera, .photoLibrary, .documents] 61 | } 62 | 63 | /// Returns this source's title. 64 | @objc public static func title() -> String { 65 | return "Local" 66 | } 67 | 68 | /// Returns an user-provided local source that uses a custom `SourceProvider`. 69 | public static func custom(description: String, image: UIImage, provider: SourceProvider) -> LocalSource { 70 | return LocalSource(description: description, 71 | image: image, 72 | sourceProvider: provider) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Public/Protocols/SourceProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceProvider.swift 3 | // SourceProvider 4 | // 5 | // Created by Ruben Nine on 30/7/21. 6 | // Copyright © 2021 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// `SourceProvider` defines the protocol that must be implemented by any view controllers that should be used to 12 | /// pick files using an user-provided implementation. 13 | public protocol SourceProvider: UIViewController { 14 | /// Defines the source provider delegate. 15 | var sourceProviderDelegate: SourceProviderDelegate? { set get } 16 | 17 | /// Initializer for this source provider. 18 | init() 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Public/Protocols/SourceProviderDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceProviderDelegate.swift 3 | // SourceProviderDelegate 4 | // 5 | // Created by Ruben Nine on 30/7/21. 6 | // Copyright © 2021 Filestack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol SourceProviderDelegate: AnyObject { 12 | /// Called when one or more URLs were picked by this source provider. 13 | func sourceProviderPicked(urls: [URL]) 14 | 15 | /// Called when source provider is cancelled (either by the user or programmatically.) 16 | func sourceProviderCancelled() 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Filestack/UI/Public/Protocols/StylizerDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StylizerDelegate.swift 3 | // Filestack 4 | // 5 | // Created by Ruben Nine on 11/09/2019. 6 | // Copyright © 2019 Filestack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// :nodoc: 12 | @objc(FSStylizerDelegate) public protocol StylizerDelegate: AnyObject { 13 | @objc func updateStyle() 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Filestack/VERSION: -------------------------------------------------------------------------------- 1 | ../../VERSION -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 3.0.1 2 | -------------------------------------------------------------------------------- /bin/apply-swiftformat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! which swiftformat >/dev/null; then 4 | if which brew >/dev/null; then 5 | brew update && brew install swiftformat 6 | else 7 | echo "WARNING: Tried to automatically install SwiftFormat using `brew` but `brew` itself could not be found. Please install SwiftFormat manually and try again." 8 | fi 9 | fi 10 | 11 | if which swiftformat >/dev/null; then 12 | swiftformat . 13 | else 14 | echo "WARNING: SwiftFormat is missing. Please install it manually and try again." 15 | fi 16 | -------------------------------------------------------------------------------- /bin/generate-and-deploy-docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o errexit #abort if any command fails 3 | 4 | ./bin/generate-docs.sh 5 | 6 | GIT_DEPLOY_DIR=docs GIT_DEPLOY_BRANCH=gh-pages GIT_DEPLOY_REPO=git@github.com:filestack/filestack-ios.git ./bin/deploy-docs.sh 7 | -------------------------------------------------------------------------------- /bin/generate-docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o errexit #abort if any command fails 3 | 4 | swift doc generate Sources --module-name Filestack --format html --output docs --base-url https://filestack.github.io/filestack-ios/ 5 | -------------------------------------------------------------------------------- /bin/set-buildnumber.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # set-buildnumber.sh 4 | # Filestack 5 | # 6 | # Created by Ruben Nine on 7/5/17. 7 | # Copyright © 2017 Filestack. All rights reserved. 8 | 9 | infoplist="$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH" 10 | buildnum=$(git --git-dir="$SRCROOT/.git" log --oneline | wc -l | tr -d '[:space:]') 11 | 12 | if [ -z "$buildnum" ]; then 13 | echo "Failed to set buildNum." 14 | exit 1 15 | fi 16 | 17 | /usr/libexec/PlistBuddy -c "Set CFBundleVersion $buildnum" "$infoplist" 18 | -------------------------------------------------------------------------------- /bin/set-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # set-version.sh 4 | # Filestack 5 | # 6 | # Created by Ruben Nine on 7/5/17. 7 | # Copyright © 2017 Filestack. All rights reserved. 8 | 9 | infoplist="$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH" 10 | shortVersionString=$(cat $SRCROOT/VERSION) 11 | 12 | if [ -z "$shortVersionString" ]; then 13 | echo "Failed to set shortVersionString." 14 | exit 1 15 | fi 16 | 17 | /usr/libexec/PlistBuddy -c "Set CFBundleShortVersionString $shortVersionString" "$infoplist" 18 | --------------------------------------------------------------------------------