From bc2611c9857e30882d533e80745b6602f0f0a879 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Sat, 29 Apr 2017 19:04:47 +0300 Subject: [PATCH 001/111] Add custom crop --- .../Example/Presenter/ExamplePresenter.swift | 80 +++++++- .../Example/Router/ExampleRouter.swift | 15 +- .../Example/Router/ExampleRouterImpl.swift | 37 ++-- .../Example/View/ExampleView.swift | 42 ++++- .../Example/View/ExampleViewController.swift | 17 ++ .../Example/View/ExampleViewInput.swift | 6 + .../AppDelegate.swift | 4 +- Paparazzo/Core/DI/AssemblyFactory.swift | 36 ++++ Paparazzo/Core/DI/BasePaparazzoAssembly.swift | 11 ++ Paparazzo/Core/DI/ServiceFactory.swift | 28 +++ Paparazzo/Core/DisposeBag.swift | 28 +++ Paparazzo/Core/MediaPickerUITheme.swift | 76 ++------ Paparazzo/Core/PaparazzoViewController.swift | 14 ++ Paparazzo/Core/PhotoTweakView.swift | 51 +---- .../Services/Camera/CameraServiceImpl.swift | 34 +++- .../CircleCroppingOverlayProvider.swift | 21 +++ .../CroppingOverlayProvider.swift | 6 + .../CroppingOverlayProvidersFactory.swift | 23 +++ .../HeartShapeCroppingOverlayProvider.swift | 65 +++++++ .../RectangleCroppingOverlayProvider.swift | 33 ++++ .../ImageCropping/ImageCroppingService.swift | 87 +++++++++ Paparazzo/Core/ThemeConfigurable.swift | 5 + Paparazzo/Core/VIPER/AssemblyFactory.swift | 24 --- .../Camera/Assembly/CameraAssembly.swift | 2 +- .../Camera/Assembly/CameraAssemblyImpl.swift | 12 +- .../Core/VIPER/Camera/View/CameraView.swift | 8 +- .../Assembly/ImageCroppingAssembly.swift | 2 +- .../Assembly/ImageCroppingAssemblyImpl.swift | 21 ++- .../Interactor/ImageCroppingInteractor.swift | 2 +- .../ImageCroppingInteractorImpl.swift | 48 +---- .../Presenter/ImageCroppingPresenter.swift | 6 +- .../View/CroppingPreviewView.swift | 105 +++++++++++ .../View/ImageCroppingControlsView.swift | 32 ++-- .../View/ImageCroppingMask.swift | 63 +++++++ .../View/ImageCroppingUITheme.swift | 12 ++ .../View/ImageCroppingView.swift | 67 +++---- .../View/ImageCroppingViewController.swift | 17 +- .../Assembly/MaskCropperAssembly.swift | 14 ++ .../Assembly/MaskCropperAssemblyImpl.swift | 44 +++++ .../Interactor/MaskCropperInteractor.swift | 7 + .../MaskCropperInteractorImpl.swift | 24 +++ .../MaskCropper/Module/MaskCropperData.swift | 15 ++ .../Module/MaskCropperModule.swift | 10 + .../Presenter/MaskCropperPresenter.swift | 64 +++++++ .../Router/MaskCropperRouter.swift | 6 + .../Router/MaskCropperUIKitRouter.swift | 4 + .../View/MaskCropperControlsView.swift | 64 +++++++ .../View/MaskCropperOverlayView.swift | 45 +++++ .../MaskCropper/View/MaskCropperUITheme.swift | 12 ++ .../MaskCropper/View/MaskCropperView.swift | 176 ++++++++++++++++++ .../View/MaskCropperViewController.swift | 117 ++++++++++++ .../View/MaskCropperViewInput.swift | 16 ++ .../Assembly/MediaPickerAssembly.swift | 8 +- .../Assembly/MediaPickerAssemblyImpl.swift | 33 ++-- .../MediaPicker/Module/MediaPickerData.swift | 26 +++ .../Module/MediaPickerModule.swift | 1 + .../Presenter/MediaPickerPresenter.swift | 76 ++++---- .../Router/MediaPickerRouter.swift | 7 +- .../Router/MediaPickerUIKitRouter.swift | 14 +- .../View/Controls/CameraControlsView.swift | 8 +- .../View/Controls/PhotoControlsView.swift | 16 +- .../View/MainView/PhotoPreviewView.swift | 5 +- .../View/MediaPickerRootModuleUITheme.swift | 22 +++ .../MediaPicker/View/MediaPickerView.swift | 85 +++++---- .../View/MediaPickerViewController.swift | 36 ++-- .../View/MediaPickerViewInput.swift | 4 +- .../View/ThumbnailsView/ThumbnailsView.swift | 14 +- .../ThumbnailsView/ThumbnailsViewLayout.swift | 139 -------------- .../Assembly/PhotoLibraryAssembly.swift | 5 +- .../Assembly/PhotoLibraryAssemblyImpl.swift | 19 +- .../Module/PhotoLibraryData.swift | 14 ++ .../View/AccessDeniedUITheme.swift | 5 + .../PhotoLibrary/View/AccessDeniedView.swift | 8 +- .../View/PhotoLibraryUITheme.swift | 9 + .../PhotoLibrary/View/PhotoLibraryView.swift | 16 +- .../View/PhotoLibraryViewController.swift | 24 +-- .../MarshrouteAssemblyFactory.swift | 16 +- .../MaskCropperMarshrouteAssembly.swift | 15 ++ .../MaskCropperMarshrouteAssemblyImpl.swift | 44 +++++ .../Router/MaskCropperMarshrouteRouter.swift | 4 + .../MediaPickerMarshrouteAssembly.swift | 8 +- .../MediaPickerMarshrouteAssemblyImpl.swift | 33 ++-- .../Router/MediaPickerMarshrouteRouter.swift | 15 +- .../PhotoLibraryMarshrouteAssembly.swift | 2 +- .../PhotoLibraryMarshrouteAssemblyImpl.swift | 12 +- 85 files changed, 1832 insertions(+), 669 deletions(-) create mode 100644 Paparazzo/Core/DI/AssemblyFactory.swift create mode 100644 Paparazzo/Core/DI/BasePaparazzoAssembly.swift create mode 100644 Paparazzo/Core/DI/ServiceFactory.swift create mode 100644 Paparazzo/Core/DisposeBag.swift create mode 100644 Paparazzo/Core/PaparazzoViewController.swift create mode 100644 Paparazzo/Core/Services/CroppingOverlayProviders/CircleCroppingOverlayProvider.swift create mode 100644 Paparazzo/Core/Services/CroppingOverlayProviders/CroppingOverlayProvider.swift create mode 100644 Paparazzo/Core/Services/CroppingOverlayProviders/CroppingOverlayProvidersFactory.swift create mode 100644 Paparazzo/Core/Services/CroppingOverlayProviders/HeartShapeCroppingOverlayProvider.swift create mode 100644 Paparazzo/Core/Services/CroppingOverlayProviders/RectangleCroppingOverlayProvider.swift create mode 100644 Paparazzo/Core/Services/ImageCropping/ImageCroppingService.swift create mode 100644 Paparazzo/Core/ThemeConfigurable.swift delete mode 100644 Paparazzo/Core/VIPER/AssemblyFactory.swift create mode 100644 Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift create mode 100644 Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingMask.swift create mode 100644 Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingUITheme.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssembly.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssemblyImpl.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractor.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractorImpl.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperData.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperModule.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/Router/MaskCropperRouter.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/Router/MaskCropperUIKitRouter.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperOverlayView.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperUITheme.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift create mode 100644 Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewInput.swift create mode 100644 Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift create mode 100644 Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerRootModuleUITheme.swift create mode 100644 Paparazzo/Core/VIPER/PhotoLibrary/Module/PhotoLibraryData.swift create mode 100644 Paparazzo/Core/VIPER/PhotoLibrary/View/AccessDeniedUITheme.swift create mode 100644 Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryUITheme.swift create mode 100644 Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssembly.swift create mode 100644 Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssemblyImpl.swift create mode 100644 Paparazzo/Marshroute/MaskCropper/Router/MaskCropperMarshrouteRouter.swift diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index 4a91d1b9..9fb814ad 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -3,6 +3,8 @@ import ImageSource final class ExamplePresenter { + // MARK: - Dependencies + private let interactor: ExampleInteractor private let router: ExampleRouter @@ -21,10 +23,18 @@ final class ExamplePresenter { private var items: [MediaPickerItem] = [] + private let cropCanvasSize = CGSize(width: 1280, height: 960) + // MARK: - Private + private let croppingOverlayProvidersFactory = Paparazzo.CroppingOverlayProvidersFactoryImpl() + private func setUpView() { + view?.setMediaPickerButtonTitle("Media Picker") + view?.setMaskCropperButtonTitle("Mask Cropper") + view?.setPhotoLibraryButtonTitle("Photo Library") + view?.onShowMediaPickerButtonTap = { [weak self] in self?.interactor.remoteItems { remoteItems in self?.showMediaPicker(remoteItems: remoteItems) @@ -48,21 +58,81 @@ final class ExamplePresenter { } } } + + view?.onMaskCropperButtonTap = { [weak self] in + self?.showMaskCropperCamera() + } + } + + func showMaskCropperCamera() { + let data = MediaPickerData( + items: items, + selectedItem: nil, + maxItemsCount: 2, + cropEnabled: true, + cropCanvasSize: cropCanvasSize, + initialActiveCameraType: .front + ) + + self.router.showMediaPicker( + data: data, + configure: { module in + weak var module = module + module?.setContinueButtonVisible(false) + module?.onCancel = { + module?.dismissModule() + } + module?.onFinish = { items in + module?.dismissModule() + } + module?.onItemsAdd = { [weak self] items in + guard let photo = items.first + else { return } + self?.showMaskCropperIn(rootModule: module, photo: photo) + } + } + ) + } + + private func showMaskCropperIn(rootModule: MediaPickerModule?, photo: MediaPickerItem) { + + let data = MaskCropperData( + photo: photo, + cropCanvasSize: cropCanvasSize + ) + router.showMaskCropper( + data: data, + croppingOverlayProvider: croppingOverlayProvidersFactory.heartShapeCroppingOverlayProvider(), + configure: { module in + weak var module = module + module?.onDiscard = { + module?.dismissModule() + } + module?.onClose = { + rootModule?.dismissModule() + } + module?.onConfirm = { _ in + rootModule?.dismissModule() + } + }) } func showMediaPicker(remoteItems: [MediaPickerItem]) { var items = self.items items.append(contentsOf: remoteItems) - - let cropCanvasSize = CGSize(width: 1280, height: 960) - self.router.showMediaPicker( + let data = MediaPickerData( items: items, selectedItem: items.last, maxItemsCount: 20, - cropCanvasSize: cropCanvasSize, - configuration: { [weak self] module in + cropEnabled: true, + cropCanvasSize: cropCanvasSize + ) + + self.router.showMediaPicker( + data: data, + configure: { [weak self] module in self?.configureMediaPicker(module: module) } ) diff --git a/Example/PaparazzoExample/Example/Router/ExampleRouter.swift b/Example/PaparazzoExample/Example/Router/ExampleRouter.swift index e80b1d30..ea2180ca 100644 --- a/Example/PaparazzoExample/Example/Router/ExampleRouter.swift +++ b/Example/PaparazzoExample/Example/Router/ExampleRouter.swift @@ -4,16 +4,19 @@ import Paparazzo protocol ExampleRouter: class, RouterFocusable, RouterDismissable { func showMediaPicker( - items: [MediaPickerItem], - selectedItem: MediaPickerItem?, - maxItemsCount: Int?, - cropCanvasSize: CGSize, - configuration: (MediaPickerModule) -> () + data: MediaPickerData, + configure: (MediaPickerModule) -> () + ) + + func showMaskCropper( + data: MaskCropperData, + croppingOverlayProvider: CroppingOverlayProvider, + configure: (MaskCropperModule) -> () ) func showPhotoLibrary( selectedItems: [PhotoLibraryItem], maxSelectedItemsCount: Int?, - configuration: (PhotoLibraryModule) -> () + configure: (PhotoLibraryModule) -> () ) } diff --git a/Example/PaparazzoExample/Example/Router/ExampleRouterImpl.swift b/Example/PaparazzoExample/Example/Router/ExampleRouterImpl.swift index e90a961c..000067d7 100644 --- a/Example/PaparazzoExample/Example/Router/ExampleRouterImpl.swift +++ b/Example/PaparazzoExample/Example/Router/ExampleRouterImpl.swift @@ -11,24 +11,35 @@ final class ExampleRouterImpl: BaseRouter, ExampleRouter { // MARK: - ExampleRouter func showMediaPicker( - items: [MediaPickerItem], - selectedItem: MediaPickerItem?, - maxItemsCount: Int?, - cropCanvasSize: CGSize, - configuration: (MediaPickerModule) -> () + data: MediaPickerData, + configure: (MediaPickerModule) -> () ) { pushViewControllerDerivedFrom { routerSeed in let assembly = mediaPickerAssemblyFactory.mediaPickerAssembly() return assembly.module( - items: items, - selectedItem: selectedItem, - maxItemsCount: maxItemsCount, - cropEnabled: true, - cropCanvasSize: cropCanvasSize, + data: data, routerSeed: routerSeed, - configuration: configuration + configure: configure + ) + } + } + + func showMaskCropper( + data: MaskCropperData, + croppingOverlayProvider: CroppingOverlayProvider, + configure: (MaskCropperModule) -> () + ) { + pushViewControllerDerivedFrom { routerSeed in + + let assembly = mediaPickerAssemblyFactory.maskCropperAssembly() + + return assembly.module( + data: data, + croppingOverlayProvider: croppingOverlayProvider, + routerSeed: routerSeed, + configure: configure ) } } @@ -36,7 +47,7 @@ final class ExampleRouterImpl: BaseRouter, ExampleRouter { func showPhotoLibrary( selectedItems: [PhotoLibraryItem], maxSelectedItemsCount: Int?, - configuration: (PhotoLibraryModule) -> () + configure: (PhotoLibraryModule) -> () ) { presentModalNavigationControllerWithRootViewControllerDerivedFrom { routerSeed in @@ -46,7 +57,7 @@ final class ExampleRouterImpl: BaseRouter, ExampleRouter { selectedItems: selectedItems, maxSelectedItemsCount: maxSelectedItemsCount, routerSeed: routerSeed, - configuration: configuration + configure: configure ) } } diff --git a/Example/PaparazzoExample/Example/View/ExampleView.swift b/Example/PaparazzoExample/Example/View/ExampleView.swift index 4e5322d5..f3005ec3 100644 --- a/Example/PaparazzoExample/Example/View/ExampleView.swift +++ b/Example/PaparazzoExample/Example/View/ExampleView.swift @@ -2,10 +2,8 @@ import UIKit final class ExampleView: UIView { - var onShowMediaPickerButtonTap: (() -> ())? - var onShowPhotoLibraryButtonTap: (() -> ())? - private let mediaPickerButton = UIButton() + private let maskCropperButton = UIButton() private let photoLibraryButton = UIButton() // MARK: - Init @@ -13,13 +11,19 @@ final class ExampleView: UIView { init() { super.init(frame: .zero) - mediaPickerButton.setTitle("Show Media Picker", for: .normal) mediaPickerButton.addTarget( self, action: #selector(onShowMediaPickerButtonTap(_:)), for: .touchUpInside ) + maskCropperButton.setTitle("Show Mask Cropper", for: .normal) + maskCropperButton.addTarget( + self, + action: #selector(onMaskCropperButtonTap(_:)), + for: .touchUpInside + ) + photoLibraryButton.setTitle("Show Photo Library", for: .normal) photoLibraryButton.addTarget( self, @@ -28,6 +32,7 @@ final class ExampleView: UIView { ) addSubview(mediaPickerButton) + addSubview(maskCropperButton) addSubview(photoLibraryButton) } @@ -35,16 +40,37 @@ final class ExampleView: UIView { fatalError("init(coder:) has not been implemented") } + // MARK: - ExampleView + + func setMediaPickerButtonTitle(_ title: String) { + mediaPickerButton.setTitle(title, for: .normal) + } + + func setMaskCropperButtonTitle(_ title: String) { + maskCropperButton.setTitle(title, for: .normal) + } + + func setPhotoLibraryButtonTitle(_ title: String) { + photoLibraryButton.setTitle(title, for: .normal) + } + + var onShowMediaPickerButtonTap: (() -> ())? + var onMaskCropperButtonTap: (() -> ())? + var onShowPhotoLibraryButtonTap: (() -> ())? + // MARK: - UIView override func layoutSubviews() { super.layoutSubviews() mediaPickerButton.sizeToFit() - mediaPickerButton.center = CGPoint(x: bounds.midX, y: bounds.midY - 30) + mediaPickerButton.center = CGPoint(x: bounds.midX, y: bounds.midY - 50) + + maskCropperButton.sizeToFit() + maskCropperButton.center = CGPoint(x: bounds.midX, y: bounds.midY) photoLibraryButton.sizeToFit() - photoLibraryButton.center = CGPoint(x: bounds.midX, y: bounds.midY + 30) + photoLibraryButton.center = CGPoint(x: bounds.midX, y: bounds.midY + 50) } // MARK: - Private @@ -53,6 +79,10 @@ final class ExampleView: UIView { onShowMediaPickerButtonTap?() } + @objc private func onMaskCropperButtonTap(_: UIButton) { + onMaskCropperButtonTap?() + } + @objc private func onShowPhotoLibraryButtonTap(_: UIButton) { onShowPhotoLibraryButtonTap?() } diff --git a/Example/PaparazzoExample/Example/View/ExampleViewController.swift b/Example/PaparazzoExample/Example/View/ExampleViewController.swift index 60bce734..4f1b3a8e 100644 --- a/Example/PaparazzoExample/Example/View/ExampleViewController.swift +++ b/Example/PaparazzoExample/Example/View/ExampleViewController.swift @@ -25,6 +25,18 @@ final class ExampleViewController: UIViewController, ExampleViewInput { // MARK: - ExampleViewInput + func setMediaPickerButtonTitle(_ title: String) { + exampleView?.setMediaPickerButtonTitle(title) + } + + func setMaskCropperButtonTitle(_ title: String) { + exampleView?.setMaskCropperButtonTitle(title) + } + + func setPhotoLibraryButtonTitle(_ title: String) { + exampleView?.setPhotoLibraryButtonTitle(title) + } + var onShowMediaPickerButtonTap: (() -> ())? { get { return exampleView?.onShowMediaPickerButtonTap } set { exampleView?.onShowMediaPickerButtonTap = newValue } @@ -34,4 +46,9 @@ final class ExampleViewController: UIViewController, ExampleViewInput { get { return exampleView?.onShowPhotoLibraryButtonTap } set { exampleView?.onShowPhotoLibraryButtonTap = newValue } } + + var onMaskCropperButtonTap: (() -> ())? { + get { return exampleView?.onMaskCropperButtonTap } + set { exampleView?.onMaskCropperButtonTap = newValue } + } } diff --git a/Example/PaparazzoExample/Example/View/ExampleViewInput.swift b/Example/PaparazzoExample/Example/View/ExampleViewInput.swift index b1cda7bb..1898af49 100644 --- a/Example/PaparazzoExample/Example/View/ExampleViewInput.swift +++ b/Example/PaparazzoExample/Example/View/ExampleViewInput.swift @@ -1,6 +1,12 @@ import Foundation protocol ExampleViewInput: class { + + func setMediaPickerButtonTitle(_ title: String) + func setMaskCropperButtonTitle(_ title: String) + func setPhotoLibraryButtonTitle(_ title: String) + var onShowMediaPickerButtonTap: (() -> ())? { get set } + var onMaskCropperButtonTap: (() -> ())? { get set } var onShowPhotoLibraryButtonTap: (() -> ())? { get set } } diff --git a/Example/PaparazzoExample_NoMarshroute/AppDelegate.swift b/Example/PaparazzoExample_NoMarshroute/AppDelegate.swift index f3983a22..19ad5aa9 100644 --- a/Example/PaparazzoExample_NoMarshroute/AppDelegate.swift +++ b/Example/PaparazzoExample_NoMarshroute/AppDelegate.swift @@ -34,7 +34,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { maxItemsCount: 20, cropEnabled: true, cropCanvasSize: CGSize(width: 1280, height: 960), - configuration: { module in + configure: { module in weak var module = module module?.setContinueButtonTitle("Готово") @@ -59,7 +59,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let galleryController = assembly.module( selectedItems: [], maxSelectedItemsCount: 5, - configuration: { module in + configure: { module in weak var module = module module?.onFinish = { _ in module?.dismissModule() diff --git a/Paparazzo/Core/DI/AssemblyFactory.swift b/Paparazzo/Core/DI/AssemblyFactory.swift new file mode 100644 index 00000000..0cf6246f --- /dev/null +++ b/Paparazzo/Core/DI/AssemblyFactory.swift @@ -0,0 +1,36 @@ +public final class AssemblyFactory: + CameraAssemblyFactory, + MediaPickerAssemblyFactory, + PhotoLibraryAssemblyFactory, + ImageCroppingAssemblyFactory, + MaskCropperAssemblyFactory +{ + + private let theme: PaparazzoUITheme + private let serviceFactory = ServiceFactoryImpl() + + public init(theme: PaparazzoUITheme = PaparazzoUITheme()) { + self.theme = theme + } + + func cameraAssembly() -> CameraAssembly { + return CameraAssemblyImpl(theme: theme, serviceFactory: serviceFactory) + } + + public func mediaPickerAssembly() -> MediaPickerAssembly { + return MediaPickerAssemblyImpl(assemblyFactory: self, theme: theme, serviceFactory: serviceFactory) + } + + func imageCroppingAssembly() -> ImageCroppingAssembly { + return ImageCroppingAssemblyImpl(theme: theme, serviceFactory: serviceFactory) + } + + public func photoLibraryAssembly() -> PhotoLibraryAssembly { + return PhotoLibraryAssemblyImpl(theme: theme, serviceFactory: serviceFactory) + } + + public func maskCropperAssembly() -> MaskCropperAssembly { + return MaskCropperAssemblyImpl(theme: theme, serviceFactory: serviceFactory) + } + +} diff --git a/Paparazzo/Core/DI/BasePaparazzoAssembly.swift b/Paparazzo/Core/DI/BasePaparazzoAssembly.swift new file mode 100644 index 00000000..3c48cd73 --- /dev/null +++ b/Paparazzo/Core/DI/BasePaparazzoAssembly.swift @@ -0,0 +1,11 @@ +public class BasePaparazzoAssembly { + // MARK: - Dependencies + let theme: PaparazzoUITheme + let serviceFactory: ServiceFactory + + init(theme: PaparazzoUITheme, serviceFactory: ServiceFactory) { + self.theme = theme + self.serviceFactory = serviceFactory + } + +} diff --git a/Paparazzo/Core/DI/ServiceFactory.swift b/Paparazzo/Core/DI/ServiceFactory.swift new file mode 100644 index 00000000..90f22d9d --- /dev/null +++ b/Paparazzo/Core/DI/ServiceFactory.swift @@ -0,0 +1,28 @@ +import ImageSource + +protocol ServiceFactory: class { + func deviceOrientationService() -> DeviceOrientationService + func cameraService(initialActiveCameraType: CameraType) -> CameraService + func photoLibraryLatestPhotoProvider() -> PhotoLibraryLatestPhotoProvider + func imageCroppingService(image: ImageSource, canvasSize: CGSize) -> ImageCroppingService +} + +final class ServiceFactoryImpl: ServiceFactory { + + func deviceOrientationService() -> DeviceOrientationService { + return DeviceOrientationServiceImpl() + } + + func cameraService(initialActiveCameraType: CameraType) -> CameraService { + return CameraServiceImpl(initialActiveCameraType: initialActiveCameraType) + } + + func photoLibraryLatestPhotoProvider() -> PhotoLibraryLatestPhotoProvider { + return PhotoLibraryLatestPhotoProviderImpl() + } + + func imageCroppingService(image: ImageSource, canvasSize: CGSize) -> ImageCroppingService { + return ImageCroppingServiceImpl(image: image, canvasSize: canvasSize) + } + +} diff --git a/Paparazzo/Core/DisposeBag.swift b/Paparazzo/Core/DisposeBag.swift new file mode 100644 index 00000000..1045e203 --- /dev/null +++ b/Paparazzo/Core/DisposeBag.swift @@ -0,0 +1,28 @@ +protocol DisposeBag { + func addDisposable(_: AnyObject) +} + +protocol DisposeBagHolder { + var disposeBag: DisposeBag { get } +} + +// Default `DisposeBag` implementation +extension DisposeBag where Self: DisposeBagHolder { + func addDisposable(_ anyObject: AnyObject) { + disposeBag.addDisposable(anyObject) + } +} + +// Non thread safe `DisposeBag` implementation +final class DisposeBagImpl: DisposeBag { + // MARK: - Private properties + private var disposables: [AnyObject] = [] + + // MARK: - Init + init() {} + + // MARK: - DisposeBag + func addDisposable(_ anyObject: AnyObject) { + disposables.append(anyObject) + } +} diff --git a/Paparazzo/Core/MediaPickerUITheme.swift b/Paparazzo/Core/MediaPickerUITheme.swift index cb6d96b4..347eb8cc 100644 --- a/Paparazzo/Core/MediaPickerUITheme.swift +++ b/Paparazzo/Core/MediaPickerUITheme.swift @@ -1,7 +1,12 @@ import UIKit -public struct PaparazzoUITheme: MediaPickerRootModuleUITheme, PhotoLibraryUITheme, ImageCroppingUITheme { - +public struct PaparazzoUITheme: + MediaPickerRootModuleUITheme, + PhotoLibraryUITheme, + ImageCroppingUITheme, + MaskCropperUITheme +{ + public init() {} // MARK: - MediaPickerRootModuleUITheme @@ -50,6 +55,19 @@ public struct PaparazzoUITheme: MediaPickerRootModuleUITheme, PhotoLibraryUIThem public var cancelRotationBackgroundColor = UIColor.RGB(red: 25, green: 25, blue: 25, alpha: 1) public var cancelRotationTitleColor = UIColor.white public var cancelRotationTitleFont = UIFont.boldSystemFont(ofSize: 14) + + // MARK: - MaskCropperUITheme + + public var maskCropperDiscardPhotoIcon = PaparazzoUITheme.image(named: "delete") + public var maskCropperCloseButtonIcon = PaparazzoUITheme.image(named: "bt-close") + + public var maskCropperButtonsBackgroundNormalColor = UIColor.white + public var maskCropperButtonsBackgroundHighlightedColor = UIColor(white: 1, alpha: 0.6) + public var maskCropperButtonsBackgroundDisabledColor = UIColor(white: 1, alpha: 0.6) + public var maskCropperConfirmButtonTitleColor = UIColor(red: 0, green: 170.0/255, blue: 1, alpha: 1) + public var maskCropperConfirmButtonTitleHighlightedColor = UIColor(red: 0, green: 152.0/255, blue: 229.0/255, alpha: 1) + + public var maskCropperConfirmButtonTitleFont = UIFont.systemFont(ofSize: 17) // MARK: - Private @@ -60,57 +78,3 @@ public struct PaparazzoUITheme: MediaPickerRootModuleUITheme, PhotoLibraryUIThem return UIImage(named: name, in: bundle, compatibleWith: nil) } } - -public protocol AccessDeniedViewTheme { - var accessDeniedTitleFont: UIFont { get } - var accessDeniedMessageFont: UIFont { get } - var accessDeniedButtonFont: UIFont { get } -} - -public protocol MediaPickerRootModuleUITheme: AccessDeniedViewTheme { - - var shutterButtonColor: UIColor { get } - var shutterButtonDisabledColor: UIColor { get } - var mediaRibbonSelectionColor: UIColor { get } - var cameraContinueButtonTitleColor: UIColor { get } - var cameraContinueButtonTitleHighlightedColor: UIColor { get } - var cameraButtonsBackgroundNormalColor: UIColor { get } - var cameraButtonsBackgroundHighlightedColor: UIColor { get } - var cameraButtonsBackgroundDisabledColor: UIColor { get } - - var removePhotoIcon: UIImage? { get } - var cropPhotoIcon: UIImage? { get } - var returnToCameraIcon: UIImage? { get } - var closeCameraIcon: UIImage? { get } - var flashOnIcon: UIImage? { get } - var flashOffIcon: UIImage? { get } - var cameraToggleIcon: UIImage? { get } - var photoPeepholePlaceholder: UIImage? { get } - - - var cameraContinueButtonTitleFont: UIFont { get } -} - -public protocol PhotoLibraryUITheme: AccessDeniedViewTheme { - - var photoLibraryDoneButtonFont: UIFont { get } - - var photoLibraryItemSelectionColor: UIColor { get } - var photoCellBackgroundColor: UIColor { get } - - var iCloudIcon: UIImage? { get } -} - -public protocol ImageCroppingUITheme { - - var rotationIcon: UIImage? { get } - var gridIcon: UIImage? { get } - var gridSelectedIcon: UIImage? { get } - var cropperDiscardIcon: UIImage? { get } - var cropperConfirmIcon: UIImage? { get } - - var cancelRotationBackgroundColor: UIColor { get } - var cancelRotationTitleColor: UIColor { get } - var cancelRotationTitleFont: UIFont { get } - var cancelRotationButtonIcon: UIImage? { get } -} diff --git a/Paparazzo/Core/PaparazzoViewController.swift b/Paparazzo/Core/PaparazzoViewController.swift new file mode 100644 index 00000000..9c9f6baa --- /dev/null +++ b/Paparazzo/Core/PaparazzoViewController.swift @@ -0,0 +1,14 @@ +import UIKit + +class PaparazzoViewController: UIViewController, DisposeBag, DisposeBagHolder { + // MARK: - DisposeBagHolder + public let disposeBag: DisposeBag = DisposeBagImpl() + + @nonobjc public init() { + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Paparazzo/Core/PhotoTweakView.swift b/Paparazzo/Core/PhotoTweakView.swift index 0d9ffa35..793dfcce 100644 --- a/Paparazzo/Core/PhotoTweakView.swift +++ b/Paparazzo/Core/PhotoTweakView.swift @@ -10,11 +10,7 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { private let scrollView = PhotoScrollView() private let gridView = GridView() - - private let topMask = UIView() - private let bottomMask = UIView() - private let leftMask = UIView() - private let rightMask = UIView() + private let croppingMask = ImageCroppingMask() // MARK: - State @@ -49,22 +45,12 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { scrollView.clipsToBounds = false scrollView.delegate = self - let maskColor = UIColor.white.withAlphaComponent(0.9) - - topMask.backgroundColor = maskColor - bottomMask.backgroundColor = maskColor - leftMask.backgroundColor = maskColor - rightMask.backgroundColor = maskColor - gridView.isUserInteractionEnabled = false gridView.isHidden = true addSubview(scrollView) addSubview(gridView) - addSubview(topMask) - addSubview(bottomMask) - addSubview(leftMask) - addSubview(rightMask) + addSubview(croppingMask) updateMasks() } @@ -180,6 +166,10 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { gridView.isHidden = !visible } + func setMaskVisible(_ visible: Bool) { + croppingMask.isHidden = !visible + } + func cropPreviewImage() -> CGImage? { // Hide grid for it to be hidden on preview image @@ -266,6 +256,8 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { gridView.bounds = CGRect(origin: .zero, size: cropSize) gridView.center = center + croppingMask.bounds = frame + originalPoint = convert(scrollView.center, to: self) updateMasks() @@ -273,33 +265,8 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { private func updateMasks(animated: Bool = false) { - let horizontalMaskSize = CGSize( - width: bounds.size.width, - height: (bounds.size.height - cropSize.height) / 2 - ) - - let verticalMaskSize = CGSize( - width: (bounds.size.width - cropSize.width) / 2, - height: bounds.size.height - horizontalMaskSize.height - ) - let animation = { - self.topMask.frame = CGRect( - origin: CGPoint(x: self.bounds.left, y: self.bounds.top), - size: horizontalMaskSize - ) - self.bottomMask.frame = CGRect( - origin: CGPoint(x: self.bounds.left, y: self.bounds.bottom - horizontalMaskSize.height), - size: horizontalMaskSize - ) - self.leftMask.frame = CGRect( - origin: CGPoint(x: self.bounds.left, y: self.topMask.bottom), - size: verticalMaskSize - ) - self.rightMask.frame = CGRect( - origin: CGPoint(x: self.bounds.right - verticalMaskSize.width, y: self.topMask.bottom), - size: verticalMaskSize - ) + self.croppingMask.performLayoutUpdate(with: self.cropSize) } if animated { diff --git a/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift b/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift index 38834b33..411c0ba8 100644 --- a/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift +++ b/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift @@ -2,6 +2,11 @@ import AVFoundation import ImageIO import ImageSource +public enum CameraType { + case back + case front +} + final class CameraServiceImpl: CameraService { // MARK: - Private types and properties @@ -12,15 +17,22 @@ final class CameraServiceImpl: CameraService { private var output: AVCaptureStillImageOutput? private var backCamera: AVCaptureDevice? private var frontCamera: AVCaptureDevice? - private var activeCamera: AVCaptureDevice? + + private var activeCamera: AVCaptureDevice? { + return camera(for: activeCameraType) + } + + private var activeCameraType: CameraType // MARK: - Init - init() { + init(initialActiveCameraType: CameraType) { let videoDevices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as? [AVCaptureDevice] backCamera = videoDevices?.filter({ $0.position == .back }).first frontCamera = videoDevices?.filter({ $0.position == .front }).first + + self.activeCameraType = initialActiveCameraType } func getCaptureSession(completion: @escaping (AVCaptureSession?) -> ()) { @@ -84,8 +96,6 @@ final class CameraServiceImpl: CameraService { try CameraServiceImpl.configureCamera(backCamera) - let activeCamera = backCamera - let input = try AVCaptureDeviceInput(device: activeCamera) let output = AVCaptureStillImageOutput() @@ -100,7 +110,6 @@ final class CameraServiceImpl: CameraService { captureSession.startRunning() - self.activeCamera = activeCamera self.output = output self.captureSession = captureSession @@ -129,7 +138,8 @@ final class CameraServiceImpl: CameraService { do { - let targetCamera = (activeCamera == backCamera) ? frontCamera : backCamera + let targetCameraType: CameraType = (activeCamera == backCamera) ? .front : .back + let targetCamera = camera(for: targetCameraType) let newInput = try AVCaptureDeviceInput(device: targetCamera) try captureSession.configure { @@ -149,7 +159,7 @@ final class CameraServiceImpl: CameraService { try CameraServiceImpl.configureCamera(targetCamera) } - activeCamera = targetCamera + activeCameraType = targetCameraType } catch { debugPrint("Couldn't toggle camera: \(error)") @@ -284,4 +294,14 @@ final class CameraServiceImpl: CameraService { return .left } } + + private func camera(for cameraType: CameraType) -> AVCaptureDevice? { + switch cameraType { + case .back: + return backCamera + case .front: + return frontCamera + } + } + } diff --git a/Paparazzo/Core/Services/CroppingOverlayProviders/CircleCroppingOverlayProvider.swift b/Paparazzo/Core/Services/CroppingOverlayProviders/CircleCroppingOverlayProvider.swift new file mode 100644 index 00000000..993e3e0e --- /dev/null +++ b/Paparazzo/Core/Services/CroppingOverlayProviders/CircleCroppingOverlayProvider.swift @@ -0,0 +1,21 @@ +final class CircleCroppingOverlayProvider: CroppingOverlayProvider { + + func calculateRectToCrop(in bounds: CGRect) -> CGRect { + let diameter = bounds.width - 16 + return CGRect( + origin: CGPoint( + x: bounds.center.x - diameter / 2, + y: bounds.center.y - diameter / 2 + ), + size: CGSize( + width: diameter, + height: diameter + ) + ) + } + + func croppingPath(in rect: CGRect) -> CGPath { + return UIBezierPath(ovalIn: rect).cgPath + } + +} diff --git a/Paparazzo/Core/Services/CroppingOverlayProviders/CroppingOverlayProvider.swift b/Paparazzo/Core/Services/CroppingOverlayProviders/CroppingOverlayProvider.swift new file mode 100644 index 00000000..1722f4bc --- /dev/null +++ b/Paparazzo/Core/Services/CroppingOverlayProviders/CroppingOverlayProvider.swift @@ -0,0 +1,6 @@ +import UIKit + +public protocol CroppingOverlayProvider: class { + func calculateRectToCrop(in bounds: CGRect) -> CGRect + func croppingPath(in rect: CGRect) -> CGPath +} diff --git a/Paparazzo/Core/Services/CroppingOverlayProviders/CroppingOverlayProvidersFactory.swift b/Paparazzo/Core/Services/CroppingOverlayProviders/CroppingOverlayProvidersFactory.swift new file mode 100644 index 00000000..f1d247f5 --- /dev/null +++ b/Paparazzo/Core/Services/CroppingOverlayProviders/CroppingOverlayProvidersFactory.swift @@ -0,0 +1,23 @@ +public protocol CroppingOverlayProvidersFactory: class { + func circleCroppingOverlayProvider() -> CroppingOverlayProvider + func rectangleCroppingOverlayProvider(cornerRadius: CGFloat, margin: CGFloat) -> CroppingOverlayProvider + func heartShapeCroppingOverlayProvider() -> CroppingOverlayProvider +} + +public class CroppingOverlayProvidersFactoryImpl: CroppingOverlayProvidersFactory { + + public init() {} + + public func circleCroppingOverlayProvider() -> CroppingOverlayProvider { + return CircleCroppingOverlayProvider() + } + + public func rectangleCroppingOverlayProvider(cornerRadius: CGFloat, margin: CGFloat) -> CroppingOverlayProvider { + return RectangleCroppingOverlayProvider(cornerRadius: cornerRadius, margin: margin) + } + + public func heartShapeCroppingOverlayProvider() -> CroppingOverlayProvider { + return HeartShapeCroppingOverlayProvider() + } + +} diff --git a/Paparazzo/Core/Services/CroppingOverlayProviders/HeartShapeCroppingOverlayProvider.swift b/Paparazzo/Core/Services/CroppingOverlayProviders/HeartShapeCroppingOverlayProvider.swift new file mode 100644 index 00000000..daba154d --- /dev/null +++ b/Paparazzo/Core/Services/CroppingOverlayProviders/HeartShapeCroppingOverlayProvider.swift @@ -0,0 +1,65 @@ +import UIKit + +private extension Int { + var degreesToRadians: CGFloat { return CGFloat(self) * .pi / 180 } +} + +final class HeartShapeCroppingOverlayProvider: CroppingOverlayProvider { + + // MARK :- CroppingOverlayProvider + + func calculateRectToCrop(in bounds: CGRect) -> CGRect { + return CGRect( + origin: CGPoint( + x: bounds.center.x - bounds.width / 2, + y: bounds.center.y - bounds.height / 2 + ), + size: CGSize( + width: bounds.width, + height: bounds.width + ) + ) + } + + func croppingPath(in rect: CGRect) -> CGPath { + let path = UIBezierPath() + + //Calculate Radius of Arcs using Pythagoras + let sideOne = rect.width * 0.4 + let sideTwo = rect.height * 0.3 + let arcRadius = sqrt(sideOne * sideOne + sideTwo * sideTwo) / 2 + + //Left Hand Curve + path.addArc( + withCenter: CGPoint(x: rect.width * 0.3, y: rect.height * 0.35), + radius: arcRadius, + startAngle: 135.degreesToRadians, + endAngle: 315.degreesToRadians, + clockwise: true + ) + + //Top Centre Dip + path.addLine(to: CGPoint(x: rect.width / 2, y: rect.height * 0.2)) + + //Right Hand Curve + path.addArc( + withCenter: CGPoint(x: rect.width * 0.7, y: rect.height * 0.35), + radius: arcRadius, + startAngle: 225.degreesToRadians, + endAngle: 45.degreesToRadians, + clockwise: true + ) + + //Right Bottom Line + path.addLine(to: CGPoint(x: rect.width * 0.5, y: rect.height * 0.95)) + + //Left Bottom Line + path.close() + + let transform = CGAffineTransform(translationX: 0, y: rect.centerY / 2 - 22.5) + path.apply(transform) + + return path.cgPath + } + +} diff --git a/Paparazzo/Core/Services/CroppingOverlayProviders/RectangleCroppingOverlayProvider.swift b/Paparazzo/Core/Services/CroppingOverlayProviders/RectangleCroppingOverlayProvider.swift new file mode 100644 index 00000000..b12c7795 --- /dev/null +++ b/Paparazzo/Core/Services/CroppingOverlayProviders/RectangleCroppingOverlayProvider.swift @@ -0,0 +1,33 @@ +import UIKit + +final class RectangleCroppingOverlayProvider: CroppingOverlayProvider { + + private let cornerRadius: CGFloat + private let margin: CGFloat + + init(cornerRadius: CGFloat, margin: CGFloat) { + self.cornerRadius = cornerRadius + self.margin = margin + } + + // MARK :- CroppingOverlayProvider + + func calculateRectToCrop(in bounds: CGRect) -> CGRect { + let diameter = bounds.width - margin + return CGRect( + origin: CGPoint( + x: bounds.center.x - diameter / 2, + y: bounds.center.y - diameter / 2 + ), + size: CGSize( + width: diameter, + height: diameter + ) + ) + } + + func croppingPath(in rect: CGRect) -> CGPath { + return UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath + } + +} diff --git a/Paparazzo/Core/Services/ImageCropping/ImageCroppingService.swift b/Paparazzo/Core/Services/ImageCropping/ImageCroppingService.swift new file mode 100644 index 00000000..8799b488 --- /dev/null +++ b/Paparazzo/Core/Services/ImageCropping/ImageCroppingService.swift @@ -0,0 +1,87 @@ +import ImageSource + +struct ImageCroppingData { + let originalImage: ImageSource + var previewImage: ImageSource? + var parameters: ImageCroppingParameters? +} + +protocol ImageCroppingService: class { + func canvasSize(completion: @escaping (CGSize) -> ()) + func imageWithParameters(completion: @escaping (ImageCroppingData) -> ()) + func croppedImage(previewImage: CGImage, completion: @escaping (CroppedImageSource) -> ()) + func croppedImageAspectRatio(completion: @escaping (Float) -> ()) + func setCroppingParameters(_ parameters: ImageCroppingParameters) +} + +final class ImageCroppingServiceImpl: ImageCroppingService { + + // MARK: - Private + + private let originalImage: ImageSource + private let previewImage: ImageSource? + private var parameters: ImageCroppingParameters? + private let canvasSize: CGSize + + // MARK: - Init + + init(image: ImageSource, canvasSize: CGSize) { + + if let image = image as? CroppedImageSource { + originalImage = image.originalImage + parameters = image.croppingParameters + } else { + originalImage = image + } + + previewImage = image + + self.canvasSize = canvasSize + } + + // MARK: - ImageCroppingService + + func canvasSize(completion: @escaping (CGSize) -> ()) { + completion(canvasSize) + } + + func imageWithParameters(completion: @escaping (ImageCroppingData) -> ()) { + completion( + ImageCroppingData( + originalImage: originalImage, + previewImage: previewImage, + parameters: parameters + ) + ) + } + + func croppedImage(previewImage: CGImage, completion: @escaping (CroppedImageSource) -> ()) { + completion( + CroppedImageSource( + originalImage: originalImage, + sourceSize: canvasSize, + parameters: parameters, + previewImage: previewImage + ) + ) + } + + func croppedImageAspectRatio(completion: @escaping (Float) -> ()) { + if let parameters = parameters, parameters.cropSize.height > 0 { + completion(Float(parameters.cropSize.width / parameters.cropSize.height)) + } else { + originalImage.imageSize { size in + if let size = size { + completion(Float(size.width / size.height)) + } else { + completion(AspectRatio.defaultRatio.widthToHeightRatio()) + } + } + } + } + + func setCroppingParameters(_ parameters: ImageCroppingParameters) { + self.parameters = parameters + } + +} diff --git a/Paparazzo/Core/ThemeConfigurable.swift b/Paparazzo/Core/ThemeConfigurable.swift new file mode 100644 index 00000000..58330065 --- /dev/null +++ b/Paparazzo/Core/ThemeConfigurable.swift @@ -0,0 +1,5 @@ +public protocol ThemeConfigurable { + associatedtype ThemeType + + func setTheme(_ theme: ThemeType) +} diff --git a/Paparazzo/Core/VIPER/AssemblyFactory.swift b/Paparazzo/Core/VIPER/AssemblyFactory.swift deleted file mode 100644 index ae17cbe6..00000000 --- a/Paparazzo/Core/VIPER/AssemblyFactory.swift +++ /dev/null @@ -1,24 +0,0 @@ -public final class AssemblyFactory: CameraAssemblyFactory, MediaPickerAssemblyFactory, ImageCroppingAssemblyFactory, PhotoLibraryAssemblyFactory { - - private let theme: PaparazzoUITheme - - public init(theme: PaparazzoUITheme = PaparazzoUITheme()) { - self.theme = theme - } - - func cameraAssembly() -> CameraAssembly { - return CameraAssemblyImpl(theme: theme) - } - - public func mediaPickerAssembly() -> MediaPickerAssembly { - return MediaPickerAssemblyImpl(assemblyFactory: self, theme: theme) - } - - func imageCroppingAssembly() -> ImageCroppingAssembly { - return ImageCroppingAssemblyImpl(theme: theme) - } - - public func photoLibraryAssembly() -> PhotoLibraryAssembly { - return PhotoLibraryAssemblyImpl(theme: theme) - } -} diff --git a/Paparazzo/Core/VIPER/Camera/Assembly/CameraAssembly.swift b/Paparazzo/Core/VIPER/Camera/Assembly/CameraAssembly.swift index 0e553155..b55844eb 100644 --- a/Paparazzo/Core/VIPER/Camera/Assembly/CameraAssembly.swift +++ b/Paparazzo/Core/VIPER/Camera/Assembly/CameraAssembly.swift @@ -1,7 +1,7 @@ import UIKit protocol CameraAssembly: class { - func module() -> (UIView, CameraModuleInput) + func module(initialActiveCameraType: CameraType) -> (UIView, CameraModuleInput) } protocol CameraAssemblyFactory { diff --git a/Paparazzo/Core/VIPER/Camera/Assembly/CameraAssemblyImpl.swift b/Paparazzo/Core/VIPER/Camera/Assembly/CameraAssemblyImpl.swift index 049faf0e..83ead72c 100644 --- a/Paparazzo/Core/VIPER/Camera/Assembly/CameraAssemblyImpl.swift +++ b/Paparazzo/Core/VIPER/Camera/Assembly/CameraAssemblyImpl.swift @@ -1,18 +1,12 @@ import UIKit -final class CameraAssemblyImpl: CameraAssembly { - - private let theme: MediaPickerRootModuleUITheme - - init(theme: MediaPickerRootModuleUITheme) { - self.theme = theme - } +final class CameraAssemblyImpl: BasePaparazzoAssembly, CameraAssembly { // MARK: - CameraAssembly - func module() -> (UIView, CameraModuleInput) { + func module(initialActiveCameraType: CameraType) -> (UIView, CameraModuleInput) { - let cameraService = CameraServiceImpl() + let cameraService = CameraServiceImpl(initialActiveCameraType: initialActiveCameraType) let deviceOrientationService = DeviceOrientationServiceImpl() let interactor = CameraInteractorImpl( diff --git a/Paparazzo/Core/VIPER/Camera/View/CameraView.swift b/Paparazzo/Core/VIPER/Camera/View/CameraView.swift index d2b58f5c..1cc23085 100644 --- a/Paparazzo/Core/VIPER/Camera/View/CameraView.swift +++ b/Paparazzo/Core/VIPER/Camera/View/CameraView.swift @@ -1,7 +1,9 @@ import ImageSource import UIKit -final class CameraView: UIView, CameraViewInput { +final class CameraView: UIView, CameraViewInput, ThemeConfigurable { + + typealias ThemeType = MediaPickerRootModuleUITheme private let accessDeniedView = AccessDeniedView() private var cameraOutputView: CameraOutputView? @@ -97,9 +99,9 @@ final class CameraView: UIView, CameraViewInput { } } - // MARK: - CameraView + // MARK: - ThemeConfigurable - func setTheme(_ theme: MediaPickerRootModuleUITheme) { + func setTheme(_ theme: ThemeType) { accessDeniedView.setTheme(theme) } diff --git a/Paparazzo/Core/VIPER/ImageCropping/Assembly/ImageCroppingAssembly.swift b/Paparazzo/Core/VIPER/ImageCropping/Assembly/ImageCroppingAssembly.swift index d28f3878..93929434 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/Assembly/ImageCroppingAssembly.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/Assembly/ImageCroppingAssembly.swift @@ -5,7 +5,7 @@ protocol ImageCroppingAssembly: class { func module( image: ImageSource, canvasSize: CGSize, - configuration: (ImageCroppingModule) -> ()) + configure: (ImageCroppingModule) -> ()) -> UIViewController } diff --git a/Paparazzo/Core/VIPER/ImageCropping/Assembly/ImageCroppingAssemblyImpl.swift b/Paparazzo/Core/VIPER/ImageCropping/Assembly/ImageCroppingAssemblyImpl.swift index 25796928..e661e125 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/Assembly/ImageCroppingAssemblyImpl.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/Assembly/ImageCroppingAssemblyImpl.swift @@ -1,21 +1,22 @@ import ImageSource import UIKit -public final class ImageCroppingAssemblyImpl: ImageCroppingAssembly { - - private let theme: ImageCroppingUITheme - - init(theme: ImageCroppingUITheme) { - self.theme = theme - } +public final class ImageCroppingAssemblyImpl: BasePaparazzoAssembly , ImageCroppingAssembly { public func module( image: ImageSource, canvasSize: CGSize, - configuration: (ImageCroppingModule) -> () + configure: (ImageCroppingModule) -> () ) -> UIViewController { + + let imageCroppingService = serviceFactory.imageCroppingService( + image: image, + canvasSize: canvasSize + ) - let interactor = ImageCroppingInteractorImpl(image: image, canvasSize: canvasSize) + let interactor = ImageCroppingInteractorImpl( + imageCroppingService: imageCroppingService + ) let presenter = ImageCroppingPresenter( interactor: interactor @@ -27,7 +28,7 @@ public final class ImageCroppingAssemblyImpl: ImageCroppingAssembly { presenter.view = viewController - configuration(presenter) + configure(presenter) return viewController } diff --git a/Paparazzo/Core/VIPER/ImageCropping/Interactor/ImageCroppingInteractor.swift b/Paparazzo/Core/VIPER/ImageCropping/Interactor/ImageCroppingInteractor.swift index 4fbbc264..03f60037 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/Interactor/ImageCroppingInteractor.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/Interactor/ImageCroppingInteractor.swift @@ -5,7 +5,7 @@ protocol ImageCroppingInteractor: class { func canvasSize(completion: @escaping (CGSize) -> ()) - func imageWithParameters(completion: @escaping (_ original: ImageSource, _ preview: ImageSource?, _ parameters: ImageCroppingParameters?) -> ()) + func imageWithParameters(completion: @escaping (ImageCroppingData) -> ()) func croppedImage(previewImage: CGImage, completion: @escaping (CroppedImageSource) -> ()) func croppedImageAspectRatio(completion: @escaping (Float) -> ()) diff --git a/Paparazzo/Core/VIPER/ImageCropping/Interactor/ImageCroppingInteractorImpl.swift b/Paparazzo/Core/VIPER/ImageCropping/Interactor/ImageCroppingInteractorImpl.swift index 804ef964..3e53ca64 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/Interactor/ImageCroppingInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/Interactor/ImageCroppingInteractorImpl.swift @@ -2,59 +2,31 @@ import ImageSource final class ImageCroppingInteractorImpl: ImageCroppingInteractor { - private let originalImage: ImageSource - private let previewImage: ImageSource? - private var parameters: ImageCroppingParameters? - private let canvasSize: CGSize - - init(image: ImageSource, canvasSize: CGSize) { - - if let image = image as? CroppedImageSource { - originalImage = image.originalImage - parameters = image.croppingParameters - } else { - originalImage = image - } - - previewImage = image - - self.canvasSize = canvasSize + private let imageCroppingService: ImageCroppingService + + init(imageCroppingService: ImageCroppingService) { + self.imageCroppingService = imageCroppingService } // MARK: - CroppingInteractor func canvasSize(completion: @escaping (CGSize) -> ()) { - completion(canvasSize) + imageCroppingService.canvasSize(completion: completion) } - func imageWithParameters(completion: @escaping (_ original: ImageSource, _ preview: ImageSource?, _ parameters: ImageCroppingParameters?) -> ()) { - completion(originalImage, previewImage, parameters) + func imageWithParameters(completion: @escaping (ImageCroppingData) -> ()) { + imageCroppingService.imageWithParameters(completion: completion) } func croppedImage(previewImage: CGImage, completion: @escaping (CroppedImageSource) -> ()) { - completion(CroppedImageSource( - originalImage: originalImage, - sourceSize: canvasSize, - parameters: parameters, - previewImage: previewImage - )) + imageCroppingService.croppedImage(previewImage: previewImage, completion: completion) } func croppedImageAspectRatio(completion: @escaping (Float) -> ()) { - if let parameters = parameters, parameters.cropSize.height > 0 { - completion(Float(parameters.cropSize.width / parameters.cropSize.height)) - } else { - originalImage.imageSize { size in - if let size = size { - completion(Float(size.width / size.height)) - } else { - completion(AspectRatio.defaultRatio.widthToHeightRatio()) - } - } - } + imageCroppingService.croppedImageAspectRatio(completion: completion) } func setCroppingParameters(_ parameters: ImageCroppingParameters) { - self.parameters = parameters + imageCroppingService.setCroppingParameters(parameters) } } diff --git a/Paparazzo/Core/VIPER/ImageCropping/Presenter/ImageCroppingPresenter.swift b/Paparazzo/Core/VIPER/ImageCropping/Presenter/ImageCroppingPresenter.swift index d5019515..d76562fc 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/Presenter/ImageCroppingPresenter.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/Presenter/ImageCroppingPresenter.swift @@ -76,11 +76,11 @@ final class ImageCroppingPresenter: ImageCroppingModule { self?.setAspectRatio(isPortrait ? .portrait_3x4 : .landscape_4x3) - self?.interactor.imageWithParameters { originalImage, previewImage, croppingParameters in - self?.view?.setImage(originalImage, previewImage: previewImage) { + self?.interactor.imageWithParameters { data in + self?.view?.setImage(data.originalImage, previewImage: data.previewImage) { self?.view?.setControlsEnabled(true) - if let croppingParameters = croppingParameters { + if let croppingParameters = data.parameters { self?.view?.setCroppingParameters(croppingParameters) diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift new file mode 100644 index 00000000..b6cdbb33 --- /dev/null +++ b/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift @@ -0,0 +1,105 @@ +import ImageSource +import UIKit + +final class CroppingPreviewView: UIView { + + /// Максимальный размер оригинальной картинки. Если меньше размера самой картинки, она будет даунскейлиться. + private var sourceImageMaxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) + + private let previewView = PhotoTweakView() + + // MARK: - Private + + private var aspectRatio: AspectRatio = .portrait_3x4 + + // MARK: - Init + + init() { + super.init(frame: .zero) + + previewView.setMaskVisible(false) + + clipsToBounds = true + + addSubview(previewView) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - UIView + + override func layoutSubviews() { + super.layoutSubviews() + + previewView.frame = bounds + } + + // MARK: - CroppingPreviewView + + var cropAspectRatio: CGFloat { + get { return previewView.cropAspectRatio } + set { previewView.cropAspectRatio = newValue } + } + + var onCroppingParametersChange: ((ImageCroppingParameters) -> ())? { + get { return previewView.onCroppingParametersChange } + set { previewView.onCroppingParametersChange = newValue } + } + + var onPreviewImageWillLoading: (() -> ())? + var onPreviewImageDidLoad: ((UIImage) -> ())? + var onImageDidLoad: (() -> ())? + + func setImage(_ image: ImageSource, previewImage: ImageSource?, completion: (() -> ())?) { + + if let previewImage = previewImage { + + let screenSize = UIScreen.main.bounds.size + let previewOptions = ImageRequestOptions(size: .fitSize(screenSize), deliveryMode: .progressive) + + onPreviewImageWillLoading?() + + previewImage.requestImage(options: previewOptions) { [weak self] (result: ImageRequestResult) in + if let image = result.image { + self?.onPreviewImageDidLoad?(image) + } + } + } + + let options = ImageRequestOptions(size: .fitSize(sourceImageMaxSize), deliveryMode: .best) + + image.requestImage(options: options) { [weak self] (result: ImageRequestResult) in + if let image = result.image { + self?.previewView.setImage(image) + self?.onImageDidLoad?() + } + completion?() + } + } + + func setImageTiltAngle(_ angle: Float) { + previewView.setTiltAngle(angle.degreesToRadians()) + } + + func turnCounterclockwise() { + previewView.turnCounterclockwise() + } + + func setCroppingParameters(_ parameters: ImageCroppingParameters) { + previewView.setCroppingParameters(parameters) + } + + func setGridVisible(_ visible: Bool) { + previewView.setGridVisible(visible) + } + + func setCanvasSize(_ size: CGSize) { + sourceImageMaxSize = size + } + + func cropPreviewImage() -> CGImage? { + return previewView.cropPreviewImage() + } +} diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift index 86746663..ba9b9ffc 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift @@ -1,6 +1,8 @@ import UIKit -final class ImageCroppingControlsView: UIView { +final class ImageCroppingControlsView: UIView, ThemeConfigurable { + + typealias ThemeType = ImageCroppingUITheme // MARK: - Subviews @@ -95,20 +97,9 @@ final class ImageCroppingControlsView: UIView { confirmButton.center = CGPoint(x: bounds.right - bounds.size.width * 0.25, y: discardButton.centerY) } - // MARK: - ImageCroppingControlsView - - var onDiscardButtonTap: (() -> ())? - var onConfirmButtonTap: (() -> ())? - var onRotationCancelButtonTap: (() -> ())? - var onRotateButtonTap: (() -> ())? - var onGridButtonTap: (() -> ())? - - var onRotationAngleChange: ((Float) -> ())? { - get { return rotationSliderView.onSliderValueChange } - set { rotationSliderView.onSliderValueChange = newValue } - } + // MARK: - ThemeConfigurable - func setTheme(_ theme: ImageCroppingUITheme) { + func setTheme(_ theme: ThemeType) { rotationButton.setImage(theme.rotationIcon, for: .normal) gridButton.setImage(theme.gridIcon, for: .normal) gridButton.setImage(theme.gridSelectedIcon, for: .selected) @@ -121,6 +112,19 @@ final class ImageCroppingControlsView: UIView { rotationCancelButton.setImage(theme.cancelRotationButtonIcon, for: .normal) } + // MARK: - ImageCroppingControlsView + + var onDiscardButtonTap: (() -> ())? + var onConfirmButtonTap: (() -> ())? + var onRotationCancelButtonTap: (() -> ())? + var onRotateButtonTap: (() -> ())? + var onGridButtonTap: (() -> ())? + + var onRotationAngleChange: ((Float) -> ())? { + get { return rotationSliderView.onSliderValueChange } + set { rotationSliderView.onSliderValueChange = newValue } + } + func setMinimumRotation(degrees: Float) { rotationSliderView.setMiminumValue(degrees) } diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingMask.swift b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingMask.swift new file mode 100644 index 00000000..a2983001 --- /dev/null +++ b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingMask.swift @@ -0,0 +1,63 @@ +final class ImageCroppingMask: UIView { + + private let topMask = UIView() + private let bottomMask = UIView() + private let leftMask = UIView() + private let rightMask = UIView() + + // MARK: - Init + + init() { + super.init(frame: .zero) + + let maskColor = UIColor.white.withAlphaComponent(0.9) + + topMask.backgroundColor = maskColor + bottomMask.backgroundColor = maskColor + leftMask.backgroundColor = maskColor + rightMask.backgroundColor = maskColor + + addSubview(topMask) + addSubview(bottomMask) + addSubview(leftMask) + addSubview(rightMask) + + backgroundColor = .clear + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - CroppingMask + + func performLayoutUpdate(with cropSize: CGSize) { + let horizontalMaskSize = CGSize( + width: bounds.size.width, + height: (bounds.size.height - cropSize.height) / 2 + ) + + let verticalMaskSize = CGSize( + width: (bounds.size.width - cropSize.width) / 2, + height: bounds.size.height - horizontalMaskSize.height + ) + + topMask.frame = CGRect( + origin: CGPoint(x: self.bounds.left, y: self.bounds.top), + size: horizontalMaskSize + ) + bottomMask.frame = CGRect( + origin: CGPoint(x: self.bounds.left, y: self.bounds.bottom - horizontalMaskSize.height), + size: horizontalMaskSize + ) + leftMask.frame = CGRect( + origin: CGPoint(x: self.bounds.left, y: self.topMask.bottom), + size: verticalMaskSize + ) + rightMask.frame = CGRect( + origin: CGPoint(x: self.bounds.right - verticalMaskSize.width, y: self.topMask.bottom), + size: verticalMaskSize + ) + } + +} diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingUITheme.swift b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingUITheme.swift new file mode 100644 index 00000000..18c75323 --- /dev/null +++ b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingUITheme.swift @@ -0,0 +1,12 @@ +public protocol ImageCroppingUITheme { + var rotationIcon: UIImage? { get } + var gridIcon: UIImage? { get } + var gridSelectedIcon: UIImage? { get } + var cropperDiscardIcon: UIImage? { get } + var cropperConfirmIcon: UIImage? { get } + + var cancelRotationBackgroundColor: UIColor { get } + var cancelRotationTitleColor: UIColor { get } + var cancelRotationTitleFont: UIFont { get } + var cancelRotationButtonIcon: UIImage? { get } +} diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift index 80c18fa1..6ebc4c23 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift @@ -1,14 +1,16 @@ import ImageSource import UIKit -final class ImageCroppingView: UIView { +final class ImageCroppingView: UIView, ThemeConfigurable { + + typealias ThemeType = ImageCroppingUITheme // MARK: - Subviews /// Вьюха, которая показывается до того, как будет доступна полная картинка для редактирования (чтобы избежать моргания) private let splashView = UIImageView() - private let previewView = PhotoTweakView() + private let previewView = CroppingPreviewView() private let controlsView = ImageCroppingControlsView() private let aspectRatioButton = UIButton() private let titleLabel = UILabel() @@ -42,6 +44,21 @@ final class ImageCroppingView: UIView { splashView.contentMode = .scaleAspectFill + previewView.onPreviewImageWillLoading = { [weak self] in + self?.splashView.isHidden = false + } + + previewView.onPreviewImageDidLoad = { [weak self] image in + if self?.splashView.isHidden == false { + self?.splashView.image = image + } + } + + previewView.onImageDidLoad = { [weak self] in + self?.splashView.isHidden = true + self?.splashView.image = nil + } + addSubview(previewView) addSubview(splashView) addSubview(controlsView) @@ -78,11 +95,10 @@ final class ImageCroppingView: UIView { top: bounds.top, bottom: controlsView.top ) - layoutSplashView() } - private func layoutSplashView() { + func layoutSplashView() { let height: CGFloat @@ -99,6 +115,12 @@ final class ImageCroppingView: UIView { splashView.center = previewView.center } + // MARK: - ThemeConfigurable + + func setTheme(_ theme: ThemeType) { + controlsView.setTheme(theme) + } + // MARK: - ImageCroppingView var onDiscardButtonTap: (() -> ())? { @@ -136,35 +158,11 @@ final class ImageCroppingView: UIView { } func setImage(_ image: ImageSource, previewImage: ImageSource?, completion: (() -> ())?) { - - if let previewImage = previewImage { - - let screenSize = UIScreen.main.bounds.size - let previewOptions = ImageRequestOptions(size: .fitSize(screenSize), deliveryMode: .progressive) - - splashView.isHidden = false - - previewImage.requestImage(options: previewOptions) { [weak self] (result: ImageRequestResult) in - if let image = result.image, self?.splashView.isHidden == false { - self?.splashView.image = image - } - } - } - - let options = ImageRequestOptions(size: .fitSize(sourceImageMaxSize), deliveryMode: .best) - - image.requestImage(options: options) { [weak self] (result: ImageRequestResult) in - if let image = result.image { - self?.previewView.setImage(image) - self?.splashView.isHidden = true - self?.splashView.image = nil - } - completion?() - } + previewView.setImage(image, previewImage: previewImage, completion: completion) } func setImageTiltAngle(_ angle: Float) { - previewView.setTiltAngle(angle.degreesToRadians()) + previewView.setImageTiltAngle(angle) } func turnCounterclockwise() { @@ -180,7 +178,7 @@ final class ImageCroppingView: UIView { } func setCanvasSize(_ size: CGSize) { - sourceImageMaxSize = size + previewView.setCanvasSize(size) } func setControlsEnabled(_ enabled: Bool) { @@ -188,10 +186,6 @@ final class ImageCroppingView: UIView { aspectRatioButton.isEnabled = enabled } - func setTheme(_ theme: ImageCroppingUITheme) { - controlsView.setTheme(theme) - } - func setTitle(_ title: String) { titleLabel.text = title } @@ -261,9 +255,6 @@ final class ImageCroppingView: UIView { private var aspectRatio: AspectRatio = .portrait_3x4 - /// Максимальный размер оригинальной картинки. Если меньше размера самой картинки, она будет даунскейлиться. - private var sourceImageMaxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) - private func aspectRatioButtonSize() -> CGSize { switch aspectRatio { case .portrait_3x4: diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingViewController.swift b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingViewController.swift index 70fb2ff5..ab059af8 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingViewController.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingViewController.swift @@ -1,7 +1,9 @@ import ImageSource import UIKit -final class ImageCroppingViewController: UIViewController, ImageCroppingViewInput { +final class ImageCroppingViewController: PaparazzoViewController, ImageCroppingViewInput, ThemeConfigurable { + + typealias ThemeType = ImageCroppingUITheme private let imageCroppingView = ImageCroppingView() @@ -135,21 +137,12 @@ final class ImageCroppingViewController: UIViewController, ImageCroppingViewInpu imageCroppingView.setGridButtonSelected(selected) } - // MARK: - ImageCroppingViewController + // MARK: - ThemeConfigurable - func setTheme(_ theme: ImageCroppingUITheme) { + func setTheme(_ theme: ThemeType) { imageCroppingView.setTheme(theme) } - // MARK: - Dispose bag - - private var disposables = [AnyObject]() - - func addDisposable(_ object: AnyObject) { - disposables.append(object) - } - - // MARK: - Private private func forcePortraitOrientation() { diff --git a/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssembly.swift b/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssembly.swift new file mode 100644 index 00000000..78acdce6 --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssembly.swift @@ -0,0 +1,14 @@ +import ImageSource +import UIKit + +public protocol MaskCropperAssembly: class { + func module( + data: MaskCropperData, + croppingOverlayProvider: CroppingOverlayProvider, + configure: (MaskCropperModule) -> ()) + -> UIViewController +} + +public protocol MaskCropperAssemblyFactory: class { + func maskCropperAssembly() -> MaskCropperAssembly +} diff --git a/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssemblyImpl.swift b/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssemblyImpl.swift new file mode 100644 index 00000000..f056a12a --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssemblyImpl.swift @@ -0,0 +1,44 @@ +import ImageSource +import UIKit + +public final class MaskCropperAssemblyImpl: BasePaparazzoAssembly, MaskCropperAssembly { + + public func module( + data: MaskCropperData, + croppingOverlayProvider: CroppingOverlayProvider, + configure: (MaskCropperModule) -> () + ) -> UIViewController { + + let imageCroppingService = serviceFactory.imageCroppingService( + image: data.photo.image, + canvasSize: data.cropCanvasSize + ) + + let interactor = MaskCropperInteractorImpl( + imageCroppingService: imageCroppingService + ) + + let viewController = MaskCropperViewController( + croppingOverlayProvider: croppingOverlayProvider + ) + + let router = CircleImageCropperUIKitRouter( + viewController: viewController + ) + + let presenter = MaskCropperPresenter( + interactor: interactor, + router: router + ) + + viewController.addDisposable(presenter) + viewController.setTheme(theme) + + presenter.view = viewController + + configure(presenter) + + return viewController + } + +} diff --git a/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractor.swift b/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractor.swift new file mode 100644 index 00000000..9929d9f7 --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractor.swift @@ -0,0 +1,7 @@ +import ImageSource + +protocol MaskCropperInteractor: class { + func canvasSize(completion: @escaping (CGSize) -> ()) + func imageWithParameters(completion: @escaping (ImageCroppingData) -> ()) + func croppedImage(previewImage: CGImage, completion: @escaping (CroppedImageSource) -> ()) +} diff --git a/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractorImpl.swift b/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractorImpl.swift new file mode 100644 index 00000000..c2acddfe --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractorImpl.swift @@ -0,0 +1,24 @@ +import ImageSource + +final class MaskCropperInteractorImpl: MaskCropperInteractor { + + private let imageCroppingService: ImageCroppingService + + init(imageCroppingService: ImageCroppingService) { + self.imageCroppingService = imageCroppingService + } + + // MARK: - CroppingInteractor + + func canvasSize(completion: @escaping (CGSize) -> ()) { + imageCroppingService.canvasSize(completion: completion) + } + + func imageWithParameters(completion: @escaping (ImageCroppingData) -> ()) { + imageCroppingService.imageWithParameters(completion: completion) + } + + func croppedImage(previewImage: CGImage, completion: @escaping (CroppedImageSource) -> ()) { + imageCroppingService.croppedImage(previewImage: previewImage, completion: completion) + } +} diff --git a/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperData.swift b/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperData.swift new file mode 100644 index 00000000..c3460351 --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperData.swift @@ -0,0 +1,15 @@ +public struct MaskCropperData { + + public let photo: MediaPickerItem + public let cropCanvasSize: CGSize + + public init( + photo: MediaPickerItem, + cropCanvasSize: CGSize) + { + self.photo = photo + self.cropCanvasSize = cropCanvasSize + } + +} + diff --git a/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperModule.swift b/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperModule.swift new file mode 100644 index 00000000..ad81af71 --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperModule.swift @@ -0,0 +1,10 @@ +import ImageSource + +public protocol MaskCropperModule: class { + + func dismissModule() + + var onClose: (() -> ())? { get set } + var onDiscard: (() -> ())? { get set } + var onConfirm: ((ImageSource) -> ())? { get set } +} diff --git a/Paparazzo/Core/VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift b/Paparazzo/Core/VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift new file mode 100644 index 00000000..1c649489 --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift @@ -0,0 +1,64 @@ +import ImageSource + +final class MaskCropperPresenter: MaskCropperModule { + + private let interactor: MaskCropperInteractor + private let router: MaskCropperRouter + + weak var view: MaskCropperViewInput? { + didSet { + setUpView() + } + } + + init(interactor: MaskCropperInteractor, router: MaskCropperRouter) { + self.interactor = interactor + self.router = router + } + + func setUpView() { + + view?.setConfirmButtonTitle("Готово") + + view?.onDiscardTap = { [weak self] in + self?.onDiscard?() + } + + view?.onCloseTap = { [weak self] in + self?.onClose?() + } + + view?.onConfirmTap = { [weak self] previewImage in + if let previewImage = previewImage { + self?.interactor.croppedImage(previewImage: previewImage) { image in + self?.onConfirm?(image) + } + } else { + self?.onDiscard?() + } + } + + interactor.canvasSize { [weak self] canvasSize in + self?.view?.setCanvasSize(canvasSize) + } + + interactor.imageWithParameters { [weak self] data in + self?.view?.setImage(data.originalImage, previewImage: data.previewImage) { + self?.view?.setControlsEnabled(true) + + if let croppingParameters = data.parameters { + self?.view?.setCroppingParameters(croppingParameters) + } + } + } + } + + var onDiscard: (() -> ())? + var onClose: (() -> ())? + var onConfirm: ((ImageSource) -> ())? + + func dismissModule() { + router.dismissCurrentModule() + } + +} diff --git a/Paparazzo/Core/VIPER/MaskCropper/Router/MaskCropperRouter.swift b/Paparazzo/Core/VIPER/MaskCropper/Router/MaskCropperRouter.swift new file mode 100644 index 00000000..e42bee8b --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/Router/MaskCropperRouter.swift @@ -0,0 +1,6 @@ +import ImageSource + +protocol MaskCropperRouter: class { + func focusOnCurrentModule() + func dismissCurrentModule() +} diff --git a/Paparazzo/Core/VIPER/MaskCropper/Router/MaskCropperUIKitRouter.swift b/Paparazzo/Core/VIPER/MaskCropper/Router/MaskCropperUIKitRouter.swift new file mode 100644 index 00000000..adb61e73 --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/Router/MaskCropperUIKitRouter.swift @@ -0,0 +1,4 @@ +import ImageSource +import UIKit + +final class CircleImageCropperUIKitRouter: BaseUIKitRouter, MaskCropperRouter {} diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift new file mode 100644 index 00000000..c157fe11 --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift @@ -0,0 +1,64 @@ +import UIKit + +final class MaskCropperControlsView: UIView, ThemeConfigurable { + + typealias ThemeType = MaskCropperUITheme + + // MARK: - Subviews + + private let discardButton = UIButton(type: .custom) + + // MARK: - Init + + init() { + super.init(frame: .zero) + + addSubview(discardButton) + + discardButton.addTarget( + self, + action: #selector(onDiscardTap(_:)), + for: .touchUpInside + ) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Layout + + public override func layoutSubviews() { + super.layoutSubviews() + + discardButton.size = CGSize( + width: 30, + height: 30 + ) + + discardButton.centerX = centerX + discardButton.bottom = height - 22 + } + + // MARK: - ThemeConfigurable + + func setTheme(_ theme: ThemeType) { + discardButton.setImage( + theme.maskCropperDiscardPhotoIcon, + for: .normal + ) + } + + // MARK: - MaskCropperControlsView + + var onDiscardTap: (() -> ())? + + func setControlsEnabled(_ enabled: Bool) { + discardButton.isEnabled = enabled + } + + // MARK: - Actions + @objc private func onDiscardTap(_ sender: UIButton) { + onDiscardTap?() + } +} diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperOverlayView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperOverlayView.swift new file mode 100644 index 00000000..8b6bef73 --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperOverlayView.swift @@ -0,0 +1,45 @@ +import UIKit + +final class MaskCropperOverlayView: UIView { + + private let croppingOverlayProvider: CroppingOverlayProvider + + // MARK: - Init + + init(croppingOverlayProvider: CroppingOverlayProvider) { + + self.croppingOverlayProvider = croppingOverlayProvider + + super.init(frame: .zero) + + isOpaque = false + } + + // MARK: - Draw + + override func draw(_ rect: CGRect) { + super.draw(rect) + + let context = UIGraphicsGetCurrentContext() + context?.setFillColor(UIColor.white.withAlphaComponent(0.6).cgColor) + context?.fill(rect) + context?.saveGState() + context?.setBlendMode(.clear) + + let rectToCrop = croppingOverlayProvider.calculateRectToCrop(in: bounds) + if rect.intersects(rectToCrop) { + context?.setFillColor(UIColor.clear.cgColor) + + context?.addPath(croppingOverlayProvider.croppingPath(in: rectToCrop)) + context?.drawPath(using: .fill) + } + + context?.restoreGState() + } + + // MARK: - Unused + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperUITheme.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperUITheme.swift new file mode 100644 index 00000000..37e69001 --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperUITheme.swift @@ -0,0 +1,12 @@ +public protocol MaskCropperUITheme { + var maskCropperDiscardPhotoIcon: UIImage? { get } + var maskCropperCloseButtonIcon: UIImage? { get } + + var maskCropperButtonsBackgroundNormalColor: UIColor { get } + var maskCropperButtonsBackgroundHighlightedColor: UIColor { get } + var maskCropperButtonsBackgroundDisabledColor: UIColor { get } + var maskCropperConfirmButtonTitleColor: UIColor { get } + var maskCropperConfirmButtonTitleHighlightedColor: UIColor { get } + + var maskCropperConfirmButtonTitleFont: UIFont { get } +} diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift new file mode 100644 index 00000000..0a496b9c --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift @@ -0,0 +1,176 @@ +import ImageSource +import UIKit + +final class MaskCropperView: UIView, ThemeConfigurable { + + typealias ThemeType = MaskCropperUITheme + + private let overlayView: MaskCropperOverlayView + private let controlsView = MaskCropperControlsView() + private let previewView = CroppingPreviewView() + private let closeButton = UIButton() + private let confirmButton = UIButton() + + // MARK: - Constants + + private let aspectRatio = CGFloat(AspectRatio.portrait_3x4.widthToHeightRatio()) + private let closeButtonSize = CGSize(width: 38, height: 38) + private let continueButtonHeight = CGFloat(38) + private let continueButtonContentInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + + // MARK: - Init + + init(croppingOverlayProvider: CroppingOverlayProvider) { + + overlayView = MaskCropperOverlayView( + croppingOverlayProvider: croppingOverlayProvider + ) + + super.init(frame: .zero) + + backgroundColor = .white + clipsToBounds = true + + previewView.setGridVisible(false) + + addSubview(previewView) + addSubview(overlayView) + addSubview(controlsView) + addSubview(closeButton) + addSubview(confirmButton) + + setupButtons() + + overlayView.isUserInteractionEnabled = false + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - ThemeConfigurable + + func setTheme(_ theme: MaskCropperUITheme) { + controlsView.setTheme(theme) + + confirmButton.setTitleColor(theme.maskCropperConfirmButtonTitleColor, for: .normal) + confirmButton.titleLabel?.font = theme.maskCropperConfirmButtonTitleFont + + closeButton.setImage(theme.maskCropperCloseButtonIcon, for: .normal) + + confirmButton.setTitleColor( + theme.maskCropperConfirmButtonTitleColor, + for: .normal + ) + confirmButton.setTitleColor( + theme.maskCropperConfirmButtonTitleHighlightedColor, + for: .highlighted + ) + + let onePointSize = CGSize(width: 1, height: 1) + for button in [confirmButton, closeButton] { + button.setBackgroundImage( + UIImage.imageWithColor(theme.maskCropperButtonsBackgroundNormalColor, imageSize: onePointSize), + for: .normal + ) + button.setBackgroundImage( + UIImage.imageWithColor(theme.maskCropperButtonsBackgroundHighlightedColor, imageSize: onePointSize), + for: .highlighted + ) + button.setBackgroundImage( + UIImage.imageWithColor(theme.maskCropperButtonsBackgroundDisabledColor, imageSize: onePointSize), + for: .disabled + ) + } + } + + // MARK: - Layout + + public override func layoutSubviews() { + + previewView.width = width + previewView.height = height * aspectRatio + + overlayView.frame = previewView.frame + + controlsView.top = previewView.bottom + controlsView.width = width + controlsView.height = height - previewView.height + + closeButton.frame = CGRect( + x: 8, + y: 8, + width: closeButton.width, + height: closeButton.height + ) + + confirmButton.frame = CGRect( + x: bounds.right - 8 - confirmButton.width, + y: 8, + width: confirmButton.width, + height: confirmButton.height + ) + } + + // MARK: - MaskCropperView + + var onCloseTap: (() -> ())? + var onConfirmTap: ((_ previewImage: CGImage?) -> ())? + + var onDiscardTap: (() -> ())? { + get { return controlsView.onDiscardTap } + set { controlsView.onDiscardTap = newValue } + } + + func setCroppingParameters(_ parameters: ImageCroppingParameters) { + previewView.setCroppingParameters(parameters) + } + + func setImage(_ imageSource: ImageSource, previewImage: ImageSource?, completion: @escaping () -> ()) { + previewView.setImage(imageSource, previewImage: previewImage, completion: completion) + } + + func setCanvasSize(_ canvasSize: CGSize) { + previewView.setCanvasSize(canvasSize) + } + + func setControlsEnabled(_ enabled: Bool) { + controlsView.setControlsEnabled(enabled) + } + + func setConfirmButtonTitle(_ title: String) { + confirmButton.setTitle(title, for: .normal) + confirmButton.size = CGSize(width: confirmButton.sizeThatFits().width, height: continueButtonHeight) + } + + // MARK: - Private + + private func setupButtons() { + closeButton.layer.cornerRadius = closeButtonSize.height / 2 + closeButton.layer.masksToBounds = true + closeButton.size = closeButtonSize + closeButton.addTarget( + self, + action: #selector(onCloseTap(_:)), + for: .touchUpInside + ) + + confirmButton.layer.cornerRadius = continueButtonHeight / 2 + confirmButton.layer.masksToBounds = true + confirmButton.contentEdgeInsets = continueButtonContentInsets + confirmButton.addTarget( + self, + action: #selector(onConfirmTap(_:)), + for: .touchUpInside + ) + } + + @objc private func onCloseTap(_: UIButton) { + onCloseTap?() + } + + @objc private func onConfirmTap(_: UIButton) { + onConfirmTap?(previewView.cropPreviewImage()) + } + +} diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift new file mode 100644 index 00000000..d7844f5a --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift @@ -0,0 +1,117 @@ +import ImageSource + +final class MaskCropperViewController: + PaparazzoViewController, + MaskCropperViewInput, + ThemeConfigurable +{ + + typealias ThemeType = MaskCropperUITheme + + private let circleImageCroppingView: MaskCropperView + + // MARK: - Init + + init(croppingOverlayProvider: CroppingOverlayProvider) { + circleImageCroppingView = MaskCropperView( + croppingOverlayProvider: croppingOverlayProvider) + + super.init() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - UIViewController + + override func loadView() { + view = circleImageCroppingView + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // Cropping doesn't work in landscape at the moment. + // Forcing orientation doesn't produce severe issues at the moment. + forcePortraitOrientation() + navigationController?.setNavigationBarHidden(true, animated: animated) + UIApplication.shared.setStatusBarHidden(true, with: .fade) + } + + override var prefersStatusBarHidden: Bool { + return true + } + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return .portrait + } + + // MARK: - MaskCropperViewInput + + var onConfirmTap: ((CGImage?) -> ())? { + get { return circleImageCroppingView.onConfirmTap } + set { circleImageCroppingView.onConfirmTap = newValue } + } + + var onCloseTap: (() -> ())? { + get { return circleImageCroppingView.onCloseTap } + set { circleImageCroppingView.onCloseTap = newValue } + } + + var onDiscardTap: (() -> ())? { + get { return circleImageCroppingView.onDiscardTap } + set { circleImageCroppingView.onDiscardTap = newValue } + } + + var onCroppingParametersChange: ((ImageCroppingParameters) -> ())? + + func setConfirmButtonTitle(_ title: String) { + circleImageCroppingView.setConfirmButtonTitle(title) + } + + func setImage(_ imageSource: ImageSource, previewImage: ImageSource?, completion: @escaping () -> ()) { + circleImageCroppingView.setImage(imageSource, previewImage: previewImage, completion: completion) + } + + func setCanvasSize(_ canvasSize: CGSize) { + circleImageCroppingView.setCanvasSize(canvasSize) + } + + func setCroppingParameters(_ parameters: ImageCroppingParameters) { + circleImageCroppingView.setCroppingParameters(parameters) + } + + func setControlsEnabled(_ enabled: Bool) { + circleImageCroppingView.setControlsEnabled(enabled) + } + + func setCroppingOverlayProvider(_: CroppingOverlayProvider) { + + } + + // MARK: - ThemeConfigurable + + func setTheme(_ theme: ThemeType) { + circleImageCroppingView.setTheme(theme) + } + + // MARK: - Private + + private func forcePortraitOrientation() { + let initialDeviceOrientation = UIDevice.current.orientation + let targetDeviceOrientation = UIDeviceOrientation.portrait + let targetInterfaceOrientation = UIInterfaceOrientation.portrait + + if UIDevice.current.orientation != targetDeviceOrientation { + + UIApplication.shared.setStatusBarOrientation(targetInterfaceOrientation, animated: true) + UIDevice.current.setValue(NSNumber(value: targetInterfaceOrientation.rawValue as Int), forKey: "orientation") + + DispatchQueue.main.async { + UIDevice.current.setValue(NSNumber(value: initialDeviceOrientation.rawValue as Int), forKey: "orientation") + } + } + } + +} diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewInput.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewInput.swift new file mode 100644 index 00000000..cb4b4694 --- /dev/null +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewInput.swift @@ -0,0 +1,16 @@ +import ImageSource + +protocol MaskCropperViewInput: class { + func setCroppingOverlayProvider(_: CroppingOverlayProvider) + func setConfirmButtonTitle(_: String) + func setImage(_: ImageSource, previewImage: ImageSource?, completion: @escaping () -> ()) + func setCroppingParameters(_: ImageCroppingParameters) + func setCanvasSize(_: CGSize) + func setControlsEnabled(_: Bool) + + var onCloseTap: (() -> ())? { get set } + var onConfirmTap: ((_ previewImage: CGImage?) -> ())? { get set } + var onDiscardTap: (() -> ())? { get set } + + var onCroppingParametersChange: ((ImageCroppingParameters) -> ())? { get set } +} diff --git a/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssembly.swift b/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssembly.swift index 9ca26ee7..b4d6b3bc 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssembly.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssembly.swift @@ -2,12 +2,8 @@ import UIKit public protocol MediaPickerAssembly: class { func module( - items: [MediaPickerItem], - selectedItem: MediaPickerItem?, - maxItemsCount: Int?, - cropEnabled: Bool, - cropCanvasSize: CGSize, - configuration: (MediaPickerModule) -> ()) + data: MediaPickerData, + configure: (MediaPickerModule) -> ()) -> UIViewController } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift index 307ed15e..1de34bae 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift @@ -1,35 +1,30 @@ import UIKit -public final class MediaPickerAssemblyImpl: MediaPickerAssembly { +public final class MediaPickerAssemblyImpl: BasePaparazzoAssembly, MediaPickerAssembly { typealias AssemblyFactory = CameraAssemblyFactory & ImageCroppingAssemblyFactory & PhotoLibraryAssemblyFactory private let assemblyFactory: AssemblyFactory - private let theme: PaparazzoUITheme - init(assemblyFactory: AssemblyFactory, theme: PaparazzoUITheme) { + init(assemblyFactory: AssemblyFactory, theme: PaparazzoUITheme, serviceFactory: ServiceFactory) { self.assemblyFactory = assemblyFactory - self.theme = theme + super.init(theme: theme, serviceFactory: serviceFactory) } // MARK: - MediaPickerAssembly public func module( - items: [MediaPickerItem], - selectedItem: MediaPickerItem?, - maxItemsCount: Int?, - cropEnabled: Bool, - cropCanvasSize: CGSize, - configuration: (MediaPickerModule) -> ()) + data: MediaPickerData, + configure: (MediaPickerModule) -> ()) -> UIViewController { let interactor = MediaPickerInteractorImpl( - items: items, - selectedItem: selectedItem, - maxItemsCount: maxItemsCount, - cropCanvasSize: cropCanvasSize, - deviceOrientationService: DeviceOrientationServiceImpl(), - latestLibraryPhotoProvider: PhotoLibraryLatestPhotoProviderImpl() + items: data.items, + selectedItem: data.selectedItem, + maxItemsCount: data.maxItemsCount, + cropCanvasSize: data.cropCanvasSize, + deviceOrientationService: serviceFactory.deviceOrientationService(), + latestLibraryPhotoProvider: serviceFactory.photoLibraryLatestPhotoProvider() ) let viewController = MediaPickerViewController() @@ -40,7 +35,7 @@ public final class MediaPickerAssemblyImpl: MediaPickerAssembly { ) let cameraAssembly = assemblyFactory.cameraAssembly() - let (cameraView, cameraModuleInput) = cameraAssembly.module() + let (cameraView, cameraModuleInput) = cameraAssembly.module(initialActiveCameraType: data.initialActiveCameraType) let presenter = MediaPickerPresenter( interactor: interactor, @@ -51,11 +46,11 @@ public final class MediaPickerAssemblyImpl: MediaPickerAssembly { viewController.addDisposable(presenter) viewController.setCameraView(cameraView) viewController.setTheme(theme) - viewController.setShowsCropButton(cropEnabled) + viewController.setShowsCropButton(data.cropEnabled) presenter.view = viewController - configuration(presenter) + configure(presenter) return viewController } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift new file mode 100644 index 00000000..8116d75b --- /dev/null +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift @@ -0,0 +1,26 @@ +import UIKit + +public struct MediaPickerData { + public let items: [MediaPickerItem] + public let selectedItem: MediaPickerItem? + public let maxItemsCount: Int? + public let cropEnabled: Bool + public let cropCanvasSize: CGSize + public let initialActiveCameraType: CameraType + + public init( + items: [MediaPickerItem], + selectedItem: MediaPickerItem?, + maxItemsCount: Int?, + cropEnabled: Bool, + cropCanvasSize: CGSize, + initialActiveCameraType: CameraType = .back) + { + self.items = items + self.selectedItem = selectedItem + self.maxItemsCount = maxItemsCount + self.cropEnabled = cropEnabled + self.cropCanvasSize = cropCanvasSize + self.initialActiveCameraType = initialActiveCameraType + } +} diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift index 3a500a26..08b335af 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift @@ -5,6 +5,7 @@ public protocol MediaPickerModule: class { func setContinueButtonTitle(_: String) func setContinueButtonEnabled(_: Bool) + func setContinueButtonVisible(_: Bool) var onItemsAdd: (([MediaPickerItem]) -> ())? { get set } var onItemUpdate: ((MediaPickerItem) -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index ba89a9c5..a55a1698 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -39,6 +39,10 @@ final class MediaPickerPresenter: MediaPickerModule { view?.setContinueButtonEnabled(enabled) } + func setContinueButtonVisible(_ visible: Bool) { + view?.setContinueButtonVisible(visible) + } + func setItems(_ items: [MediaPickerItem], selectedItem: MediaPickerItem?) { addItems(items, fromCamera: false) { [weak self] in if let selectedItem = selectedItem { @@ -60,41 +64,40 @@ final class MediaPickerPresenter: MediaPickerModule { private var continueButtonTitle: String? private func setUpView() { - weak var `self` = self view?.setContinueButtonTitle(continueButtonTitle ?? "Далее") view?.setPhotoTitle("Фото 1") view?.setCameraControlsEnabled(false) - cameraModuleInput.getOutputParameters { parameters in + cameraModuleInput.getOutputParameters { [weak self] parameters in if let parameters = parameters { self?.view?.setCameraOutputParameters(parameters) self?.view?.setCameraControlsEnabled(true) } } - cameraModuleInput.isFlashAvailable { flashAvailable in + cameraModuleInput.isFlashAvailable { [weak self] flashAvailable in self?.view?.setFlashButtonVisible(flashAvailable) } - cameraModuleInput.isFlashEnabled { isFlashEnabled in + cameraModuleInput.isFlashEnabled { [weak self] isFlashEnabled in self?.view?.setFlashButtonOn(isFlashEnabled) } - cameraModuleInput.canToggleCamera { canToggleCamera in + cameraModuleInput.canToggleCamera { [weak self] canToggleCamera in self?.view?.setCameraToggleButtonVisible(canToggleCamera) } - interactor.observeDeviceOrientation { deviceOrientation in + interactor.observeDeviceOrientation { [weak self] deviceOrientation in self?.view?.adjustForDeviceOrientation(deviceOrientation) } - interactor.observeLatestPhotoLibraryItem { image in + interactor.observeLatestPhotoLibraryItem { [weak self] image in self?.view?.setLatestLibraryPhoto(image) } - interactor.items { items, canAddMoreItems in + interactor.items { [weak self] items, canAddMoreItems in guard items.count > 0 else { return } self?.view?.setCameraButtonVisible(canAddMoreItems) @@ -111,11 +114,11 @@ final class MediaPickerPresenter: MediaPickerModule { } } - view?.onPhotoLibraryButtonTap = { + view?.onPhotoLibraryButtonTap = { [weak self] in self?.showPhotoLibrary() } - view?.onShutterButtonTap = { + view?.onShutterButtonTap = { [weak self] in // Если фоткать со вспышкой, это занимает много времени, и если несколько раз подряд быстро тапнуть на кнопку, // он будет потом еще долго фоткать :) Поэтому временно блокируем кнопку. @@ -137,7 +140,7 @@ final class MediaPickerPresenter: MediaPickerModule { } } - view?.onFlashToggle = { shouldEnableFlash in + view?.onFlashToggle = { [weak self] shouldEnableFlash in self?.cameraModuleInput.setFlashEnabled(shouldEnableFlash) { success in if !success { self?.view?.setFlashButtonOn(!shouldEnableFlash) @@ -145,16 +148,16 @@ final class MediaPickerPresenter: MediaPickerModule { } } - view?.onItemSelect = { item in + view?.onItemSelect = { [weak self] item in self?.interactor.selectItem(item) - self?.adjustViewForSelectedItem(item, animated: true, scrollToSelected: true) + self?.adjustViewForSelectedItem(item, animated: true) } - view?.onItemMove = { sourceIndex, destinationIndex in + view?.onItemMove = { [weak self] (sourceIndex, destinationIndex) in self?.interactor.moveItem(from: sourceIndex, to: destinationIndex) self?.interactor.selectedItem { item in if let item = item { - self?.adjustViewForSelectedItem(item, animated: true, scrollToSelected: false) + self?.adjustViewForSelectedItem(item, animated: true) } } self?.view?.moveItem(from: sourceIndex, to: destinationIndex) @@ -166,37 +169,37 @@ final class MediaPickerPresenter: MediaPickerModule { self?.view?.scrollToCameraThumbnail(animated: true) } - view?.onCameraToggleButtonTap = { + view?.onCameraToggleButtonTap = { [weak self] in self?.cameraModuleInput.toggleCamera { newOutputOrientation in self?.view?.setCameraOutputOrientation(newOutputOrientation) } } - view?.onSwipeToItem = { item in + view?.onSwipeToItem = { [weak self] item in self?.view?.selectItem(item) } - view?.onSwipeToCamera = { + view?.onSwipeToCamera = { [weak self] in self?.view?.selectCamera() } - view?.onSwipeToCameraProgressChange = { progress in + view?.onSwipeToCameraProgressChange = { [weak self] progress in self?.view?.setPhotoTitleAlpha(1 - progress) } - view?.onCloseButtonTap = { + view?.onCloseButtonTap = { [weak self] in self?.cameraModuleInput.setFlashEnabled(false, completion: nil) self?.onCancel?() } - view?.onContinueButtonTap = { + view?.onContinueButtonTap = { [weak self] in self?.cameraModuleInput.setFlashEnabled(false, completion: nil) self?.interactor.items { items, _ in self?.onFinish?(items) } } - view?.onCropButtonTap = { + view?.onCropButtonTap = { [weak self] in self?.interactor.selectedItem { item in if let item = item { self?.showCroppingModule(forItem: item) @@ -204,34 +207,24 @@ final class MediaPickerPresenter: MediaPickerModule { } } - view?.onRemoveButtonTap = { + view?.onRemoveButtonTap = { [weak self] in self?.removeSelectedItem() } - view?.onPreviewSizeDetermined = { previewSize in + view?.onPreviewSizeDetermined = { [weak self] previewSize in self?.cameraModuleInput.setPreviewImagesSizeForNewPhotos(previewSize) } - view?.onViewDidAppear = { animated in + view?.onViewDidAppear = { [weak self] animated in self?.cameraModuleInput.mainModuleDidAppear(animated: animated) } - - view?.onViewWillAppear = { _ in - self?.cameraModuleInput.setCameraOutputNeeded(true) - } - - view?.onViewDidDisappear = { _ in - self?.cameraModuleInput.setCameraOutputNeeded(false) - } } - private func adjustViewForSelectedItem(_ item: MediaPickerItem, animated: Bool, scrollToSelected: Bool) { + private func adjustViewForSelectedItem(_ item: MediaPickerItem, animated: Bool) { adjustPhotoTitleForItem(item) view?.setMode(.photoPreview(item)) - if scrollToSelected { - view?.scrollToItemThumbnail(item, animated: animated) - } + view?.scrollToItemThumbnail(item, animated: animated) } private func adjustPhotoTitleForItem(_ item: MediaPickerItem) { @@ -260,7 +253,7 @@ final class MediaPickerPresenter: MediaPickerModule { private func selectItem(_ item: MediaPickerItem) { view?.selectItem(item) - adjustViewForSelectedItem(item, animated: false, scrollToSelected: true) + adjustViewForSelectedItem(item, animated: false) } private func selectCamera() { @@ -320,8 +313,13 @@ final class MediaPickerPresenter: MediaPickerModule { interactor.numberOfItemsAvailableForAdding { [weak self] maxItemsCount in self?.interactor.photoLibraryItems { photoLibraryItems in + + let data = PhotoLibraryData( + selectedItems: [], + maxSelectedItemsCount: maxItemsCount + ) - self?.router.showPhotoLibrary(selectedItems: [], maxSelectedItemsCount: maxItemsCount) { module in + self?.router.showPhotoLibrary(data: data) { module in module.onFinish = { result in self?.router.focusOnCurrentModule() diff --git a/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerRouter.swift b/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerRouter.swift index e9be78af..41f027f3 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerRouter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerRouter.swift @@ -3,15 +3,14 @@ import ImageSource protocol MediaPickerRouter: class { func showPhotoLibrary( - selectedItems: [PhotoLibraryItem], - maxSelectedItemsCount: Int?, - configuration: (PhotoLibraryModule) -> () + data: PhotoLibraryData, + configure: (PhotoLibraryModule) -> () ) func showCroppingModule( forImage: ImageSource, canvasSize: CGSize, - configuration: (ImageCroppingModule) -> () + configure: (ImageCroppingModule) -> () ) func focusOnCurrentModule() diff --git a/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerUIKitRouter.swift b/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerUIKitRouter.swift index cac25543..61aaa787 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerUIKitRouter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerUIKitRouter.swift @@ -16,16 +16,14 @@ final class MediaPickerUIKitRouter: BaseUIKitRouter, MediaPickerRouter { // MARK: - PhotoPickerRouter func showPhotoLibrary( - selectedItems: [PhotoLibraryItem], - maxSelectedItemsCount: Int?, - configuration: (PhotoLibraryModule) -> ()) + data: PhotoLibraryData, + configure: (PhotoLibraryModule) -> ()) { let assembly = assemblyFactory.photoLibraryAssembly() let viewController = assembly.module( - selectedItems: selectedItems, - maxSelectedItemsCount: maxSelectedItemsCount, - configuration: configuration + data: data, + configure: configure ) let navigationController = UINavigationController(rootViewController: viewController) @@ -36,14 +34,14 @@ final class MediaPickerUIKitRouter: BaseUIKitRouter, MediaPickerRouter { func showCroppingModule( forImage image: ImageSource, canvasSize: CGSize, - configuration: (ImageCroppingModule) -> ()) + configure: (ImageCroppingModule) -> ()) { let assembly = assemblyFactory.imageCroppingAssembly() let viewController = assembly.module( image: image, canvasSize: canvasSize, - configuration: configuration + configure: configure ) cropViewControllers.append(WeakWrapper(value: viewController)) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift index 428d111c..beb4e5cb 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift @@ -2,7 +2,9 @@ import JNWSpringAnimation import ImageSource import UIKit -final class CameraControlsView: UIView { +final class CameraControlsView: UIView, ThemeConfigurable { + + typealias ThemeType = MediaPickerRootModuleUITheme var onShutterButtonTap: (() -> ())? var onPhotoLibraryButtonTap: (() -> ())? @@ -153,7 +155,9 @@ final class CameraControlsView: UIView { photoView.isUserInteractionEnabled = enabled } - func setTheme(_ theme: MediaPickerRootModuleUITheme) { + // MARK: - ThemeConfigurable + + func setTheme(_ theme: ThemeType) { self.theme = theme diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift index 93d6edf4..d1aeadfc 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift @@ -1,6 +1,8 @@ import UIKit -final class PhotoControlsView: UIView { +final class PhotoControlsView: UIView, ThemeConfigurable { + + typealias ThemeType = MediaPickerRootModuleUITheme // MARK: - Subviews @@ -48,6 +50,13 @@ final class PhotoControlsView: UIView { } } + // MARK: - ThemeConfigurable + + func setTheme(_ theme: ThemeType) { + removeButton.setImage(theme.removePhotoIcon, for: .normal) + cropButton.setImage(theme.cropPhotoIcon, for: .normal) + } + // MARK: - PhotoControlsView var onRemoveButtonTap: (() -> ())? @@ -59,11 +68,6 @@ final class PhotoControlsView: UIView { cropButton.transform = transform } - func setTheme(_ theme: MediaPickerRootModuleUITheme) { - removeButton.setImage(theme.removePhotoIcon, for: .normal) - cropButton.setImage(theme.cropPhotoIcon, for: .normal) - } - func setShowsCropButton(_ showsCropButton: Bool) { cropButton.isHidden = !showsCropButton setNeedsLayout() diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewView.swift index 8fa372c2..06181690 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewView.swift @@ -92,16 +92,17 @@ final class PhotoPreviewView: UIView, UICollectionViewDataSource, UICollectionVi func moveItem(from sourceIndex: Int, to destinationIndex: Int) { guard sourceIndex != destinationIndex else { return } - collectionView.performBatchUpdates(animated: false, { [weak self] in + collectionView.performBatchUpdates() { [weak self] in self?.dataSource.moveItem( from: sourceIndex, to: destinationIndex ) + self?.collectionView.moveItem( at: IndexPath(item: sourceIndex, section: 0), to: IndexPath(item: destinationIndex, section: 0) ) - }) + } } func setCameraVisible(_ visible: Bool) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerRootModuleUITheme.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerRootModuleUITheme.swift new file mode 100644 index 00000000..e471ac38 --- /dev/null +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerRootModuleUITheme.swift @@ -0,0 +1,22 @@ +public protocol MediaPickerRootModuleUITheme: AccessDeniedViewTheme { + + var shutterButtonColor: UIColor { get } + var shutterButtonDisabledColor: UIColor { get } + var mediaRibbonSelectionColor: UIColor { get } + var cameraContinueButtonTitleColor: UIColor { get } + var cameraContinueButtonTitleHighlightedColor: UIColor { get } + var cameraButtonsBackgroundNormalColor: UIColor { get } + var cameraButtonsBackgroundHighlightedColor: UIColor { get } + var cameraButtonsBackgroundDisabledColor: UIColor { get } + + var removePhotoIcon: UIImage? { get } + var cropPhotoIcon: UIImage? { get } + var returnToCameraIcon: UIImage? { get } + var closeCameraIcon: UIImage? { get } + var flashOnIcon: UIImage? { get } + var flashOffIcon: UIImage? { get } + var cameraToggleIcon: UIImage? { get } + var photoPeepholePlaceholder: UIImage? { get } + + var cameraContinueButtonTitleFont: UIFont { get } +} diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index 565181b7..5722093e 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -1,7 +1,9 @@ import ImageSource import UIKit -final class MediaPickerView: UIView { +final class MediaPickerView: UIView, ThemeConfigurable { + + typealias ThemeType = MediaPickerRootModuleUITheme // MARK: - Subviews @@ -171,6 +173,45 @@ final class MediaPickerView: UIView { flashView.frame = cameraFrame } + // MARK: - ThemeConfigurable + + func setTheme(_ theme: ThemeType) { + + cameraControlsView.setTheme(theme) + photoControlsView.setTheme(theme) + thumbnailRibbonView.setTheme(theme) + + continueButton.setTitleColor(theme.cameraContinueButtonTitleColor, for: .normal) + continueButton.titleLabel?.font = theme.cameraContinueButtonTitleFont + + closeButton.setImage(theme.closeCameraIcon, for: .normal) + + continueButton.setTitleColor( + theme.cameraContinueButtonTitleColor, + for: .normal + ) + continueButton.setTitleColor( + theme.cameraContinueButtonTitleHighlightedColor, + for: .highlighted + ) + + let onePointSize = CGSize(width: 1, height: 1) + for button in [continueButton, closeButton] { + button.setBackgroundImage( + UIImage.imageWithColor(theme.cameraButtonsBackgroundNormalColor, imageSize: onePointSize), + for: .normal + ) + button.setBackgroundImage( + UIImage.imageWithColor(theme.cameraButtonsBackgroundHighlightedColor, imageSize: onePointSize), + for: .highlighted + ) + button.setBackgroundImage( + UIImage.imageWithColor(theme.cameraButtonsBackgroundDisabledColor, imageSize: onePointSize), + for: .disabled + ) + } + } + // MARK: - MediaPickerView var onShutterButtonTap: (() -> ())? { @@ -287,6 +328,7 @@ final class MediaPickerView: UIView { } var onCloseButtonTap: (() -> ())? + var onContinueButtonTap: (() -> ())? var onCameraToggleButtonTap: (() -> ())? { @@ -306,6 +348,10 @@ final class MediaPickerView: UIView { cameraControlsView.setPhotoLibraryButtonEnabled(enabled) } + func setContinueButtonVisible(_ visible: Bool) { + continueButton.isHidden = !visible + } + func addItems(_ items: [MediaPickerItem], animated: Bool, completion: @escaping () -> ()) { photoPreviewView.addItems(items) thumbnailRibbonView.addItems(items, animated: animated, completion: completion) @@ -403,43 +449,6 @@ final class MediaPickerView: UIView { continueButton.isEnabled = enabled } - func setTheme(_ theme: MediaPickerRootModuleUITheme) { - - cameraControlsView.setTheme(theme) - photoControlsView.setTheme(theme) - thumbnailRibbonView.setTheme(theme) - - continueButton.setTitleColor(theme.cameraContinueButtonTitleColor, for: .normal) - continueButton.titleLabel?.font = theme.cameraContinueButtonTitleFont - - closeButton.setImage(theme.closeCameraIcon, for: .normal) - - continueButton.setTitleColor( - theme.cameraContinueButtonTitleColor, - for: .normal - ) - continueButton.setTitleColor( - theme.cameraContinueButtonTitleHighlightedColor, - for: .highlighted - ) - - let onePointSize = CGSize(width: 1, height: 1) - for button in [continueButton, closeButton] { - button.setBackgroundImage( - UIImage.imageWithColor(theme.cameraButtonsBackgroundNormalColor, imageSize: onePointSize), - for: .normal - ) - button.setBackgroundImage( - UIImage.imageWithColor(theme.cameraButtonsBackgroundHighlightedColor, imageSize: onePointSize), - for: .highlighted - ) - button.setBackgroundImage( - UIImage.imageWithColor(theme.cameraButtonsBackgroundDisabledColor, imageSize: onePointSize), - for: .disabled - ) - } - } - func setShowsCropButton(_ showsCropButton: Bool) { photoControlsView.setShowsCropButton(showsCropButton) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index 3839974a..9abeb92b 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -1,7 +1,9 @@ import ImageSource import UIKit -final class MediaPickerViewController: UIViewController, MediaPickerViewInput { +final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewInput, ThemeConfigurable { + + typealias ThemeType = MediaPickerRootModuleUITheme private var isBeingRotated: Bool = false private let mediaPickerView = MediaPickerView() @@ -28,8 +30,6 @@ final class MediaPickerViewController: UIViewController, MediaPickerViewInput { navigationController?.setNavigationBarHidden(true, animated: animated) UIApplication.shared.setStatusBarHidden(true, with: .fade) - - onViewWillAppear?(animated) } override func viewWillDisappear(_ animated: Bool) { @@ -74,11 +74,6 @@ final class MediaPickerViewController: UIViewController, MediaPickerViewInput { onViewDidAppear?(animated) } - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - onViewDidDisappear?(animated) - } - override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() @@ -197,9 +192,7 @@ final class MediaPickerViewController: UIViewController, MediaPickerViewInput { } var onViewDidLoad: (() -> ())? - var onViewWillAppear: ((_ animated: Bool) -> ())? var onViewDidAppear: ((_ animated: Bool) -> ())? - var onViewDidDisappear: ((_ animated: Bool) -> ())? var onPreviewSizeDetermined: ((_ previewSize: CGSize) -> ())? func setMode(_ mode: MediaPickerViewMode) { @@ -238,6 +231,10 @@ final class MediaPickerViewController: UIViewController, MediaPickerViewInput { mediaPickerView.setContinueButtonEnabled(enabled) } + func setContinueButtonVisible(_ visible: Bool) { + mediaPickerView.setContinueButtonVisible(visible) + } + func adjustForDeviceOrientation(_ orientation: DeviceOrientation) { UIView.animate(withDuration: 0.25) { self.mediaPickerView.adjustForDeviceOrientation(orientation) @@ -333,16 +330,18 @@ final class MediaPickerViewController: UIViewController, MediaPickerViewInput { mediaPickerView.setPhotoLibraryButtonEnabled(enabled) } + // MARK: - ThemeConfigurable + + func setTheme(_ theme: ThemeType) { + mediaPickerView.setTheme(theme) + } + // MARK: - MediaPickerViewController func setCameraView(_ view: UIView) { mediaPickerView.setCameraView(view) } - func setTheme(_ theme: MediaPickerRootModuleUITheme) { - mediaPickerView.setTheme(theme) - } - func setShowsCropButton(_ showsCropButton: Bool) { mediaPickerView.setShowsCropButton(showsCropButton) } @@ -357,12 +356,5 @@ final class MediaPickerViewController: UIViewController, MediaPickerViewInput { mediaPickerView.transform = CGAffineTransform(interfaceOrientation: interfaceOrientation) mediaPickerView.frame = view.bounds } - - // MARK: - Dispose bag - - private var disposables = [AnyObject]() - - func addDisposable(_ object: AnyObject) { - disposables.append(object) - } + } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift index 28c2fe21..142f1f2f 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift @@ -52,6 +52,8 @@ protocol MediaPickerViewInput: class { var onCameraToggleButtonTap: (() -> ())? { get set } func setCameraToggleButtonVisible(_: Bool) + func setContinueButtonVisible(_: Bool) + // MARK: - Actions in photo ribbon var onItemSelect: ((MediaPickerItem) -> ())? { get set } var onItemMove: ((_ sourceIndex: Int, _ destinationIndex: Int) -> ())? { get set } @@ -71,9 +73,7 @@ protocol MediaPickerViewInput: class { var onSwipeToCameraProgressChange: ((CGFloat) -> ())? { get set } var onViewDidLoad: (() -> ())? { get set } - var onViewWillAppear: ((_ animated: Bool) -> ())? { get set } var onViewDidAppear: ((_ animated: Bool) -> ())? { get set } - var onViewDidDisappear: ((_ animated: Bool) -> ())? { get set } var onPreviewSizeDetermined: ((_ previewSize: CGSize) -> ())? { get set } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsView.swift index 8e3d287a..046124db 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsView.swift @@ -1,7 +1,9 @@ import ImageSource import UIKit -final class ThumbnailsView: UIView, UICollectionViewDataSource, MediaRibbonLayoutDelegate { +final class ThumbnailsView: UIView, UICollectionViewDataSource, MediaRibbonLayoutDelegate, ThemeConfigurable { + + typealias ThemeType = MediaPickerRootModuleUITheme private let layout: ThumbnailsViewLayout private let collectionView: UICollectionView @@ -51,6 +53,12 @@ final class ThumbnailsView: UIView, UICollectionViewDataSource, MediaRibbonLayou collectionView.frame = bounds } + // MARK: - ThemeConfigurable + + func setTheme(_ theme: ThemeType) { + self.theme = theme + } + // MARK: - ThumbnailRibbonView var cameraOutputParameters: CameraOutputParameters? { @@ -97,10 +105,6 @@ final class ThumbnailsView: UIView, UICollectionViewDataSource, MediaRibbonLayou ) } - func setTheme(_ theme: MediaPickerRootModuleUITheme) { - self.theme = theme - } - func setControlsTransform(_ transform: CGAffineTransform) { layout.itemsTransform = transform diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift index 78cc68e6..caa743fb 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift @@ -124,7 +124,6 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { if let newIndexPath = collectionView.indexPathForItem(at: location), delegate.canMove(to: newIndexPath) { collectionView.moveItem(at: draggingIndexPath, to: newIndexPath) self.draggingIndexPath = newIndexPath - beginScrollIfNeeded() } } @@ -159,144 +158,6 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { self.invalidateLayout() }) } - - // MARK: Handle scrolling to the edges - - private var continuousScrollDirection: Direction = .none - - enum Direction { - case left - case right - case none - - func scrollValue(_ speedValue: CGFloat, percentage: CGFloat) -> CGFloat { - var value: CGFloat = 0.0 - switch self { - case .left: - value = -speedValue - case .right: - value = speedValue - case .none: - return 0 - } - - let proofedPercentage: CGFloat = max(min(1.0, percentage), 0) - return value * proofedPercentage - } - } - - - private let triggerInset : CGFloat = 30.0 - - private var scrollSpeedValue: CGFloat = 5.0 - private var displayLink: CADisplayLink? - - private var offsetFromLeft: CGFloat { - guard let contentOffset = collectionView?.contentOffset else { return 0 } - return contentOffset.x - } - - private var collectionViewLength: CGFloat { - guard let collectionViewSize = collectionView?.bounds.size else { return 0 } - return collectionViewSize.width - } - - private var contentLength: CGFloat { - guard let contentSize = collectionView?.contentSize else { return 0 } - return contentSize.width - } - - private var draggingViewTopEdge: CGFloat? { - return draggingView.flatMap { $0.frame.minX } - } - - private var draggingViewEndEdge: CGFloat? { - return draggingView.flatMap { $0.frame.maxX } - } - - private func setUpDisplayLink() { - guard self.displayLink == nil else { return } - - let displayLink = CADisplayLink(target: self, selector: #selector(onContinuousScroll)) - displayLink.frameInterval = 1 - displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) - self.displayLink = displayLink - } - - private func invalidateDisplayLink() { - continuousScrollDirection = .none - displayLink?.invalidate() - displayLink = nil - } - - private func beginScrollIfNeeded() { - guard - let draggingViewTopEdge = draggingViewTopEdge, - let draggingViewEndEdge = draggingViewEndEdge - else { return } - - if draggingViewTopEdge <= offsetFromLeft + triggerInset { - continuousScrollDirection = .left - setUpDisplayLink() - } else if draggingViewEndEdge >= offsetFromLeft + collectionViewLength - triggerInset { - continuousScrollDirection = .right - setUpDisplayLink() - } else { - invalidateDisplayLink() - } - } - - @objc private func onContinuousScroll() { - guard let draggingView = draggingView else { return } - - let percentage = calculateTriggerPercentage() - var scrollRate = continuousScrollDirection.scrollValue(scrollSpeedValue, percentage: percentage) - - let offset = offsetFromLeft - let length = collectionViewLength - - if contentLength <= length { - return - } - - if offset + scrollRate <= 0 { - scrollRate = -offset - } else if offset + scrollRate >= contentLength - length { - scrollRate = contentLength - length - offset - } - - draggingView.x += scrollRate - - collectionView?.performBatchUpdates({ - self.collectionView?.contentOffset.x += scrollRate - }, completion: nil) - } - - private func calculateTriggerPercentage() -> CGFloat { - guard draggingView != nil else { return 0 } - - let offset = offsetFromLeft - let offsetEnd = offsetFromLeft + collectionViewLength - - var percentage: CGFloat = 0 - - guard triggerInset != 0 else { - return 0 - } - - if self.continuousScrollDirection == .left { - if let fakeCellEdge = draggingViewTopEdge { - percentage = 1.0 - ((fakeCellEdge - offset) / triggerInset) - } - } else if continuousScrollDirection == .right { - if let draggingViewEdge = draggingViewEndEdge { - percentage = 1.0 - ((offsetEnd - draggingViewEdge) / triggerInset) - } - } - - percentage = min(1, max(0, percentage)) - return percentage - } } protocol MediaRibbonLayoutDelegate: UICollectionViewDelegateFlowLayout { diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/Assembly/PhotoLibraryAssembly.swift b/Paparazzo/Core/VIPER/PhotoLibrary/Assembly/PhotoLibraryAssembly.swift index 556d6489..af63d7e3 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/Assembly/PhotoLibraryAssembly.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/Assembly/PhotoLibraryAssembly.swift @@ -2,9 +2,8 @@ import UIKit public protocol PhotoLibraryAssembly: class { func module( - selectedItems: [PhotoLibraryItem], - maxSelectedItemsCount: Int?, - configuration: (PhotoLibraryModule) -> () + data: PhotoLibraryData, + configure: (PhotoLibraryModule) -> () ) -> UIViewController } diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/Assembly/PhotoLibraryAssemblyImpl.swift b/Paparazzo/Core/VIPER/PhotoLibrary/Assembly/PhotoLibraryAssemblyImpl.swift index 588a2977..03546960 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/Assembly/PhotoLibraryAssemblyImpl.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/Assembly/PhotoLibraryAssemblyImpl.swift @@ -1,24 +1,17 @@ import UIKit -public final class PhotoLibraryAssemblyImpl: PhotoLibraryAssembly { - - private let theme: PhotoLibraryUITheme - - init(theme: PhotoLibraryUITheme) { - self.theme = theme - } +public final class PhotoLibraryAssemblyImpl: BasePaparazzoAssembly, PhotoLibraryAssembly { public func module( - selectedItems: [PhotoLibraryItem], - maxSelectedItemsCount: Int?, - configuration: (PhotoLibraryModule) -> ()) + data: PhotoLibraryData, + configure: (PhotoLibraryModule) -> ()) -> UIViewController { let photoLibraryItemsService = PhotoLibraryItemsServiceImpl() let interactor = PhotoLibraryInteractorImpl( - selectedItems: selectedItems, - maxSelectedItemsCount: maxSelectedItemsCount, + selectedItems: data.selectedItems, + maxSelectedItemsCount: data.maxSelectedItemsCount, photoLibraryItemsService: photoLibraryItemsService ) @@ -36,7 +29,7 @@ public final class PhotoLibraryAssemblyImpl: PhotoLibraryAssembly { presenter.view = viewController - configuration(presenter) + configure(presenter) return viewController } diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/Module/PhotoLibraryData.swift b/Paparazzo/Core/VIPER/PhotoLibrary/Module/PhotoLibraryData.swift new file mode 100644 index 00000000..505759f2 --- /dev/null +++ b/Paparazzo/Core/VIPER/PhotoLibrary/Module/PhotoLibraryData.swift @@ -0,0 +1,14 @@ +import UIKit + +public struct PhotoLibraryData { + public let selectedItems: [PhotoLibraryItem] + public let maxSelectedItemsCount: Int? + + public init( + selectedItems: [PhotoLibraryItem], + maxSelectedItemsCount: Int?) + { + self.selectedItems = selectedItems + self.maxSelectedItemsCount = maxSelectedItemsCount + } +} diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/AccessDeniedUITheme.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/AccessDeniedUITheme.swift new file mode 100644 index 00000000..f5e888b5 --- /dev/null +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/AccessDeniedUITheme.swift @@ -0,0 +1,5 @@ +public protocol AccessDeniedViewTheme { + var accessDeniedTitleFont: UIFont { get } + var accessDeniedMessageFont: UIFont { get } + var accessDeniedButtonFont: UIFont { get } +} diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/AccessDeniedView.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/AccessDeniedView.swift index 67e0813f..17f66261 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/View/AccessDeniedView.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/AccessDeniedView.swift @@ -1,6 +1,8 @@ import UIKit -final class AccessDeniedView: UIView { +final class AccessDeniedView: UIView, ThemeConfigurable { + + typealias ThemeType = AccessDeniedViewTheme let titleLabel = UILabel() let messageLabel = UILabel() @@ -68,9 +70,9 @@ final class AccessDeniedView: UIView { button.frame = frames.buttonFrame } - // MARK: - AccessDeniedView + // MARK: - ThemeConfigurable - func setTheme(_ theme: AccessDeniedViewTheme) { + func setTheme(_ theme: ThemeType) { titleLabel.font = theme.accessDeniedTitleFont messageLabel.font = theme.accessDeniedMessageFont button.titleLabel?.font = theme.accessDeniedButtonFont diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryUITheme.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryUITheme.swift new file mode 100644 index 00000000..239cbc6b --- /dev/null +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryUITheme.swift @@ -0,0 +1,9 @@ +public protocol PhotoLibraryUITheme: AccessDeniedViewTheme { + + var photoLibraryDoneButtonFont: UIFont { get } + + var photoLibraryItemSelectionColor: UIColor { get } + var photoCellBackgroundColor: UIColor { get } + + var iCloudIcon: UIImage? { get } +} diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift index 62d078f4..09731742 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift @@ -1,6 +1,8 @@ import UIKit -final class PhotoLibraryView: UIView, UICollectionViewDelegateFlowLayout { +final class PhotoLibraryView: UIView, UICollectionViewDelegateFlowLayout, ThemeConfigurable { + + typealias ThemeType = PhotoLibraryUITheme // MARK: - State @@ -54,6 +56,13 @@ final class PhotoLibraryView: UIView, UICollectionViewDelegateFlowLayout { accessDeniedView.frame = bounds } + // MARK: - ThemeConfigurable + + func setTheme(_ theme: ThemeType) { + self.theme = theme + accessDeniedView.setTheme(theme) + } + // MARK: - PhotoLibraryView var onAccessDeniedButtonTap: (() -> ())? { @@ -131,11 +140,6 @@ final class PhotoLibraryView: UIView, UICollectionViewDelegateFlowLayout { collectionView.scrollToBottom() } - func setTheme(_ theme: PhotoLibraryUITheme) { - self.theme = theme - accessDeniedView.setTheme(theme) - } - func setAccessDeniedViewVisible(_ visible: Bool) { accessDeniedView.isHidden = !visible } diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewController.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewController.swift index 76c5f72a..56946943 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewController.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewController.swift @@ -1,6 +1,8 @@ import UIKit -final class PhotoLibraryViewController: UIViewController, PhotoLibraryViewInput { +final class PhotoLibraryViewController: PaparazzoViewController, PhotoLibraryViewInput, ThemeConfigurable { + + typealias ThemeType = PhotoLibraryUITheme private let photoLibraryView = PhotoLibraryView() @@ -28,6 +30,13 @@ final class PhotoLibraryViewController: UIViewController, PhotoLibraryViewInput return true } + // MARK: - ThemeConfigurable + + func setTheme(_ theme: ThemeType) { + self.theme = theme + photoLibraryView.setTheme(theme) + } + // MARK: - PhotoLibraryViewInput var onItemSelect: ((PhotoLibraryItem) -> ())? @@ -90,11 +99,6 @@ final class PhotoLibraryViewController: UIViewController, PhotoLibraryViewInput photoLibraryView.scrollToBottom() } - func setTheme(_ theme: PhotoLibraryUITheme) { - self.theme = theme - photoLibraryView.setTheme(theme) - } - func setAccessDeniedViewVisible(_ visible: Bool) { photoLibraryView.setAccessDeniedViewVisible(visible) } @@ -111,14 +115,6 @@ final class PhotoLibraryViewController: UIViewController, PhotoLibraryViewInput photoLibraryView.setAccessDeniedButtonTitle(title) } - // MARK: - Dispose bag - - private var disposables = [AnyObject]() - - func addDisposable(_ object: AnyObject) { - disposables.append(object) - } - // MARK: - Private private var pickBarButtonItem: UIBarButtonItem? diff --git a/Paparazzo/Marshroute/MarshrouteAssemblyFactory.swift b/Paparazzo/Marshroute/MarshrouteAssemblyFactory.swift index 60a5fac5..4b2b6d3f 100644 --- a/Paparazzo/Marshroute/MarshrouteAssemblyFactory.swift +++ b/Paparazzo/Marshroute/MarshrouteAssemblyFactory.swift @@ -1,3 +1,5 @@ +typealias MarshrouteAssemblyFactoryType = CameraAssemblyFactory & ImageCroppingAssemblyFactory & PhotoLibraryMarshrouteAssemblyFactory + public final class MarshrouteAssemblyFactory: CameraAssemblyFactory, MediaPickerMarshrouteAssemblyFactory, @@ -5,24 +7,30 @@ public final class MarshrouteAssemblyFactory: PhotoLibraryMarshrouteAssemblyFactory { private let theme: PaparazzoUITheme + private let serviceFactory = ServiceFactoryImpl() public init(theme: PaparazzoUITheme = PaparazzoUITheme()) { self.theme = theme } func cameraAssembly() -> CameraAssembly { - return CameraAssemblyImpl(theme: theme) + return CameraAssemblyImpl(theme: theme, serviceFactory: serviceFactory) } public func mediaPickerAssembly() -> MediaPickerMarshrouteAssembly { - return MediaPickerMarshrouteAssemblyImpl(assemblyFactory: self, theme: theme) + return MediaPickerMarshrouteAssemblyImpl(assemblyFactory: self, theme: theme, serviceFactory: serviceFactory) } func imageCroppingAssembly() -> ImageCroppingAssembly { - return ImageCroppingAssemblyImpl(theme: theme) + return ImageCroppingAssemblyImpl(theme: theme, serviceFactory: serviceFactory) } public func photoLibraryAssembly() -> PhotoLibraryMarshrouteAssembly { - return PhotoLibraryMarshrouteAssemblyImpl(theme: theme) + return PhotoLibraryMarshrouteAssemblyImpl(theme: theme, serviceFactory: serviceFactory) + } + + public func maskCropperAssembly() -> MaskCropperMarshrouteAssembly { + return MaskCropperMarshrouteAssemblyImpl(theme: theme, serviceFactory: serviceFactory) } + } diff --git a/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssembly.swift b/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssembly.swift new file mode 100644 index 00000000..95cf0fee --- /dev/null +++ b/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssembly.swift @@ -0,0 +1,15 @@ +import Marshroute +import UIKit + +public protocol MaskCropperMarshrouteAssembly: class { + func module( + data: MaskCropperData, + croppingOverlayProvider: CroppingOverlayProvider, + routerSeed: RouterSeed, + configure: (MaskCropperModule) -> ()) + -> UIViewController +} + +public protocol MaskCropperMarshrouteAssemblyFactory: class { + func circleImageCroppingMarshrouteAssembly() -> MaskCropperAssembly +} diff --git a/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssemblyImpl.swift b/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssemblyImpl.swift new file mode 100644 index 00000000..e784d948 --- /dev/null +++ b/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssemblyImpl.swift @@ -0,0 +1,44 @@ +import Marshroute +import UIKit + +public final class MaskCropperMarshrouteAssemblyImpl: BasePaparazzoAssembly, MaskCropperMarshrouteAssembly { + + public func module( + data: MaskCropperData, + croppingOverlayProvider: CroppingOverlayProvider, + routerSeed: RouterSeed, + configure: (MaskCropperModule) -> () + ) -> UIViewController { + + let imageCroppingService = serviceFactory.imageCroppingService( + image: data.photo.image, + canvasSize: data.cropCanvasSize + ) + + let interactor = MaskCropperInteractorImpl( + imageCroppingService: imageCroppingService + ) + + let router = MaskCropperMarshrouteRouter( + routerSeed: routerSeed + ) + + let presenter = MaskCropperPresenter( + interactor: interactor, + router: router + ) + + let viewController = MaskCropperViewController( + croppingOverlayProvider: croppingOverlayProvider + ) + viewController.addDisposable(presenter) + viewController.setTheme(theme) + + presenter.view = viewController + + configure(presenter) + + return viewController + } + +} diff --git a/Paparazzo/Marshroute/MaskCropper/Router/MaskCropperMarshrouteRouter.swift b/Paparazzo/Marshroute/MaskCropper/Router/MaskCropperMarshrouteRouter.swift new file mode 100644 index 00000000..3ac5e82f --- /dev/null +++ b/Paparazzo/Marshroute/MaskCropper/Router/MaskCropperMarshrouteRouter.swift @@ -0,0 +1,4 @@ +import ImageSource +import Marshroute + +final class MaskCropperMarshrouteRouter: BaseRouter, MaskCropperRouter {} diff --git a/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssembly.swift b/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssembly.swift index 808abbc8..d3788961 100644 --- a/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssembly.swift +++ b/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssembly.swift @@ -3,13 +3,9 @@ import UIKit public protocol MediaPickerMarshrouteAssembly: class { func module( - items: [MediaPickerItem], - selectedItem: MediaPickerItem?, - maxItemsCount: Int?, - cropEnabled: Bool, - cropCanvasSize: CGSize, + data: MediaPickerData, routerSeed: RouterSeed, - configuration: (MediaPickerModule) -> ()) + configure: (MediaPickerModule) -> ()) -> UIViewController } diff --git a/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift b/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift index daa3aa7d..02aef417 100644 --- a/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift +++ b/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift @@ -1,37 +1,32 @@ import Marshroute import UIKit -public final class MediaPickerMarshrouteAssemblyImpl: MediaPickerMarshrouteAssembly { +public final class MediaPickerMarshrouteAssemblyImpl: BasePaparazzoAssembly, MediaPickerMarshrouteAssembly { typealias AssemblyFactory = CameraAssemblyFactory & ImageCroppingAssemblyFactory & PhotoLibraryMarshrouteAssemblyFactory private let assemblyFactory: AssemblyFactory - private let theme: PaparazzoUITheme - init(assemblyFactory: AssemblyFactory, theme: PaparazzoUITheme) { + init(assemblyFactory: AssemblyFactory, theme: PaparazzoUITheme, serviceFactory: ServiceFactory) { self.assemblyFactory = assemblyFactory - self.theme = theme + super.init(theme: theme, serviceFactory: serviceFactory) } // MARK: - MediaPickerAssembly public func module( - items: [MediaPickerItem], - selectedItem: MediaPickerItem?, - maxItemsCount: Int?, - cropEnabled: Bool, - cropCanvasSize: CGSize, + data: MediaPickerData, routerSeed: RouterSeed, - configuration: (MediaPickerModule) -> ()) + configure: (MediaPickerModule) -> ()) -> UIViewController { let interactor = MediaPickerInteractorImpl( - items: items, - selectedItem: selectedItem, - maxItemsCount: maxItemsCount, - cropCanvasSize: cropCanvasSize, - deviceOrientationService: DeviceOrientationServiceImpl(), - latestLibraryPhotoProvider: PhotoLibraryLatestPhotoProviderImpl() + items: data.items, + selectedItem: data.selectedItem, + maxItemsCount: data.maxItemsCount, + cropCanvasSize: data.cropCanvasSize, + deviceOrientationService: serviceFactory.deviceOrientationService(), + latestLibraryPhotoProvider: serviceFactory.photoLibraryLatestPhotoProvider() ) let router = MediaPickerMarshrouteRouter( @@ -40,7 +35,7 @@ public final class MediaPickerMarshrouteAssemblyImpl: MediaPickerMarshrouteAssem ) let cameraAssembly = assemblyFactory.cameraAssembly() - let (cameraView, cameraModuleInput) = cameraAssembly.module() + let (cameraView, cameraModuleInput) = cameraAssembly.module(initialActiveCameraType: data.initialActiveCameraType) let presenter = MediaPickerPresenter( interactor: interactor, @@ -52,11 +47,11 @@ public final class MediaPickerMarshrouteAssemblyImpl: MediaPickerMarshrouteAssem viewController.addDisposable(presenter) viewController.setCameraView(cameraView) viewController.setTheme(theme) - viewController.setShowsCropButton(cropEnabled) + viewController.setShowsCropButton(data.cropEnabled) presenter.view = viewController - configuration(presenter) + configure(presenter) return viewController } diff --git a/Paparazzo/Marshroute/MediaPicker/Router/MediaPickerMarshrouteRouter.swift b/Paparazzo/Marshroute/MediaPicker/Router/MediaPickerMarshrouteRouter.swift index e82b6d88..2a5cc429 100644 --- a/Paparazzo/Marshroute/MediaPicker/Router/MediaPickerMarshrouteRouter.swift +++ b/Paparazzo/Marshroute/MediaPicker/Router/MediaPickerMarshrouteRouter.swift @@ -15,19 +15,18 @@ final class MediaPickerMarshrouteRouter: BaseRouter, MediaPickerRouter { // MARK: - PhotoPickerRouter func showPhotoLibrary( - selectedItems: [PhotoLibraryItem], - maxSelectedItemsCount: Int?, - configuration: (PhotoLibraryModule) -> ()) + data: PhotoLibraryData, + configure: (PhotoLibraryModule) -> ()) { presentModalNavigationControllerWithRootViewControllerDerivedFrom { routerSeed in let assembly = assemblyFactory.photoLibraryAssembly() return assembly.module( - selectedItems: selectedItems, - maxSelectedItemsCount: maxSelectedItemsCount, + selectedItems: data.selectedItems, + maxSelectedItemsCount: data.maxSelectedItemsCount, routerSeed: routerSeed, - configuration: configuration + configure: configure ) } } @@ -35,7 +34,7 @@ final class MediaPickerMarshrouteRouter: BaseRouter, MediaPickerRouter { func showCroppingModule( forImage image: ImageSource, canvasSize: CGSize, - configuration: (ImageCroppingModule) -> ()) + configure: (ImageCroppingModule) -> ()) { pushViewControllerDerivedFrom({ _ in @@ -44,7 +43,7 @@ final class MediaPickerMarshrouteRouter: BaseRouter, MediaPickerRouter { return assembly.module( image: image, canvasSize: canvasSize, - configuration: configuration + configure: configure ) }, animator: NonAnimatedPushAnimator()) diff --git a/Paparazzo/Marshroute/PhotoLibrary/Assembly/PhotoLibraryMarshrouteAssembly.swift b/Paparazzo/Marshroute/PhotoLibrary/Assembly/PhotoLibraryMarshrouteAssembly.swift index ef6c4fa9..c6247cc7 100644 --- a/Paparazzo/Marshroute/PhotoLibrary/Assembly/PhotoLibraryMarshrouteAssembly.swift +++ b/Paparazzo/Marshroute/PhotoLibrary/Assembly/PhotoLibraryMarshrouteAssembly.swift @@ -6,7 +6,7 @@ public protocol PhotoLibraryMarshrouteAssembly: class { selectedItems: [PhotoLibraryItem], maxSelectedItemsCount: Int?, routerSeed: RouterSeed, - configuration: (PhotoLibraryModule) -> ()) + configure: (PhotoLibraryModule) -> ()) -> UIViewController } diff --git a/Paparazzo/Marshroute/PhotoLibrary/Assembly/PhotoLibraryMarshrouteAssemblyImpl.swift b/Paparazzo/Marshroute/PhotoLibrary/Assembly/PhotoLibraryMarshrouteAssemblyImpl.swift index 407f8fe2..29b50700 100644 --- a/Paparazzo/Marshroute/PhotoLibrary/Assembly/PhotoLibraryMarshrouteAssemblyImpl.swift +++ b/Paparazzo/Marshroute/PhotoLibrary/Assembly/PhotoLibraryMarshrouteAssemblyImpl.swift @@ -1,19 +1,13 @@ import UIKit import Marshroute -public final class PhotoLibraryMarshrouteAssemblyImpl: PhotoLibraryMarshrouteAssembly { - - private let theme: PhotoLibraryUITheme - - init(theme: PhotoLibraryUITheme) { - self.theme = theme - } +public final class PhotoLibraryMarshrouteAssemblyImpl: BasePaparazzoAssembly, PhotoLibraryMarshrouteAssembly { public func module( selectedItems: [PhotoLibraryItem], maxSelectedItemsCount: Int?, routerSeed: RouterSeed, - configuration: (PhotoLibraryModule) -> () + configure: (PhotoLibraryModule) -> () ) -> UIViewController { let photoLibraryItemsService = PhotoLibraryItemsServiceImpl() @@ -37,7 +31,7 @@ public final class PhotoLibraryMarshrouteAssemblyImpl: PhotoLibraryMarshrouteAss presenter.view = viewController - configuration(presenter) + configure(presenter) return viewController } From 03ca7be6919c0dc168ca1de4ff98c187904927b7 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Tue, 2 May 2017 17:21:10 +0300 Subject: [PATCH 002/111] Use ImageSource instead of MediaPickerItem in MaskCropper --- .../Example/Presenter/ExamplePresenter.swift | 2 +- .../MaskCropper/Assembly/MaskCropperAssemblyImpl.swift | 2 +- .../Core/VIPER/MaskCropper/Module/MaskCropperData.swift | 8 +++++--- .../Assembly/MaskCropperMarshrouteAssemblyImpl.swift | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index 9fb814ad..df09e254 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -97,7 +97,7 @@ final class ExamplePresenter { private func showMaskCropperIn(rootModule: MediaPickerModule?, photo: MediaPickerItem) { let data = MaskCropperData( - photo: photo, + imageSource: photo.image, cropCanvasSize: cropCanvasSize ) router.showMaskCropper( diff --git a/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssemblyImpl.swift b/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssemblyImpl.swift index f056a12a..5f406bdc 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssemblyImpl.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssemblyImpl.swift @@ -10,7 +10,7 @@ public final class MaskCropperAssemblyImpl: BasePaparazzoAssembly, MaskCropperAs ) -> UIViewController { let imageCroppingService = serviceFactory.imageCroppingService( - image: data.photo.image, + image: data.imageSource, canvasSize: data.cropCanvasSize ) diff --git a/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperData.swift b/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperData.swift index c3460351..ce095a3f 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperData.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperData.swift @@ -1,13 +1,15 @@ +import ImageSource + public struct MaskCropperData { - public let photo: MediaPickerItem + public let imageSource: ImageSource public let cropCanvasSize: CGSize public init( - photo: MediaPickerItem, + imageSource: ImageSource, cropCanvasSize: CGSize) { - self.photo = photo + self.imageSource = imageSource self.cropCanvasSize = cropCanvasSize } diff --git a/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssemblyImpl.swift b/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssemblyImpl.swift index e784d948..b32792c1 100644 --- a/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssemblyImpl.swift +++ b/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssemblyImpl.swift @@ -11,7 +11,7 @@ public final class MaskCropperMarshrouteAssemblyImpl: BasePaparazzoAssembly, Mas ) -> UIViewController { let imageCroppingService = serviceFactory.imageCroppingService( - image: data.photo.image, + image: data.imageSource, canvasSize: data.cropCanvasSize ) From 179c0d3d9f1e8523cb1f0b92328610bb97dd8136 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Wed, 3 May 2017 12:22:25 +0300 Subject: [PATCH 003/111] Update cropping parameters in MaskCropper --- .../VIPER/MaskCropper/Interactor/MaskCropperInteractor.swift | 1 + .../MaskCropper/Interactor/MaskCropperInteractorImpl.swift | 4 ++++ .../VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift | 4 ++++ Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift | 5 +++++ .../VIPER/MaskCropper/View/MaskCropperViewController.swift | 5 ++++- 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractor.swift b/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractor.swift index 9929d9f7..72c27fd8 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractor.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractor.swift @@ -4,4 +4,5 @@ protocol MaskCropperInteractor: class { func canvasSize(completion: @escaping (CGSize) -> ()) func imageWithParameters(completion: @escaping (ImageCroppingData) -> ()) func croppedImage(previewImage: CGImage, completion: @escaping (CroppedImageSource) -> ()) + func setCroppingParameters(_ parameters: ImageCroppingParameters) } diff --git a/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractorImpl.swift b/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractorImpl.swift index c2acddfe..0aa2d78c 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/Interactor/MaskCropperInteractorImpl.swift @@ -21,4 +21,8 @@ final class MaskCropperInteractorImpl: MaskCropperInteractor { func croppedImage(previewImage: CGImage, completion: @escaping (CroppedImageSource) -> ()) { imageCroppingService.croppedImage(previewImage: previewImage, completion: completion) } + + func setCroppingParameters(_ parameters: ImageCroppingParameters) { + imageCroppingService.setCroppingParameters(parameters) + } } diff --git a/Paparazzo/Core/VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift b/Paparazzo/Core/VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift index 1c649489..8c99b357 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift @@ -28,6 +28,10 @@ final class MaskCropperPresenter: MaskCropperModule { self?.onClose?() } + view?.onCroppingParametersChange = { [weak self] parameters in + self?.interactor.setCroppingParameters(parameters) + } + view?.onConfirmTap = { [weak self] previewImage in if let previewImage = previewImage { self?.interactor.croppedImage(previewImage: previewImage) { image in diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift index 0a496b9c..a577e1ee 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift @@ -122,6 +122,11 @@ final class MaskCropperView: UIView, ThemeConfigurable { set { controlsView.onDiscardTap = newValue } } + var onCroppingParametersChange: ((ImageCroppingParameters) -> ())? { + get { return previewView.onCroppingParametersChange } + set { previewView.onCroppingParametersChange = newValue } + } + func setCroppingParameters(_ parameters: ImageCroppingParameters) { previewView.setCroppingParameters(parameters) } diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift index d7844f5a..a950e0a5 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift @@ -64,7 +64,10 @@ final class MaskCropperViewController: set { circleImageCroppingView.onDiscardTap = newValue } } - var onCroppingParametersChange: ((ImageCroppingParameters) -> ())? + var onCroppingParametersChange: ((ImageCroppingParameters) -> ())? { + get { return circleImageCroppingView.onCroppingParametersChange } + set { circleImageCroppingView.onCroppingParametersChange = newValue } + } func setConfirmButtonTitle(_ title: String) { circleImageCroppingView.setConfirmButtonTitle(title) From 20b82976d3a5435a0b63a439240cd591a7343d25 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Wed, 3 May 2017 15:04:25 +0300 Subject: [PATCH 004/111] Fix aspect ratio for MaskCropper --- Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift index 0a496b9c..85af0071 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift @@ -13,7 +13,7 @@ final class MaskCropperView: UIView, ThemeConfigurable { // MARK: - Constants - private let aspectRatio = CGFloat(AspectRatio.portrait_3x4.widthToHeightRatio()) + private let aspectRatio = CGFloat(AspectRatio.defaultRatio.widthToHeightRatio()) private let closeButtonSize = CGSize(width: 38, height: 38) private let continueButtonHeight = CGFloat(38) private let continueButtonContentInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) From e7561c2721673cc9f0f6dd710232f1100e8f66bf Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Wed, 3 May 2017 16:01:23 +0300 Subject: [PATCH 005/111] revert aspect ratio --- Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift index 6e508735..a577e1ee 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift @@ -13,7 +13,7 @@ final class MaskCropperView: UIView, ThemeConfigurable { // MARK: - Constants - private let aspectRatio = CGFloat(AspectRatio.defaultRatio.widthToHeightRatio()) + private let aspectRatio = CGFloat(AspectRatio.portrait_3x4.widthToHeightRatio()) private let closeButtonSize = CGSize(width: 38, height: 38) private let continueButtonHeight = CGFloat(38) private let continueButtonContentInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) From 75e24604bd648c8d8fe4683c1209e081c1d37a2c Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Mon, 29 May 2017 14:24:15 +0300 Subject: [PATCH 006/111] Add previewEnabled --- .../project.pbxproj | 17 +++++++------ .../Example/Presenter/ExamplePresenter.swift | 9 ++++--- Example/Podfile.lock | 4 +-- .../Assembly/MaskCropperAssemblyImpl.swift | 2 +- .../Router/MaskCropperUIKitRouter.swift | 2 +- .../Assembly/MediaPickerAssemblyImpl.swift | 2 ++ .../Interactor/MediaPickerInteractor.swift | 2 ++ .../MediaPickerInteractorImpl.swift | 7 ++++++ .../MediaPicker/Module/MediaPickerData.swift | 3 +++ .../Presenter/MediaPickerPresenter.swift | 11 ++++++-- .../Router/MediaPickerRouter.swift | 6 +++++ .../Router/MediaPickerUIKitRouter.swift | 9 +++++++ .../MediaPicker/View/MediaPickerView.swift | 25 ++++++++++++++++--- .../View/MediaPickerViewController.swift | 4 +++ .../MediaPickerMarshrouteAssemblyImpl.swift | 2 ++ 15 files changed, 84 insertions(+), 21 deletions(-) diff --git a/Example/PaparazzoExample.xcodeproj/project.pbxproj b/Example/PaparazzoExample.xcodeproj/project.pbxproj index e9a40433..f5bd3290 100644 --- a/Example/PaparazzoExample.xcodeproj/project.pbxproj +++ b/Example/PaparazzoExample.xcodeproj/project.pbxproj @@ -92,8 +92,8 @@ 8EC973CB836BAD3EDC80CABA /* Pods-PaparazzoExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PaparazzoExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PaparazzoExample/Pods-PaparazzoExample.debug.xcconfig"; sourceTree = ""; }; AD2DAC113521ADEBFF8A22F1 /* Pods_PaparazzoExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PaparazzoExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B9A4E214165A8FB30A7CE203 /* Pods_PaparazzoExample_NoMarshroute.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PaparazzoExample_NoMarshroute.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EFA7610D1E829434000EB296 /* ItemProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemProvider.swift; sourceTree = ""; }; E747D4112A5EEA41675E0267 /* Pods-PaparazzoExample_Storyboard.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PaparazzoExample_Storyboard.release.xcconfig"; path = "Pods/Target Support Files/Pods-PaparazzoExample_Storyboard/Pods-PaparazzoExample_Storyboard.release.xcconfig"; sourceTree = ""; }; + EFA7610D1E829434000EB296 /* ItemProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemProvider.swift; sourceTree = ""; }; F2F50FC31E6C9F60006F9171 /* PaparazzoExample_Storyboard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PaparazzoExample_Storyboard.app; sourceTree = BUILT_PRODUCTS_DIR; }; F2F50FC51E6C9F60006F9171 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; F2F50FC71E6C9F60006F9171 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -375,7 +375,8 @@ TargetAttributes = { 251E57BF1E65651F0009A288 = { CreatedOnToolsVersion = 8.2.1; - ProvisioningStyle = Manual; + DevelopmentTeam = 5PHGGKL9UQ; + ProvisioningStyle = Automatic; }; 25A489B01E656A2B00CC431B = { CreatedOnToolsVersion = 8.2.1; @@ -479,7 +480,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 6620037BC3D2966EBE230528 /* [CP] Copy Pods Resources */ = { @@ -524,7 +525,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; BE84EDEE72F665CE084E60AC /* [CP] Embed Pods Frameworks */ = { @@ -554,7 +555,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; E607E68B79D7FA6BB43CDC07 /* [CP] Embed Pods Frameworks */ = { @@ -757,7 +758,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; - DEVELOPMENT_TEAM = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEVELOPMENT_TEAM = 5PHGGKL9UQ; INFOPLIST_FILE = PaparazzoExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -773,7 +775,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; - DEVELOPMENT_TEAM = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEVELOPMENT_TEAM = 5PHGGKL9UQ; INFOPLIST_FILE = PaparazzoExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index df09e254..ddca77c7 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -43,7 +43,7 @@ final class ExamplePresenter { view?.onShowPhotoLibraryButtonTap = { [weak self] in self?.interactor.photoLibraryItems { items in - self?.router.showPhotoLibrary(selectedItems: items, maxSelectedItemsCount: 5) { module in + self?.router.showPhotoLibrary(selectedItems: items, maxSelectedItemsCount: 1) { module in weak var weakModule = module module.onFinish = { result in weakModule?.dismissModule() @@ -68,9 +68,10 @@ final class ExamplePresenter { let data = MediaPickerData( items: items, selectedItem: nil, - maxItemsCount: 2, + maxItemsCount: 1, cropEnabled: true, cropCanvasSize: cropCanvasSize, + previewEnabled: false, initialActiveCameraType: .front ) @@ -123,9 +124,9 @@ final class ExamplePresenter { items.append(contentsOf: remoteItems) let data = MediaPickerData( - items: items, + items: [], selectedItem: items.last, - maxItemsCount: 20, + maxItemsCount: 1, cropEnabled: true, cropCanvasSize: cropCanvasSize ) diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 0253aeb1..69496efd 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -94,8 +94,8 @@ SPEC CHECKSUMS: JNWSpringAnimation: cd4c2f4464324f63f176c3624ffccf205211a100 Marshroute: abed7a53cf04db1a60aaddfe797dea7a7d010464 Paparazzo: d4a7ada3cc99d5742f579d63e20f6da838a86398 - SDWebImage: 098e97e6176540799c27e804c96653ee0833d13c + SDWebImage: '098e97e6176540799c27e804c96653ee0833d13c' PODFILE CHECKSUM: de862853608cf1d1ac04ebaaa29cb02cccfc5328 -COCOAPODS: 1.2.0 +COCOAPODS: 1.2.1 diff --git a/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssemblyImpl.swift b/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssemblyImpl.swift index 5f406bdc..6a61f406 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssemblyImpl.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/Assembly/MaskCropperAssemblyImpl.swift @@ -22,7 +22,7 @@ public final class MaskCropperAssemblyImpl: BasePaparazzoAssembly, MaskCropperAs croppingOverlayProvider: croppingOverlayProvider ) - let router = CircleImageCropperUIKitRouter( + let router = MaskCropperUIKitRouter( viewController: viewController ) diff --git a/Paparazzo/Core/VIPER/MaskCropper/Router/MaskCropperUIKitRouter.swift b/Paparazzo/Core/VIPER/MaskCropper/Router/MaskCropperUIKitRouter.swift index adb61e73..d63e704f 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/Router/MaskCropperUIKitRouter.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/Router/MaskCropperUIKitRouter.swift @@ -1,4 +1,4 @@ import ImageSource import UIKit -final class CircleImageCropperUIKitRouter: BaseUIKitRouter, MaskCropperRouter {} +final class MaskCropperUIKitRouter: BaseUIKitRouter, MaskCropperRouter {} diff --git a/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift index 1de34bae..55e7de9b 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift @@ -23,6 +23,7 @@ public final class MediaPickerAssemblyImpl: BasePaparazzoAssembly, MediaPickerAs selectedItem: data.selectedItem, maxItemsCount: data.maxItemsCount, cropCanvasSize: data.cropCanvasSize, + previewEnabled: data.previewEnabled, deviceOrientationService: serviceFactory.deviceOrientationService(), latestLibraryPhotoProvider: serviceFactory.photoLibraryLatestPhotoProvider() ) @@ -47,6 +48,7 @@ public final class MediaPickerAssemblyImpl: BasePaparazzoAssembly, MediaPickerAs viewController.setCameraView(cameraView) viewController.setTheme(theme) viewController.setShowsCropButton(data.cropEnabled) + viewController.setShowPreview(data.previewEnabled) presenter.view = viewController diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift index dc1d1200..09004cf2 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift @@ -25,4 +25,6 @@ protocol MediaPickerInteractor: class { func observeDeviceOrientation(handler: @escaping (DeviceOrientation) -> ()) func observeLatestPhotoLibraryItem(handler: @escaping (ImageSource?) -> ()) + + func previewEnabled(completion: @escaping (Bool) -> ()) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 49f38b2c..2f889dc1 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -7,6 +7,7 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { private let maxItemsCount: Int? private let cropCanvasSize: CGSize + private let previewEnabled: Bool private var items = [MediaPickerItem]() private var photoLibraryItems = [PhotoLibraryItem]() @@ -17,6 +18,7 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { selectedItem: MediaPickerItem?, maxItemsCount: Int?, cropCanvasSize: CGSize, + previewEnabled: Bool, deviceOrientationService: DeviceOrientationService, latestLibraryPhotoProvider: PhotoLibraryLatestPhotoProvider ) { @@ -24,12 +26,17 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { self.selectedItem = selectedItem self.maxItemsCount = maxItemsCount self.cropCanvasSize = cropCanvasSize + self.previewEnabled = previewEnabled self.deviceOrientationService = deviceOrientationService self.latestLibraryPhotoProvider = latestLibraryPhotoProvider } // MARK: - MediaPickerInteractor + func previewEnabled(completion: @escaping (Bool) -> ()) { + completion(previewEnabled) + } + func observeDeviceOrientation(handler: @escaping (DeviceOrientation) -> ()) { deviceOrientationService.onOrientationChange = handler handler(deviceOrientationService.currentOrientation) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift index 8116d75b..3b618076 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift @@ -6,6 +6,7 @@ public struct MediaPickerData { public let maxItemsCount: Int? public let cropEnabled: Bool public let cropCanvasSize: CGSize + public let previewEnabled: Bool public let initialActiveCameraType: CameraType public init( @@ -14,6 +15,7 @@ public struct MediaPickerData { maxItemsCount: Int?, cropEnabled: Bool, cropCanvasSize: CGSize, + previewEnabled: Bool = true, initialActiveCameraType: CameraType = .back) { self.items = items @@ -21,6 +23,7 @@ public struct MediaPickerData { self.maxItemsCount = maxItemsCount self.cropEnabled = cropEnabled self.cropCanvasSize = cropCanvasSize + self.previewEnabled = previewEnabled self.initialActiveCameraType = initialActiveCameraType } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index a55a1698..5db43a0a 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -267,14 +267,21 @@ final class MediaPickerPresenter: MediaPickerModule { guard items.count > 0 else { completion?(); return } view?.addItems(items, animated: fromCamera) { [view] in + view?.setCameraButtonVisible(canAddMoreItems) if canAddMoreItems { view?.setMode(.camera) view?.scrollToCameraThumbnail(animated: true) } else if let lastItem = items.last { - view?.selectItem(lastItem) - view?.scrollToItemThumbnail(lastItem, animated: true) + self.interactor.previewEnabled { previewEnabled in + if previewEnabled { + view?.selectItem(lastItem) + view?.scrollToItemThumbnail(lastItem, animated: true) + } else { + //view?.setMode(.camera) + } + } } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerRouter.swift b/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerRouter.swift index 41f027f3..9196f6af 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerRouter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerRouter.swift @@ -13,6 +13,12 @@ protocol MediaPickerRouter: class { configure: (ImageCroppingModule) -> () ) + func showMaskCropper( + data: MaskCropperData, + croppingOverlayProvider: CroppingOverlayProvider, + configure: (MaskCropperModule) -> () + ) + func focusOnCurrentModule() func dismissCurrentModule() } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerUIKitRouter.swift b/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerUIKitRouter.swift index 61aaa787..9b2a2fe9 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerUIKitRouter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerUIKitRouter.swift @@ -49,6 +49,15 @@ final class MediaPickerUIKitRouter: BaseUIKitRouter, MediaPickerRouter { push(viewController, animated: false) } + func showMaskCropper( + data: MaskCropperData, + croppingOverlayProvider: CroppingOverlayProvider, + configure: (MaskCropperModule) -> ()) + { + + + } + override func focusOnCurrentModule() { super.focusOnCurrentModule(shouldDismissAnimated: { viewController in !cropViewControllers.contains { $0.value == viewController } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index 5722093e..e116dce0 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -43,6 +43,12 @@ final class MediaPickerView: UIView, ThemeConfigurable { private var mode = MediaPickerViewMode.camera private var deviceOrientation = DeviceOrientation.portrait + private var showsPreview: Bool = true { + didSet { + thumbnailRibbonView.isHidden = !showsPreview + } + } + // MARK: - UIView override init(frame: CGRect) { @@ -116,6 +122,8 @@ final class MediaPickerView: UIView, ThemeConfigurable { fatalError("init(coder:) has not been implemented") } + // MARK: - Layout + override func layoutSubviews() { super.layoutSubviews() @@ -123,12 +131,17 @@ final class MediaPickerView: UIView, ThemeConfigurable { left: bounds.left, right: bounds.right, top: bounds.top, - height: bounds.size.width * cameraAspectRatio + height: showsPreview ? bounds.size.width * cameraAspectRatio : bounds.size.height - controlsExtendedHeight ) - let freeSpaceUnderCamera = bounds.bottom - cameraFrame.bottom - let canFitExtendedControls = (freeSpaceUnderCamera >= controlsExtendedHeight) - let controlsHeight = canFitExtendedControls ? controlsExtendedHeight : controlsCompactHeight + let controlsHeight: CGFloat + if showsPreview { + let freeSpaceUnderCamera = bounds.bottom - cameraFrame.bottom + let canFitExtendedControls = (freeSpaceUnderCamera >= controlsExtendedHeight) + controlsHeight = canFitExtendedControls ? controlsExtendedHeight : controlsCompactHeight + } else { + controlsHeight = controlsExtendedHeight + } photoPreviewView.frame = cameraFrame @@ -453,6 +466,10 @@ final class MediaPickerView: UIView, ThemeConfigurable { photoControlsView.setShowsCropButton(showsCropButton) } + func setShowsPreview(_ showsPreview: Bool) { + self.showsPreview = showsPreview + } + func reloadCamera() { photoPreviewView.reloadCamera() thumbnailRibbonView.reloadCamera() diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index 9abeb92b..9bc9bc01 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -346,6 +346,10 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI mediaPickerView.setShowsCropButton(showsCropButton) } + func setShowPreview(_ showPreview: Bool) { + mediaPickerView.setShowsPreview(showPreview) + } + // MARK: - Private func layoutMediaPickerView(interfaceOrientation: UIInterfaceOrientation) { diff --git a/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift b/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift index 02aef417..395d9a0c 100644 --- a/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift +++ b/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift @@ -25,6 +25,7 @@ public final class MediaPickerMarshrouteAssemblyImpl: BasePaparazzoAssembly, Med selectedItem: data.selectedItem, maxItemsCount: data.maxItemsCount, cropCanvasSize: data.cropCanvasSize, + previewEnabled: data.previewEnabled, deviceOrientationService: serviceFactory.deviceOrientationService(), latestLibraryPhotoProvider: serviceFactory.photoLibraryLatestPhotoProvider() ) @@ -48,6 +49,7 @@ public final class MediaPickerMarshrouteAssemblyImpl: BasePaparazzoAssembly, Med viewController.setCameraView(cameraView) viewController.setTheme(theme) viewController.setShowsCropButton(data.cropEnabled) + viewController.setShowPreview(data.previewEnabled) presenter.view = viewController From e9b9b4f42670f349291c39b203956420bc6b996a Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Mon, 29 May 2017 15:01:43 +0300 Subject: [PATCH 007/111] Add MaskCropper to MediaPicker --- .../Example/Presenter/ExamplePresenter.swift | 10 ++-- .../Assembly/MediaPickerAssemblyImpl.swift | 2 +- .../Presenter/MediaPickerPresenter.swift | 47 ++++++++++++++++--- .../Router/MediaPickerUIKitRouter.swift | 13 ++++- .../MarshrouteAssemblyFactory.swift | 3 +- .../MaskCropperMarshrouteAssembly.swift | 2 +- .../MediaPickerMarshrouteAssemblyImpl.swift | 2 +- .../Router/MediaPickerMarshrouteRouter.swift | 20 +++++++- 8 files changed, 80 insertions(+), 19 deletions(-) diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index ddca77c7..78ce7731 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -86,11 +86,11 @@ final class ExamplePresenter { module?.onFinish = { items in module?.dismissModule() } - module?.onItemsAdd = { [weak self] items in - guard let photo = items.first - else { return } - self?.showMaskCropperIn(rootModule: module, photo: photo) - } +// module?.onItemsAdd = { [weak self] items in +// guard let photo = items.first +// else { return } +// self?.showMaskCropperIn(rootModule: module, photo: photo) +// } } ) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift index 55e7de9b..1f6abb1f 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift @@ -2,7 +2,7 @@ import UIKit public final class MediaPickerAssemblyImpl: BasePaparazzoAssembly, MediaPickerAssembly { - typealias AssemblyFactory = CameraAssemblyFactory & ImageCroppingAssemblyFactory & PhotoLibraryAssemblyFactory + typealias AssemblyFactory = CameraAssemblyFactory & ImageCroppingAssemblyFactory & PhotoLibraryAssemblyFactory & MaskCropperAssemblyFactory private let assemblyFactory: AssemblyFactory diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 5db43a0a..9db82e41 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -22,6 +22,10 @@ final class MediaPickerPresenter: MediaPickerModule { } } + // MARK: - Helpers + + private let croppingOverlayProvidersFactory = CroppingOverlayProvidersFactoryImpl() + // MARK: - MediaPickerModule var onItemsAdd: (([MediaPickerItem]) -> ())? @@ -266,7 +270,7 @@ final class MediaPickerPresenter: MediaPickerModule { guard items.count > 0 else { completion?(); return } - view?.addItems(items, animated: fromCamera) { [view] in + view?.addItems(items, animated: fromCamera) { [weak self, view] in view?.setCameraButtonVisible(canAddMoreItems) @@ -274,12 +278,12 @@ final class MediaPickerPresenter: MediaPickerModule { view?.setMode(.camera) view?.scrollToCameraThumbnail(animated: true) } else if let lastItem = items.last { - self.interactor.previewEnabled { previewEnabled in - if previewEnabled { - view?.selectItem(lastItem) - view?.scrollToItemThumbnail(lastItem, animated: true) - } else { - //view?.setMode(.camera) + view?.selectItem(lastItem) + view?.scrollToItemThumbnail(lastItem, animated: true) + + self?.interactor.previewEnabled { [weak self] previewEnabled in + if !previewEnabled { + self?.showMaskCropper(item: lastItem) } } } @@ -316,6 +320,35 @@ final class MediaPickerPresenter: MediaPickerModule { } } + private func showMaskCropper(item: MediaPickerItem) { + + interactor.cropCanvasSize { [weak self] cropCanvasSize in + + let data = MaskCropperData( + imageSource: item.image, + cropCanvasSize: cropCanvasSize + ) + if let croppingOverlayProvider = self?.croppingOverlayProvidersFactory.circleCroppingOverlayProvider() { + self?.router.showMaskCropper( + data: data, + croppingOverlayProvider: croppingOverlayProvider) { module in + + module.onDiscard = { + self?.removeSelectedItem() + module.dismissModule() + } + module.onClose = { + self?.onCancel?() + } + module.onConfirm = { item in + //self?.onFinish?([item]) + } + } + } + } + + } + private func showPhotoLibrary() { interactor.numberOfItemsAvailableForAdding { [weak self] maxItemsCount in diff --git a/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerUIKitRouter.swift b/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerUIKitRouter.swift index 9b2a2fe9..7013281b 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerUIKitRouter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Router/MediaPickerUIKitRouter.swift @@ -3,8 +3,8 @@ import UIKit final class MediaPickerUIKitRouter: BaseUIKitRouter, MediaPickerRouter { - typealias AssemblyFactory = ImageCroppingAssemblyFactory & PhotoLibraryAssemblyFactory - + typealias AssemblyFactory = ImageCroppingAssemblyFactory & PhotoLibraryAssemblyFactory & MaskCropperAssemblyFactory + private let assemblyFactory: AssemblyFactory private var cropViewControllers = [WeakWrapper]() @@ -54,8 +54,17 @@ final class MediaPickerUIKitRouter: BaseUIKitRouter, MediaPickerRouter { croppingOverlayProvider: CroppingOverlayProvider, configure: (MaskCropperModule) -> ()) { + let assembly = assemblyFactory.maskCropperAssembly() + + let viewController = assembly.module( + data: data, + croppingOverlayProvider: croppingOverlayProvider, + configure: configure + ) + cropViewControllers.append(WeakWrapper(value: viewController)) + push(viewController, animated: false) } override func focusOnCurrentModule() { diff --git a/Paparazzo/Marshroute/MarshrouteAssemblyFactory.swift b/Paparazzo/Marshroute/MarshrouteAssemblyFactory.swift index 4b2b6d3f..030ea524 100644 --- a/Paparazzo/Marshroute/MarshrouteAssemblyFactory.swift +++ b/Paparazzo/Marshroute/MarshrouteAssemblyFactory.swift @@ -4,7 +4,8 @@ public final class MarshrouteAssemblyFactory: CameraAssemblyFactory, MediaPickerMarshrouteAssemblyFactory, ImageCroppingAssemblyFactory, - PhotoLibraryMarshrouteAssemblyFactory + PhotoLibraryMarshrouteAssemblyFactory, + MaskCropperMarshrouteAssemblyFactory { private let theme: PaparazzoUITheme private let serviceFactory = ServiceFactoryImpl() diff --git a/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssembly.swift b/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssembly.swift index 95cf0fee..02f1c8c1 100644 --- a/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssembly.swift +++ b/Paparazzo/Marshroute/MaskCropper/Assembly/MaskCropperMarshrouteAssembly.swift @@ -11,5 +11,5 @@ public protocol MaskCropperMarshrouteAssembly: class { } public protocol MaskCropperMarshrouteAssemblyFactory: class { - func circleImageCroppingMarshrouteAssembly() -> MaskCropperAssembly + func maskCropperAssembly() -> MaskCropperMarshrouteAssembly } diff --git a/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift b/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift index 395d9a0c..cb218d77 100644 --- a/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift +++ b/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift @@ -3,7 +3,7 @@ import UIKit public final class MediaPickerMarshrouteAssemblyImpl: BasePaparazzoAssembly, MediaPickerMarshrouteAssembly { - typealias AssemblyFactory = CameraAssemblyFactory & ImageCroppingAssemblyFactory & PhotoLibraryMarshrouteAssemblyFactory + typealias AssemblyFactory = CameraAssemblyFactory & ImageCroppingAssemblyFactory & PhotoLibraryMarshrouteAssemblyFactory & MaskCropperMarshrouteAssemblyFactory private let assemblyFactory: AssemblyFactory diff --git a/Paparazzo/Marshroute/MediaPicker/Router/MediaPickerMarshrouteRouter.swift b/Paparazzo/Marshroute/MediaPicker/Router/MediaPickerMarshrouteRouter.swift index 2a5cc429..f53f300b 100644 --- a/Paparazzo/Marshroute/MediaPicker/Router/MediaPickerMarshrouteRouter.swift +++ b/Paparazzo/Marshroute/MediaPicker/Router/MediaPickerMarshrouteRouter.swift @@ -3,7 +3,7 @@ import Marshroute final class MediaPickerMarshrouteRouter: BaseRouter, MediaPickerRouter { - typealias AssemblyFactory = ImageCroppingAssemblyFactory & PhotoLibraryMarshrouteAssemblyFactory + typealias AssemblyFactory = ImageCroppingAssemblyFactory & PhotoLibraryMarshrouteAssemblyFactory & MaskCropperMarshrouteAssemblyFactory private let assemblyFactory: AssemblyFactory @@ -48,4 +48,22 @@ final class MediaPickerMarshrouteRouter: BaseRouter, MediaPickerRouter { }, animator: NonAnimatedPushAnimator()) } + + func showMaskCropper( + data: MaskCropperData, + croppingOverlayProvider: CroppingOverlayProvider, + configure: (MaskCropperModule) -> ()) + { + pushViewControllerDerivedFrom({ routerSeed in + + let assembly = assemblyFactory.maskCropperAssembly() + + return assembly.module( + data: data, + croppingOverlayProvider: croppingOverlayProvider, + routerSeed: routerSeed, + configure: configure + ) + }, animator: NonAnimatedPushAnimator()) + } } From 9490fafc8fbedf613887a996ebf54b92de015a44 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Mon, 29 May 2017 15:22:14 +0300 Subject: [PATCH 008/111] Remove CircleImageCroppingView --- .../View/MaskCropperViewController.swift | 38 +++++++++---------- .../View/MaskCropperViewInput.swift | 1 - 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift index a950e0a5..d93595d6 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift @@ -8,12 +8,12 @@ final class MaskCropperViewController: typealias ThemeType = MaskCropperUITheme - private let circleImageCroppingView: MaskCropperView + private let maskCropperView: MaskCropperView // MARK: - Init init(croppingOverlayProvider: CroppingOverlayProvider) { - circleImageCroppingView = MaskCropperView( + maskCropperView = MaskCropperView( croppingOverlayProvider: croppingOverlayProvider) super.init() @@ -26,7 +26,7 @@ final class MaskCropperViewController: // MARK: - UIViewController override func loadView() { - view = circleImageCroppingView + view = maskCropperView } override func viewWillAppear(_ animated: Bool) { @@ -50,53 +50,49 @@ final class MaskCropperViewController: // MARK: - MaskCropperViewInput var onConfirmTap: ((CGImage?) -> ())? { - get { return circleImageCroppingView.onConfirmTap } - set { circleImageCroppingView.onConfirmTap = newValue } + get { return maskCropperView.onConfirmTap } + set { maskCropperView.onConfirmTap = newValue } } var onCloseTap: (() -> ())? { - get { return circleImageCroppingView.onCloseTap } - set { circleImageCroppingView.onCloseTap = newValue } + get { return maskCropperView.onCloseTap } + set { maskCropperView.onCloseTap = newValue } } var onDiscardTap: (() -> ())? { - get { return circleImageCroppingView.onDiscardTap } - set { circleImageCroppingView.onDiscardTap = newValue } + get { return maskCropperView.onDiscardTap } + set { maskCropperView.onDiscardTap = newValue } } var onCroppingParametersChange: ((ImageCroppingParameters) -> ())? { - get { return circleImageCroppingView.onCroppingParametersChange } - set { circleImageCroppingView.onCroppingParametersChange = newValue } + get { return maskCropperView.onCroppingParametersChange } + set { maskCropperView.onCroppingParametersChange = newValue } } func setConfirmButtonTitle(_ title: String) { - circleImageCroppingView.setConfirmButtonTitle(title) + maskCropperView.setConfirmButtonTitle(title) } func setImage(_ imageSource: ImageSource, previewImage: ImageSource?, completion: @escaping () -> ()) { - circleImageCroppingView.setImage(imageSource, previewImage: previewImage, completion: completion) + maskCropperView.setImage(imageSource, previewImage: previewImage, completion: completion) } func setCanvasSize(_ canvasSize: CGSize) { - circleImageCroppingView.setCanvasSize(canvasSize) + maskCropperView.setCanvasSize(canvasSize) } func setCroppingParameters(_ parameters: ImageCroppingParameters) { - circleImageCroppingView.setCroppingParameters(parameters) + maskCropperView.setCroppingParameters(parameters) } func setControlsEnabled(_ enabled: Bool) { - circleImageCroppingView.setControlsEnabled(enabled) - } - - func setCroppingOverlayProvider(_: CroppingOverlayProvider) { - + maskCropperView.setControlsEnabled(enabled) } // MARK: - ThemeConfigurable func setTheme(_ theme: ThemeType) { - circleImageCroppingView.setTheme(theme) + maskCropperView.setTheme(theme) } // MARK: - Private diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewInput.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewInput.swift index cb4b4694..6509ba5a 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewInput.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewInput.swift @@ -1,7 +1,6 @@ import ImageSource protocol MaskCropperViewInput: class { - func setCroppingOverlayProvider(_: CroppingOverlayProvider) func setConfirmButtonTitle(_: String) func setImage(_: ImageSource, previewImage: ImageSource?, completion: @escaping () -> ()) func setCroppingParameters(_: ImageCroppingParameters) From d50c5031cf9878927187ab470bc8153a55e9ea38 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Mon, 29 May 2017 16:33:32 +0300 Subject: [PATCH 009/111] Fix choosing for maxCount in case of selection one item --- .../Interactor/PhotoLibraryInteractor.swift | 2 ++ .../PhotoLibraryInteractorImpl.swift | 4 +++ .../Presenter/PhotoLibraryPresenter.swift | 15 +++++++++ .../PhotoLibrary/View/PhotoLibraryView.swift | 32 +++++++++++++++---- .../View/PhotoLibraryViewController.swift | 4 +++ .../View/PhotoLibraryViewInput.swift | 3 ++ 6 files changed, 53 insertions(+), 7 deletions(-) diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractor.swift b/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractor.swift index 91006f79..6c83e905 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractor.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractor.swift @@ -9,6 +9,8 @@ protocol PhotoLibraryInteractor: class { func selectItem(_: PhotoLibraryItem, completion: @escaping (PhotoLibraryItemSelectionState) -> ()) func deselectItem(_: PhotoLibraryItem, completion: @escaping (PhotoLibraryItemSelectionState) -> ()) func selectedItems(completion: @escaping ([PhotoLibraryItem]) -> ()) + + func maxSelectedItemsCount(completion: @escaping ((Int?) -> ())) } public struct PhotoLibraryItem: Equatable { diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractorImpl.swift b/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractorImpl.swift index c9bc48ac..bb2be50b 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractorImpl.swift @@ -29,6 +29,10 @@ final class PhotoLibraryInteractorImpl: PhotoLibraryInteractor { maxSelectedItemsCount = count } + func maxSelectedItemsCount(completion: @escaping ((Int?) -> ())) { + completion(maxSelectedItemsCount) + } + func observeAuthorizationStatus(handler: @escaping (_ accessGranted: Bool) -> ()) { photoLibraryItemsService.observeAuthorizationStatus(handler: handler) } diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift b/Paparazzo/Core/VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift index f65d8b80..b6dcb86e 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift @@ -101,6 +101,21 @@ final class PhotoLibraryPresenter: PhotoLibraryModule { cellData.selected = item.selected + cellData.onShouldSelect = { [weak self] in + self?.interactor.selectedItems { items in + guard items.count > 0 else { + return + } + + self?.interactor.maxSelectedItemsCount { maxSelectedItemsCount in + if maxSelectedItemsCount == 1 { + self?.view?.setCanSelectMoreItems(true) + self?.view?.deselectAllItems() + } + } + } + } + cellData.onSelect = { [weak self] in self?.interactor.selectItem(item) { selectionState in self?.adjustViewForSelectionState(selectionState) diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift index 09731742..35dc83ba 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift @@ -136,6 +136,17 @@ final class PhotoLibraryView: UIView, UICollectionViewDelegateFlowLayout, ThemeC ) } + func deselectAndAdjustAllCells() { + + guard let indexPathsForSelectedItems = collectionView.indexPathsForSelectedItems + else { return } + + for indexPath in indexPathsForSelectedItems { + collectionView.deselectItem(at: indexPath, animated: false) + onDeselectItem(at: indexPath) + } + } + func scrollToBottom() { collectionView.scrollToBottom() } @@ -164,6 +175,9 @@ final class PhotoLibraryView: UIView, UICollectionViewDelegateFlowLayout, ThemeC func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { let cellData = dataSource.item(at: indexPath) + + cellData.onShouldSelect?() + return canSelectMoreItems && cellData.previewAvailable } @@ -178,13 +192,7 @@ final class PhotoLibraryView: UIView, UICollectionViewDelegateFlowLayout, ThemeC } func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { - - dataSource.mutateItem(at: indexPath) { (cellData: inout PhotoLibraryItemCellData) in - cellData.selected = false - } - dataSource.item(at: indexPath).onDeselect?() - - adjustDimmingForCellAtIndexPath(indexPath) + onDeselectItem(at: indexPath) } // MARK: - Private @@ -279,4 +287,14 @@ final class PhotoLibraryView: UIView, UICollectionViewDelegateFlowLayout, ThemeC collectionView.selectItem(at: indexPath, animated: false, scrollPosition: []) } } + + private func onDeselectItem(at indexPath: IndexPath) { + dataSource.mutateItem(at: indexPath) { (cellData: inout PhotoLibraryItemCellData) in + cellData.selected = false + } + dataSource.item(at: indexPath).onDeselect?() + + adjustDimmingForCellAtIndexPath(indexPath) + } + } diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewController.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewController.swift index 56946943..8971b8fa 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewController.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewController.swift @@ -87,6 +87,10 @@ final class PhotoLibraryViewController: PaparazzoViewController, PhotoLibraryVie photoLibraryView.dimsUnselectedItems = dimUnselectedItems } + func deselectAllItems() { + photoLibraryView.deselectAndAdjustAllCells() + } + func setPickButtonVisible(_ visible: Bool) { navigationItem.rightBarButtonItem = visible ? pickBarButtonItem : nil } diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift index 391abc40..0c6bf4fa 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift @@ -12,6 +12,8 @@ protocol PhotoLibraryViewInput: class { func setCanSelectMoreItems(_: Bool) func setDimsUnselectedItems(_: Bool) + func deselectAllItems() + func setPickButtonVisible(_: Bool) func setPickButtonEnabled(_: Bool) @@ -38,6 +40,7 @@ struct PhotoLibraryItemCellData { var previewAvailable = false var onSelect: (() -> ())? + var onShouldSelect: (() -> ())? var onDeselect: (() -> ())? init(image: ImageSource) { From 46f3fb4f8abf7d310ff86af4a12f6c84d1c397b4 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Mon, 29 May 2017 18:42:50 +0300 Subject: [PATCH 010/111] Changed mask cropped controls, fix MediaPicker result for MaskCropper --- .../Example/Presenter/ExamplePresenter.swift | 3 - Paparazzo/Core/MediaPickerUITheme.swift | 12 +-- .../View/CroppingPreviewView.swift | 3 +- .../Module/MaskCropperModule.swift | 1 - .../Presenter/MaskCropperPresenter.swift | 7 -- .../View/MaskCropperControlsView.swift | 32 ++++-- .../MaskCropper/View/MaskCropperUITheme.swift | 10 +- .../MaskCropper/View/MaskCropperView.swift | 102 ++---------------- .../View/MaskCropperViewController.swift | 9 -- .../View/MaskCropperViewInput.swift | 2 - .../Presenter/MediaPickerPresenter.swift | 14 ++- 11 files changed, 50 insertions(+), 145 deletions(-) diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index 78ce7731..967d3da5 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -109,9 +109,6 @@ final class ExamplePresenter { module?.onDiscard = { module?.dismissModule() } - module?.onClose = { - rootModule?.dismissModule() - } module?.onConfirm = { _ in rootModule?.dismissModule() } diff --git a/Paparazzo/Core/MediaPickerUITheme.swift b/Paparazzo/Core/MediaPickerUITheme.swift index 347eb8cc..f8a754d7 100644 --- a/Paparazzo/Core/MediaPickerUITheme.swift +++ b/Paparazzo/Core/MediaPickerUITheme.swift @@ -58,16 +58,8 @@ public struct PaparazzoUITheme: // MARK: - MaskCropperUITheme - public var maskCropperDiscardPhotoIcon = PaparazzoUITheme.image(named: "delete") - public var maskCropperCloseButtonIcon = PaparazzoUITheme.image(named: "bt-close") - - public var maskCropperButtonsBackgroundNormalColor = UIColor.white - public var maskCropperButtonsBackgroundHighlightedColor = UIColor(white: 1, alpha: 0.6) - public var maskCropperButtonsBackgroundDisabledColor = UIColor(white: 1, alpha: 0.6) - public var maskCropperConfirmButtonTitleColor = UIColor(red: 0, green: 170.0/255, blue: 1, alpha: 1) - public var maskCropperConfirmButtonTitleHighlightedColor = UIColor(red: 0, green: 152.0/255, blue: 229.0/255, alpha: 1) - - public var maskCropperConfirmButtonTitleFont = UIFont.systemFont(ofSize: 17) + public var maskCropperDiscardPhotoIcon = PaparazzoUITheme.image(named: "discard") + public var maskCropperConfirmPhotoIcon = PaparazzoUITheme.image(named: "confirm") // MARK: - Private diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift index b6cdbb33..cb2378a8 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift @@ -56,7 +56,8 @@ final class CroppingPreviewView: UIView { if let previewImage = previewImage { - let screenSize = UIScreen.main.bounds.size + var screenSize = UIScreen.main.bounds.size + let previewOptions = ImageRequestOptions(size: .fitSize(screenSize), deliveryMode: .progressive) onPreviewImageWillLoading?() diff --git a/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperModule.swift b/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperModule.swift index ad81af71..f27eb493 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperModule.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/Module/MaskCropperModule.swift @@ -4,7 +4,6 @@ public protocol MaskCropperModule: class { func dismissModule() - var onClose: (() -> ())? { get set } var onDiscard: (() -> ())? { get set } var onConfirm: ((ImageSource) -> ())? { get set } } diff --git a/Paparazzo/Core/VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift b/Paparazzo/Core/VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift index 8c99b357..c3545724 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/Presenter/MaskCropperPresenter.swift @@ -18,16 +18,10 @@ final class MaskCropperPresenter: MaskCropperModule { func setUpView() { - view?.setConfirmButtonTitle("Готово") - view?.onDiscardTap = { [weak self] in self?.onDiscard?() } - view?.onCloseTap = { [weak self] in - self?.onClose?() - } - view?.onCroppingParametersChange = { [weak self] parameters in self?.interactor.setCroppingParameters(parameters) } @@ -58,7 +52,6 @@ final class MaskCropperPresenter: MaskCropperModule { } var onDiscard: (() -> ())? - var onClose: (() -> ())? var onConfirm: ((ImageSource) -> ())? func dismissModule() { diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift index c157fe11..19bbd3d9 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift @@ -6,7 +6,8 @@ final class MaskCropperControlsView: UIView, ThemeConfigurable { // MARK: - Subviews - private let discardButton = UIButton(type: .custom) + private let discardButton = UIButton(type: .custom) + private let confirmButton = UIButton(type: .custom) // MARK: - Init @@ -14,12 +15,19 @@ final class MaskCropperControlsView: UIView, ThemeConfigurable { super.init(frame: .zero) addSubview(discardButton) + addSubview(confirmButton) discardButton.addTarget( self, action: #selector(onDiscardTap(_:)), for: .touchUpInside ) + + confirmButton.addTarget( + self, + action: #selector(onConfirmTap(_:)), + for: .touchUpInside + ) } required init?(coder aDecoder: NSCoder) { @@ -31,13 +39,16 @@ final class MaskCropperControlsView: UIView, ThemeConfigurable { public override func layoutSubviews() { super.layoutSubviews() - discardButton.size = CGSize( - width: 30, - height: 30 + discardButton.size = CGSize.minimumTapAreaSize + discardButton.center = CGPoint( + x: bounds.left + bounds.size.width * 0.25, + y: bounds.bottom - 40 ) - discardButton.centerX = centerX - discardButton.bottom = height - 22 + confirmButton.size = CGSize.minimumTapAreaSize + confirmButton.center = CGPoint( + x: bounds.right - bounds.size.width * 0.25, + y: discardButton.centerY) } // MARK: - ThemeConfigurable @@ -47,11 +58,16 @@ final class MaskCropperControlsView: UIView, ThemeConfigurable { theme.maskCropperDiscardPhotoIcon, for: .normal ) + confirmButton.setImage( + theme.maskCropperConfirmPhotoIcon, + for: .normal + ) } // MARK: - MaskCropperControlsView var onDiscardTap: (() -> ())? + var onConfirmTap: (() -> ())? func setControlsEnabled(_ enabled: Bool) { discardButton.isEnabled = enabled @@ -61,4 +77,8 @@ final class MaskCropperControlsView: UIView, ThemeConfigurable { @objc private func onDiscardTap(_ sender: UIButton) { onDiscardTap?() } + + @objc private func onConfirmTap(_ sender: UIButton) { + onConfirmTap?() + } } diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperUITheme.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperUITheme.swift index 37e69001..8c1b152d 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperUITheme.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperUITheme.swift @@ -1,12 +1,4 @@ public protocol MaskCropperUITheme { var maskCropperDiscardPhotoIcon: UIImage? { get } - var maskCropperCloseButtonIcon: UIImage? { get } - - var maskCropperButtonsBackgroundNormalColor: UIColor { get } - var maskCropperButtonsBackgroundHighlightedColor: UIColor { get } - var maskCropperButtonsBackgroundDisabledColor: UIColor { get } - var maskCropperConfirmButtonTitleColor: UIColor { get } - var maskCropperConfirmButtonTitleHighlightedColor: UIColor { get } - - var maskCropperConfirmButtonTitleFont: UIFont { get } + var maskCropperConfirmPhotoIcon: UIImage? { get } } diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift index a577e1ee..44be055f 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift @@ -8,15 +8,11 @@ final class MaskCropperView: UIView, ThemeConfigurable { private let overlayView: MaskCropperOverlayView private let controlsView = MaskCropperControlsView() private let previewView = CroppingPreviewView() - private let closeButton = UIButton() - private let confirmButton = UIButton() // MARK: - Constants private let aspectRatio = CGFloat(AspectRatio.portrait_3x4.widthToHeightRatio()) - private let closeButtonSize = CGSize(width: 38, height: 38) - private let continueButtonHeight = CGFloat(38) - private let continueButtonContentInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + private let controlsExtendedHeight = CGFloat(80) // MARK: - Init @@ -36,10 +32,6 @@ final class MaskCropperView: UIView, ThemeConfigurable { addSubview(previewView) addSubview(overlayView) addSubview(controlsView) - addSubview(closeButton) - addSubview(confirmButton) - - setupButtons() overlayView.isUserInteractionEnabled = false } @@ -52,36 +44,6 @@ final class MaskCropperView: UIView, ThemeConfigurable { func setTheme(_ theme: MaskCropperUITheme) { controlsView.setTheme(theme) - - confirmButton.setTitleColor(theme.maskCropperConfirmButtonTitleColor, for: .normal) - confirmButton.titleLabel?.font = theme.maskCropperConfirmButtonTitleFont - - closeButton.setImage(theme.maskCropperCloseButtonIcon, for: .normal) - - confirmButton.setTitleColor( - theme.maskCropperConfirmButtonTitleColor, - for: .normal - ) - confirmButton.setTitleColor( - theme.maskCropperConfirmButtonTitleHighlightedColor, - for: .highlighted - ) - - let onePointSize = CGSize(width: 1, height: 1) - for button in [confirmButton, closeButton] { - button.setBackgroundImage( - UIImage.imageWithColor(theme.maskCropperButtonsBackgroundNormalColor, imageSize: onePointSize), - for: .normal - ) - button.setBackgroundImage( - UIImage.imageWithColor(theme.maskCropperButtonsBackgroundHighlightedColor, imageSize: onePointSize), - for: .highlighted - ) - button.setBackgroundImage( - UIImage.imageWithColor(theme.maskCropperButtonsBackgroundDisabledColor, imageSize: onePointSize), - for: .disabled - ) - } } // MARK: - Layout @@ -89,33 +51,24 @@ final class MaskCropperView: UIView, ThemeConfigurable { public override func layoutSubviews() { previewView.width = width - previewView.height = height * aspectRatio + previewView.height = height - controlsExtendedHeight overlayView.frame = previewView.frame controlsView.top = previewView.bottom controlsView.width = width - controlsView.height = height - previewView.height - - closeButton.frame = CGRect( - x: 8, - y: 8, - width: closeButton.width, - height: closeButton.height - ) - - confirmButton.frame = CGRect( - x: bounds.right - 8 - confirmButton.width, - y: 8, - width: confirmButton.width, - height: confirmButton.height - ) + controlsView.height = controlsExtendedHeight } // MARK: - MaskCropperView - var onCloseTap: (() -> ())? - var onConfirmTap: ((_ previewImage: CGImage?) -> ())? + var onConfirmTap: ((_ previewImage: CGImage?) -> ())? { + didSet { + controlsView.onConfirmTap = { [weak self] in + self?.onConfirmTap?(self?.previewView.cropPreviewImage()) + } + } + } var onDiscardTap: (() -> ())? { get { return controlsView.onDiscardTap } @@ -143,39 +96,4 @@ final class MaskCropperView: UIView, ThemeConfigurable { controlsView.setControlsEnabled(enabled) } - func setConfirmButtonTitle(_ title: String) { - confirmButton.setTitle(title, for: .normal) - confirmButton.size = CGSize(width: confirmButton.sizeThatFits().width, height: continueButtonHeight) - } - - // MARK: - Private - - private func setupButtons() { - closeButton.layer.cornerRadius = closeButtonSize.height / 2 - closeButton.layer.masksToBounds = true - closeButton.size = closeButtonSize - closeButton.addTarget( - self, - action: #selector(onCloseTap(_:)), - for: .touchUpInside - ) - - confirmButton.layer.cornerRadius = continueButtonHeight / 2 - confirmButton.layer.masksToBounds = true - confirmButton.contentEdgeInsets = continueButtonContentInsets - confirmButton.addTarget( - self, - action: #selector(onConfirmTap(_:)), - for: .touchUpInside - ) - } - - @objc private func onCloseTap(_: UIButton) { - onCloseTap?() - } - - @objc private func onConfirmTap(_: UIButton) { - onConfirmTap?(previewView.cropPreviewImage()) - } - } diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift index d93595d6..7d1aaa62 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift @@ -54,11 +54,6 @@ final class MaskCropperViewController: set { maskCropperView.onConfirmTap = newValue } } - var onCloseTap: (() -> ())? { - get { return maskCropperView.onCloseTap } - set { maskCropperView.onCloseTap = newValue } - } - var onDiscardTap: (() -> ())? { get { return maskCropperView.onDiscardTap } set { maskCropperView.onDiscardTap = newValue } @@ -69,10 +64,6 @@ final class MaskCropperViewController: set { maskCropperView.onCroppingParametersChange = newValue } } - func setConfirmButtonTitle(_ title: String) { - maskCropperView.setConfirmButtonTitle(title) - } - func setImage(_ imageSource: ImageSource, previewImage: ImageSource?, completion: @escaping () -> ()) { maskCropperView.setImage(imageSource, previewImage: previewImage, completion: completion) } diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewInput.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewInput.swift index 6509ba5a..4d9db0bf 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewInput.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewInput.swift @@ -1,13 +1,11 @@ import ImageSource protocol MaskCropperViewInput: class { - func setConfirmButtonTitle(_: String) func setImage(_: ImageSource, previewImage: ImageSource?, completion: @escaping () -> ()) func setCroppingParameters(_: ImageCroppingParameters) func setCanvasSize(_: CGSize) func setControlsEnabled(_: Bool) - var onCloseTap: (() -> ())? { get set } var onConfirmTap: ((_ previewImage: CGImage?) -> ())? { get set } var onDiscardTap: (() -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 9db82e41..18a00e2f 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -337,11 +337,15 @@ final class MediaPickerPresenter: MediaPickerModule { self?.removeSelectedItem() module.dismissModule() } - module.onClose = { - self?.onCancel?() - } - module.onConfirm = { item in - //self?.onFinish?([item]) + module.onConfirm = { image in + + let croppedItem = MediaPickerItem( + identifier: item.identifier, + image: image, + source: item.source + ) + + self?.onFinish?([croppedItem]) } } } From 2a376522a4f41cff9ad90bcddb17610b09a883f8 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Mon, 29 May 2017 19:28:54 +0300 Subject: [PATCH 011/111] Add crop mode --- .../Example/Presenter/ExamplePresenter.swift | 7 +- .../Assembly/MediaPickerAssemblyImpl.swift | 2 - .../Interactor/MediaPickerInteractor.swift | 3 +- .../MediaPickerInteractorImpl.swift | 12 ++-- .../MediaPicker/Module/MediaPickerData.swift | 3 - .../Module/MediaPickerModule.swift | 7 ++ .../Presenter/MediaPickerPresenter.swift | 64 +++++++++++-------- .../View/MediaPickerViewInput.swift | 2 + .../MediaPickerMarshrouteAssemblyImpl.swift | 2 - 9 files changed, 56 insertions(+), 46 deletions(-) diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index 967d3da5..39d61530 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -71,7 +71,6 @@ final class ExamplePresenter { maxItemsCount: 1, cropEnabled: true, cropCanvasSize: cropCanvasSize, - previewEnabled: false, initialActiveCameraType: .front ) @@ -80,17 +79,13 @@ final class ExamplePresenter { configure: { module in weak var module = module module?.setContinueButtonVisible(false) + module?.setCropMode(.custom(croppingOverlayProvidersFactory.circleCroppingOverlayProvider())) module?.onCancel = { module?.dismissModule() } module?.onFinish = { items in module?.dismissModule() } -// module?.onItemsAdd = { [weak self] items in -// guard let photo = items.first -// else { return } -// self?.showMaskCropperIn(rootModule: module, photo: photo) -// } } ) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift index 1f6abb1f..50ef206e 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift @@ -23,7 +23,6 @@ public final class MediaPickerAssemblyImpl: BasePaparazzoAssembly, MediaPickerAs selectedItem: data.selectedItem, maxItemsCount: data.maxItemsCount, cropCanvasSize: data.cropCanvasSize, - previewEnabled: data.previewEnabled, deviceOrientationService: serviceFactory.deviceOrientationService(), latestLibraryPhotoProvider: serviceFactory.photoLibraryLatestPhotoProvider() ) @@ -48,7 +47,6 @@ public final class MediaPickerAssemblyImpl: BasePaparazzoAssembly, MediaPickerAs viewController.setCameraView(cameraView) viewController.setTheme(theme) viewController.setShowsCropButton(data.cropEnabled) - viewController.setShowPreview(data.previewEnabled) presenter.view = viewController diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift index 09004cf2..69bf38ec 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift @@ -26,5 +26,6 @@ protocol MediaPickerInteractor: class { func observeDeviceOrientation(handler: @escaping (DeviceOrientation) -> ()) func observeLatestPhotoLibraryItem(handler: @escaping (ImageSource?) -> ()) - func previewEnabled(completion: @escaping (Bool) -> ()) + func setCropMode(_: MediaPickerCropMode) + func cropMode(completion: @escaping (MediaPickerCropMode) -> ()) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 2f889dc1..08993e8b 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -7,18 +7,17 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { private let maxItemsCount: Int? private let cropCanvasSize: CGSize - private let previewEnabled: Bool private var items = [MediaPickerItem]() private var photoLibraryItems = [PhotoLibraryItem]() private var selectedItem: MediaPickerItem? + private var mode: MediaPickerCropMode = .normal init( items: [MediaPickerItem], selectedItem: MediaPickerItem?, maxItemsCount: Int?, cropCanvasSize: CGSize, - previewEnabled: Bool, deviceOrientationService: DeviceOrientationService, latestLibraryPhotoProvider: PhotoLibraryLatestPhotoProvider ) { @@ -26,15 +25,18 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { self.selectedItem = selectedItem self.maxItemsCount = maxItemsCount self.cropCanvasSize = cropCanvasSize - self.previewEnabled = previewEnabled self.deviceOrientationService = deviceOrientationService self.latestLibraryPhotoProvider = latestLibraryPhotoProvider } // MARK: - MediaPickerInteractor - func previewEnabled(completion: @escaping (Bool) -> ()) { - completion(previewEnabled) + func setCropMode(_ mode: MediaPickerCropMode) { + self.mode = mode + } + + func cropMode(completion: @escaping (MediaPickerCropMode) -> ()) { + completion(mode) } func observeDeviceOrientation(handler: @escaping (DeviceOrientation) -> ()) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift index 3b618076..8116d75b 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift @@ -6,7 +6,6 @@ public struct MediaPickerData { public let maxItemsCount: Int? public let cropEnabled: Bool public let cropCanvasSize: CGSize - public let previewEnabled: Bool public let initialActiveCameraType: CameraType public init( @@ -15,7 +14,6 @@ public struct MediaPickerData { maxItemsCount: Int?, cropEnabled: Bool, cropCanvasSize: CGSize, - previewEnabled: Bool = true, initialActiveCameraType: CameraType = .back) { self.items = items @@ -23,7 +21,6 @@ public struct MediaPickerData { self.maxItemsCount = maxItemsCount self.cropEnabled = cropEnabled self.cropCanvasSize = cropCanvasSize - self.previewEnabled = previewEnabled self.initialActiveCameraType = initialActiveCameraType } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift index 08b335af..31833e24 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift @@ -1,3 +1,8 @@ +public enum MediaPickerCropMode { + case normal + case custom(CroppingOverlayProvider) +} + public protocol MediaPickerModule: class { func focusOnModule() @@ -7,6 +12,8 @@ public protocol MediaPickerModule: class { func setContinueButtonEnabled(_: Bool) func setContinueButtonVisible(_: Bool) + func setCropMode(_: MediaPickerCropMode) + var onItemsAdd: (([MediaPickerItem]) -> ())? { get set } var onItemUpdate: ((MediaPickerItem) -> ())? { get set } var onItemRemove: ((MediaPickerItem) -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 18a00e2f..310d3d16 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -22,10 +22,6 @@ final class MediaPickerPresenter: MediaPickerModule { } } - // MARK: - Helpers - - private let croppingOverlayProvidersFactory = CroppingOverlayProvidersFactoryImpl() - // MARK: - MediaPickerModule var onItemsAdd: (([MediaPickerItem]) -> ())? @@ -55,6 +51,16 @@ final class MediaPickerPresenter: MediaPickerModule { } } + func setCropMode(_ cropMode: MediaPickerCropMode) { + switch cropMode { + case .normal: + view?.setShowPreview(true) + case .custom: + view?.setShowPreview(false) + } + interactor.setCropMode(cropMode) + } + func focusOnModule() { router.focusOnCurrentModule() } @@ -281,9 +287,15 @@ final class MediaPickerPresenter: MediaPickerModule { view?.selectItem(lastItem) view?.scrollToItemThumbnail(lastItem, animated: true) - self?.interactor.previewEnabled { [weak self] previewEnabled in - if !previewEnabled { - self?.showMaskCropper(item: lastItem) + self?.interactor.cropMode { [weak self] mode in + switch mode { + case .normal: + break + case .custom(let provider): + self?.showMaskCropper( + croppingOverlayProvider: provider, + item: lastItem + ) } } } @@ -320,7 +332,7 @@ final class MediaPickerPresenter: MediaPickerModule { } } - private func showMaskCropper(item: MediaPickerItem) { + private func showMaskCropper(croppingOverlayProvider: CroppingOverlayProvider, item: MediaPickerItem) { interactor.cropCanvasSize { [weak self] cropCanvasSize in @@ -328,26 +340,24 @@ final class MediaPickerPresenter: MediaPickerModule { imageSource: item.image, cropCanvasSize: cropCanvasSize ) - if let croppingOverlayProvider = self?.croppingOverlayProvidersFactory.circleCroppingOverlayProvider() { - self?.router.showMaskCropper( - data: data, - croppingOverlayProvider: croppingOverlayProvider) { module in + self?.router.showMaskCropper( + data: data, + croppingOverlayProvider: croppingOverlayProvider) { module in + + module.onDiscard = { + self?.removeSelectedItem() + module.dismissModule() + } + module.onConfirm = { image in - module.onDiscard = { - self?.removeSelectedItem() - module.dismissModule() - } - module.onConfirm = { image in - - let croppedItem = MediaPickerItem( - identifier: item.identifier, - image: image, - source: item.source - ) - - self?.onFinish?([croppedItem]) - } - } + let croppedItem = MediaPickerItem( + identifier: item.identifier, + image: image, + source: item.source + ) + + self?.onFinish?([croppedItem]) + } } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift index 142f1f2f..429757c2 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift @@ -54,6 +54,8 @@ protocol MediaPickerViewInput: class { func setContinueButtonVisible(_: Bool) + func setShowPreview(_ showPreview: Bool) + // MARK: - Actions in photo ribbon var onItemSelect: ((MediaPickerItem) -> ())? { get set } var onItemMove: ((_ sourceIndex: Int, _ destinationIndex: Int) -> ())? { get set } diff --git a/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift b/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift index cb218d77..a16a2d01 100644 --- a/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift +++ b/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift @@ -25,7 +25,6 @@ public final class MediaPickerMarshrouteAssemblyImpl: BasePaparazzoAssembly, Med selectedItem: data.selectedItem, maxItemsCount: data.maxItemsCount, cropCanvasSize: data.cropCanvasSize, - previewEnabled: data.previewEnabled, deviceOrientationService: serviceFactory.deviceOrientationService(), latestLibraryPhotoProvider: serviceFactory.photoLibraryLatestPhotoProvider() ) @@ -49,7 +48,6 @@ public final class MediaPickerMarshrouteAssemblyImpl: BasePaparazzoAssembly, Med viewController.setCameraView(cameraView) viewController.setTheme(theme) viewController.setShowsCropButton(data.cropEnabled) - viewController.setShowPreview(data.previewEnabled) presenter.view = viewController From d2718ef47024615ee1e68c564ed6ae32c4753b3f Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Mon, 29 May 2017 19:39:16 +0300 Subject: [PATCH 012/111] Revert signing option --- Example/PaparazzoExample.xcodeproj/project.pbxproj | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Example/PaparazzoExample.xcodeproj/project.pbxproj b/Example/PaparazzoExample.xcodeproj/project.pbxproj index f5bd3290..ad42161c 100644 --- a/Example/PaparazzoExample.xcodeproj/project.pbxproj +++ b/Example/PaparazzoExample.xcodeproj/project.pbxproj @@ -375,8 +375,7 @@ TargetAttributes = { 251E57BF1E65651F0009A288 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = 5PHGGKL9UQ; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; 25A489B01E656A2B00CC431B = { CreatedOnToolsVersion = 8.2.1; @@ -759,7 +758,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 5PHGGKL9UQ; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = PaparazzoExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -776,7 +775,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 5PHGGKL9UQ; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = PaparazzoExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; From 1fb23a45fd6c895010a23566dd238c93b54c437d Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Mon, 29 May 2017 19:40:28 +0300 Subject: [PATCH 013/111] Fix MediaPicker example --- .../PaparazzoExample/Example/Presenter/ExamplePresenter.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index 39d61530..eb9f7a8e 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -116,9 +116,9 @@ final class ExamplePresenter { items.append(contentsOf: remoteItems) let data = MediaPickerData( - items: [], + items: items, selectedItem: items.last, - maxItemsCount: 1, + maxItemsCount: 20, cropEnabled: true, cropCanvasSize: cropCanvasSize ) From e407a710f0b2acee395ac0492c09cee795e56214 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Tue, 30 May 2017 15:45:01 +0300 Subject: [PATCH 014/111] Rename onShouldSelect to onPrepareSelection, move logic for preparing selection in Interactor --- .../View/CroppingPreviewView.swift | 2 +- .../Presenter/MediaPickerPresenter.swift | 2 +- .../Interactor/PhotoLibraryInteractor.swift | 10 +++++++-- .../PhotoLibraryInteractorImpl.swift | 22 ++++++++++--------- .../Presenter/PhotoLibraryPresenter.swift | 22 +++++++++---------- .../PhotoLibrary/View/PhotoLibraryView.swift | 2 +- .../View/PhotoLibraryViewInput.swift | 2 +- 7 files changed, 34 insertions(+), 28 deletions(-) diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift index cb2378a8..c04a464a 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift @@ -56,7 +56,7 @@ final class CroppingPreviewView: UIView { if let previewImage = previewImage { - var screenSize = UIScreen.main.bounds.size + let screenSize = UIScreen.main.bounds.size let previewOptions = ImageRequestOptions(size: .fitSize(screenSize), deliveryMode: .progressive) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 310d3d16..4f854cdd 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -348,8 +348,8 @@ final class MediaPickerPresenter: MediaPickerModule { self?.removeSelectedItem() module.dismissModule() } + module.onConfirm = { image in - let croppedItem = MediaPickerItem( identifier: item.identifier, image: image, diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractor.swift b/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractor.swift index 6c83e905..16b4fd8c 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractor.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractor.swift @@ -8,9 +8,8 @@ protocol PhotoLibraryInteractor: class { func selectItem(_: PhotoLibraryItem, completion: @escaping (PhotoLibraryItemSelectionState) -> ()) func deselectItem(_: PhotoLibraryItem, completion: @escaping (PhotoLibraryItemSelectionState) -> ()) + func prepareSelection(completion: @escaping (PhotoLibraryItemSelectionState) -> ()) func selectedItems(completion: @escaping ([PhotoLibraryItem]) -> ()) - - func maxSelectedItemsCount(completion: @escaping ((Int?) -> ())) } public struct PhotoLibraryItem: Equatable { @@ -32,8 +31,15 @@ public func ==(item1: PhotoLibraryItem, item2: PhotoLibraryItem) -> Bool { } struct PhotoLibraryItemSelectionState { + + enum PreSelectionAction { + case none + case deselectAll + } + var isAnyItemSelected: Bool var canSelectMoreItems: Bool + var preSelectionAction: PreSelectionAction } struct PhotoLibraryChanges { diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractorImpl.swift b/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractorImpl.swift index bb2be50b..6da25988 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/Interactor/PhotoLibraryInteractorImpl.swift @@ -25,14 +25,6 @@ final class PhotoLibraryInteractorImpl: PhotoLibraryInteractor { // MARK: - PhotoLibraryInteractor - func setMaxSelectedItemsCount(count: Int?) { - maxSelectedItemsCount = count - } - - func maxSelectedItemsCount(completion: @escaping ((Int?) -> ())) { - completion(maxSelectedItemsCount) - } - func observeAuthorizationStatus(handler: @escaping (_ accessGranted: Bool) -> ()) { photoLibraryItemsService.observeAuthorizationStatus(handler: handler) } @@ -76,6 +68,15 @@ final class PhotoLibraryInteractorImpl: PhotoLibraryInteractor { completion(selectionState()) } + func prepareSelection(completion: @escaping (PhotoLibraryItemSelectionState) -> ()) { + if selectedItems.count > 0 && maxSelectedItemsCount == 1 { + selectedItems.removeAll() + completion(selectionState(preSelectionAction: .deselectAll)) + } else { + completion(selectionState()) + } + } + func selectedItems(completion: @escaping ([PhotoLibraryItem]) -> ()) { completion(selectedItems) } @@ -86,10 +87,11 @@ final class PhotoLibraryInteractorImpl: PhotoLibraryInteractor { return maxSelectedItemsCount.flatMap { selectedItems.count < $0 } ?? true } - private func selectionState() -> PhotoLibraryItemSelectionState { + private func selectionState(preSelectionAction: PhotoLibraryItemSelectionState.PreSelectionAction = .none) -> PhotoLibraryItemSelectionState { return PhotoLibraryItemSelectionState( isAnyItemSelected: selectedItems.count > 0, - canSelectMoreItems: canSelectMoreItems() + canSelectMoreItems: canSelectMoreItems(), + preSelectionAction: preSelectionAction ) } diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift b/Paparazzo/Core/VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift index b6dcb86e..01028a01 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift @@ -93,6 +93,13 @@ final class PhotoLibraryPresenter: PhotoLibraryModule { view?.setDimsUnselectedItems(!state.canSelectMoreItems) view?.setCanSelectMoreItems(state.canSelectMoreItems) view?.setPickButtonEnabled(state.isAnyItemSelected) + + switch state.preSelectionAction { + case .none: + break + case .deselectAll: + view?.deselectAllItems() + } } private func cellData(_ item: PhotoLibraryItem) -> PhotoLibraryItemCellData { @@ -101,18 +108,9 @@ final class PhotoLibraryPresenter: PhotoLibraryModule { cellData.selected = item.selected - cellData.onShouldSelect = { [weak self] in - self?.interactor.selectedItems { items in - guard items.count > 0 else { - return - } - - self?.interactor.maxSelectedItemsCount { maxSelectedItemsCount in - if maxSelectedItemsCount == 1 { - self?.view?.setCanSelectMoreItems(true) - self?.view?.deselectAllItems() - } - } + cellData.onPrepareSelection = { [weak self] in + self?.interactor.prepareSelection { [weak self] selectionState in + self?.adjustViewForSelectionState(selectionState) } } diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift index 35dc83ba..4d5b1031 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift @@ -176,7 +176,7 @@ final class PhotoLibraryView: UIView, UICollectionViewDelegateFlowLayout, ThemeC func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { let cellData = dataSource.item(at: indexPath) - cellData.onShouldSelect?() + cellData.onPrepareSelection?() return canSelectMoreItems && cellData.previewAvailable } diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift index 0c6bf4fa..094efc11 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift @@ -40,7 +40,7 @@ struct PhotoLibraryItemCellData { var previewAvailable = false var onSelect: (() -> ())? - var onShouldSelect: (() -> ())? + var onPrepareSelection: (() -> ())? var onDeselect: (() -> ())? init(image: ImageSource) { From b6f19c85b8adfd3ea8871ba04df33166c33e0453 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Tue, 30 May 2017 15:49:42 +0300 Subject: [PATCH 015/111] Revert default maxSelectedItemsCount in demo --- .../PaparazzoExample/Example/Presenter/ExamplePresenter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index eb9f7a8e..5c83819e 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -43,7 +43,7 @@ final class ExamplePresenter { view?.onShowPhotoLibraryButtonTap = { [weak self] in self?.interactor.photoLibraryItems { items in - self?.router.showPhotoLibrary(selectedItems: items, maxSelectedItemsCount: 1) { module in + self?.router.showPhotoLibrary(selectedItems: items, maxSelectedItemsCount: 5) { module in weak var weakModule = module module.onFinish = { result in weakModule?.dismissModule() From 1156f54b8bfb79200d12d308441343cfdcd4e6f0 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Tue, 30 May 2017 16:13:02 +0300 Subject: [PATCH 016/111] Rename onPrepareSelection to onSelectionPrepare --- .../VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift | 2 +- Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift | 2 +- .../Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift b/Paparazzo/Core/VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift index 01028a01..29406436 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/Presenter/PhotoLibraryPresenter.swift @@ -108,7 +108,7 @@ final class PhotoLibraryPresenter: PhotoLibraryModule { cellData.selected = item.selected - cellData.onPrepareSelection = { [weak self] in + cellData.onSelectionPrepare = { [weak self] in self?.interactor.prepareSelection { [weak self] selectionState in self?.adjustViewForSelectionState(selectionState) } diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift index 4d5b1031..cb220272 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryView.swift @@ -176,7 +176,7 @@ final class PhotoLibraryView: UIView, UICollectionViewDelegateFlowLayout, ThemeC func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { let cellData = dataSource.item(at: indexPath) - cellData.onPrepareSelection?() + cellData.onSelectionPrepare?() return canSelectMoreItems && cellData.previewAvailable } diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift index 094efc11..f00cb380 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryViewInput.swift @@ -40,7 +40,7 @@ struct PhotoLibraryItemCellData { var previewAvailable = false var onSelect: (() -> ())? - var onPrepareSelection: (() -> ())? + var onSelectionPrepare: (() -> ())? var onDeselect: (() -> ())? init(image: ImageSource) { From 589933603eb87485e66338421b3402ff848e1a0c Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Wed, 31 May 2017 12:30:52 +0300 Subject: [PATCH 017/111] Add setMaskVisible --- Paparazzo/Core/PhotoTweakView.swift | 58 ++++++++++++++--- .../View/CroppingPreviewView.swift | 6 +- .../View/ImageCroppingMask.swift | 63 ------------------- .../MaskCropper/View/MaskCropperView.swift | 1 + 4 files changed, 54 insertions(+), 74 deletions(-) delete mode 100644 Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingMask.swift diff --git a/Paparazzo/Core/PhotoTweakView.swift b/Paparazzo/Core/PhotoTweakView.swift index 793dfcce..8a6b9fc7 100644 --- a/Paparazzo/Core/PhotoTweakView.swift +++ b/Paparazzo/Core/PhotoTweakView.swift @@ -10,7 +10,11 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { private let scrollView = PhotoScrollView() private let gridView = GridView() - private let croppingMask = ImageCroppingMask() + + private let topMask = UIView() + private let bottomMask = UIView() + private let leftMask = UIView() + private let rightMask = UIView() // MARK: - State @@ -45,12 +49,22 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { scrollView.clipsToBounds = false scrollView.delegate = self + let maskColor = UIColor.white.withAlphaComponent(0.9) + + topMask.backgroundColor = maskColor + bottomMask.backgroundColor = maskColor + leftMask.backgroundColor = maskColor + rightMask.backgroundColor = maskColor + gridView.isUserInteractionEnabled = false gridView.isHidden = true addSubview(scrollView) addSubview(gridView) - addSubview(croppingMask) + addSubview(topMask) + addSubview(bottomMask) + addSubview(leftMask) + addSubview(rightMask) updateMasks() } @@ -73,6 +87,13 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { // MARK: - PhotoTweakView + func setMaskVisible(_ visible: Bool) { + topMask.isHidden = !visible + bottomMask.isHidden = !visible + leftMask.isHidden = !visible + rightMask.isHidden = !visible + } + var cropAspectRatio = CGFloat(AspectRatio.defaultRatio.widthToHeightRatio()) { didSet { if cropAspectRatio != oldValue { @@ -166,10 +187,6 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { gridView.isHidden = !visible } - func setMaskVisible(_ visible: Bool) { - croppingMask.isHidden = !visible - } - func cropPreviewImage() -> CGImage? { // Hide grid for it to be hidden on preview image @@ -256,8 +273,6 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { gridView.bounds = CGRect(origin: .zero, size: cropSize) gridView.center = center - croppingMask.bounds = frame - originalPoint = convert(scrollView.center, to: self) updateMasks() @@ -265,8 +280,33 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { private func updateMasks(animated: Bool = false) { + let horizontalMaskSize = CGSize( + width: bounds.size.width, + height: (bounds.size.height - cropSize.height) / 2 + ) + + let verticalMaskSize = CGSize( + width: (bounds.size.width - cropSize.width) / 2, + height: bounds.size.height - horizontalMaskSize.height + ) + let animation = { - self.croppingMask.performLayoutUpdate(with: self.cropSize) + self.topMask.frame = CGRect( + origin: CGPoint(x: self.bounds.left, y: self.bounds.top), + size: horizontalMaskSize + ) + self.bottomMask.frame = CGRect( + origin: CGPoint(x: self.bounds.left, y: self.bounds.bottom - horizontalMaskSize.height), + size: horizontalMaskSize + ) + self.leftMask.frame = CGRect( + origin: CGPoint(x: self.bounds.left, y: self.topMask.bottom), + size: verticalMaskSize + ) + self.rightMask.frame = CGRect( + origin: CGPoint(x: self.bounds.right - verticalMaskSize.width, y: self.topMask.bottom), + size: verticalMaskSize + ) } if animated { diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift index c04a464a..695af1fe 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift @@ -17,8 +17,6 @@ final class CroppingPreviewView: UIView { init() { super.init(frame: .zero) - previewView.setMaskVisible(false) - clipsToBounds = true addSubview(previewView) @@ -52,6 +50,10 @@ final class CroppingPreviewView: UIView { var onPreviewImageDidLoad: ((UIImage) -> ())? var onImageDidLoad: (() -> ())? + func setMaskVisible(_ visible: Bool) { + previewView.setMaskVisible(visible) + } + func setImage(_ image: ImageSource, previewImage: ImageSource?, completion: (() -> ())?) { if let previewImage = previewImage { diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingMask.swift b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingMask.swift deleted file mode 100644 index a2983001..00000000 --- a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingMask.swift +++ /dev/null @@ -1,63 +0,0 @@ -final class ImageCroppingMask: UIView { - - private let topMask = UIView() - private let bottomMask = UIView() - private let leftMask = UIView() - private let rightMask = UIView() - - // MARK: - Init - - init() { - super.init(frame: .zero) - - let maskColor = UIColor.white.withAlphaComponent(0.9) - - topMask.backgroundColor = maskColor - bottomMask.backgroundColor = maskColor - leftMask.backgroundColor = maskColor - rightMask.backgroundColor = maskColor - - addSubview(topMask) - addSubview(bottomMask) - addSubview(leftMask) - addSubview(rightMask) - - backgroundColor = .clear - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - CroppingMask - - func performLayoutUpdate(with cropSize: CGSize) { - let horizontalMaskSize = CGSize( - width: bounds.size.width, - height: (bounds.size.height - cropSize.height) / 2 - ) - - let verticalMaskSize = CGSize( - width: (bounds.size.width - cropSize.width) / 2, - height: bounds.size.height - horizontalMaskSize.height - ) - - topMask.frame = CGRect( - origin: CGPoint(x: self.bounds.left, y: self.bounds.top), - size: horizontalMaskSize - ) - bottomMask.frame = CGRect( - origin: CGPoint(x: self.bounds.left, y: self.bounds.bottom - horizontalMaskSize.height), - size: horizontalMaskSize - ) - leftMask.frame = CGRect( - origin: CGPoint(x: self.bounds.left, y: self.topMask.bottom), - size: verticalMaskSize - ) - rightMask.frame = CGRect( - origin: CGPoint(x: self.bounds.right - verticalMaskSize.width, y: self.topMask.bottom), - size: verticalMaskSize - ) - } - -} diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift index 44be055f..a419816c 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift @@ -28,6 +28,7 @@ final class MaskCropperView: UIView, ThemeConfigurable { clipsToBounds = true previewView.setGridVisible(false) + previewView.setMaskVisible(false) addSubview(previewView) addSubview(overlayView) From 51110e46cb58a54544b938650921f96ab86bf9b0 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Thu, 1 Jun 2017 15:30:31 +0300 Subject: [PATCH 018/111] Fix mask cropper aspect ratio --- Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift index a419816c..98e917f9 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperView.swift @@ -11,7 +11,7 @@ final class MaskCropperView: UIView, ThemeConfigurable { // MARK: - Constants - private let aspectRatio = CGFloat(AspectRatio.portrait_3x4.widthToHeightRatio()) + private let aspectRatio = CGFloat(1) private let controlsExtendedHeight = CGFloat(80) // MARK: - Init @@ -29,6 +29,7 @@ final class MaskCropperView: UIView, ThemeConfigurable { previewView.setGridVisible(false) previewView.setMaskVisible(false) + previewView.cropAspectRatio = aspectRatio addSubview(previewView) addSubview(overlayView) From 779e4202413a9dc6e063a9b6a11ded2f45df303c Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Thu, 1 Jun 2017 17:39:48 +0300 Subject: [PATCH 019/111] Fix shutter button enabled state after taking photo --- .../Presenter/MediaPickerPresenter.swift | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 4f854cdd..aceee0dd 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -140,13 +140,18 @@ final class MediaPickerPresenter: MediaPickerModule { self?.cameraModuleInput.takePhoto { photo in + let enableShutterButton = { + self?.view?.setShutterButtonEnabled(true) + self?.view?.setPhotoLibraryButtonEnabled(true) + self?.view?.setContinueButtonEnabled(true) + } + if let photo = photo { - self?.addItems([photo], fromCamera: true) + self?.addItems([photo], fromCamera: true, completion: enableShutterButton) + } else { + enableShutterButton() } - self?.view?.setShutterButtonEnabled(true) - self?.view?.setPhotoLibraryButtonEnabled(true) - self?.view?.setContinueButtonEnabled(true) } } @@ -283,6 +288,7 @@ final class MediaPickerPresenter: MediaPickerModule { if canAddMoreItems { view?.setMode(.camera) view?.scrollToCameraThumbnail(animated: true) + completion?() } else if let lastItem = items.last { view?.selectItem(lastItem) view?.scrollToItemThumbnail(lastItem, animated: true) @@ -297,6 +303,7 @@ final class MediaPickerPresenter: MediaPickerModule { item: lastItem ) } + completion?() } } } @@ -306,8 +313,6 @@ final class MediaPickerPresenter: MediaPickerModule { } onItemsAdd?(items) - - completion?() } private func removeSelectedItem() { From 1d0d108c678f754663c7815419bf4988259ed4f2 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Tue, 6 Jun 2017 11:46:25 +0300 Subject: [PATCH 020/111] Add ability to configure access denied texts on media picker --- .../VIPER/Camera/Module/CameraModuleInput.swift | 4 ++++ .../VIPER/Camera/Presenter/CameraPresenter.swift | 16 ++++++++++++---- .../MediaPicker/Module/MediaPickerModule.swift | 4 ++++ .../Presenter/MediaPickerPresenter.swift | 12 ++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Paparazzo/Core/VIPER/Camera/Module/CameraModuleInput.swift b/Paparazzo/Core/VIPER/Camera/Module/CameraModuleInput.swift index f5159c0a..fdb6bb20 100644 --- a/Paparazzo/Core/VIPER/Camera/Module/CameraModuleInput.swift +++ b/Paparazzo/Core/VIPER/Camera/Module/CameraModuleInput.swift @@ -17,4 +17,8 @@ protocol CameraModuleInput: class { func setPreviewImagesSizeForNewPhotos(_: CGSize) func mainModuleDidAppear(animated: Bool) + + func setAccessDeniedTitle(_: String) + func setAccessDeniedMessage(_: String) + func setAccessDeniedButtonTitle(_: String) } diff --git a/Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift b/Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift index 43f34572..9dd3ade4 100644 --- a/Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift +++ b/Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift @@ -61,14 +61,22 @@ final class CameraPresenter: CameraModuleInput { view?.mainModuleDidAppear(animated: animated) } + func setAccessDeniedTitle(_ title: String) { + view?.setAccessDeniedTitle(title) + } + + func setAccessDeniedMessage(_ message: String) { + view?.setAccessDeniedMessage(message) + } + + func setAccessDeniedButtonTitle(_ title: String) { + view?.setAccessDeniedButtonTitle(title) + } + // MARK: - Private private func setUpView() { - view?.setAccessDeniedTitle("Чтобы фотографировать товар") - view?.setAccessDeniedMessage("Разрешите камере делать фото с помощью приложения Avito") - view?.setAccessDeniedButtonTitle("Разрешить доступ к камере") - view?.onAccessDeniedButtonTap = { if let url = URL(string: UIApplicationOpenSettingsURLString) { UIApplication.shared.openURL(url) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift index 31833e24..446f2ece 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift @@ -12,6 +12,10 @@ public protocol MediaPickerModule: class { func setContinueButtonEnabled(_: Bool) func setContinueButtonVisible(_: Bool) + func setAccessDeniedTitle(_: String) + func setAccessDeniedMessage(_: String) + func setAccessDeniedButtonTitle(_: String) + func setCropMode(_: MediaPickerCropMode) var onItemsAdd: (([MediaPickerItem]) -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index aceee0dd..aca59b74 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -43,6 +43,18 @@ final class MediaPickerPresenter: MediaPickerModule { view?.setContinueButtonVisible(visible) } + public func setAccessDeniedTitle(_ title: String) { + cameraModuleInput.setAccessDeniedTitle(title) + } + + public func setAccessDeniedMessage(_ message: String) { + cameraModuleInput.setAccessDeniedMessage(message) + } + + public func setAccessDeniedButtonTitle(_ title: String) { + cameraModuleInput.setAccessDeniedButtonTitle(title) + } + func setItems(_ items: [MediaPickerItem], selectedItem: MediaPickerItem?) { addItems(items, fromCamera: false) { [weak self] in if let selectedItem = selectedItem { From 8c3b373e6204c6911a7ddb030ad074a639a455f0 Mon Sep 17 00:00:00 2001 From: Vladimir Kaltyrin Date: Tue, 6 Jun 2017 11:51:56 +0300 Subject: [PATCH 021/111] Revert titles in Camera --- Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift b/Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift index 9dd3ade4..15d9cc4f 100644 --- a/Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift +++ b/Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift @@ -77,6 +77,10 @@ final class CameraPresenter: CameraModuleInput { private func setUpView() { + view?.setAccessDeniedTitle("Чтобы фотографировать товар") + view?.setAccessDeniedMessage("Разрешите камере делать фото с помощью приложения Avito") + view?.setAccessDeniedButtonTitle("Разрешить доступ к камере") + view?.onAccessDeniedButtonTap = { if let url = URL(string: UIApplicationOpenSettingsURLString) { UIApplication.shared.openURL(url) From 17e71524ab1642aba604d0d869b20f5f0d346ef3 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Fri, 9 Jun 2017 10:51:36 +0300 Subject: [PATCH 022/111] AI-6110: Recover autoscroll --- .../View/MainView/PhotoPreviewView.swift | 4 +- .../ThumbnailsView/ThumbnailsViewLayout.swift | 136 ++++++++++++++++++ 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewView.swift index 06181690..ca161461 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewView.swift @@ -92,7 +92,7 @@ final class PhotoPreviewView: UIView, UICollectionViewDataSource, UICollectionVi func moveItem(from sourceIndex: Int, to destinationIndex: Int) { guard sourceIndex != destinationIndex else { return } - collectionView.performBatchUpdates() { [weak self] in + collectionView.performBatchUpdates(animated: false, { [weak self] in self?.dataSource.moveItem( from: sourceIndex, to: destinationIndex @@ -102,7 +102,7 @@ final class PhotoPreviewView: UIView, UICollectionViewDataSource, UICollectionVi at: IndexPath(item: sourceIndex, section: 0), to: IndexPath(item: destinationIndex, section: 0) ) - } + }) } func setCameraVisible(_ visible: Bool) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift index caa743fb..13e384f9 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift @@ -124,6 +124,7 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { if let newIndexPath = collectionView.indexPathForItem(at: location), delegate.canMove(to: newIndexPath) { collectionView.moveItem(at: draggingIndexPath, to: newIndexPath) self.draggingIndexPath = newIndexPath + beginScrollIfNeeded() } } @@ -158,6 +159,141 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { self.invalidateLayout() }) } + + // MARK: Handle scrolling to the edges + + private var continuousScrollDirection: direction = .none + + enum direction { + case left + case right + case none + + func scrollValue(_ speedValue: CGFloat, percentage: CGFloat) -> CGFloat { + var value: CGFloat = 0.0 + switch self { + case .left: + value = -speedValue + case .right: + value = speedValue + case .none: + return 0 + } + + let proofedPercentage: CGFloat = max(0, min(percentage, 1.0)) + return value * proofedPercentage + } + } + + + private let triggerInset : CGFloat = 30.0 + + private var scrollSpeedValue: CGFloat = 5.0 + private var displayLink: CADisplayLink? + + private var offsetFromLeft: CGFloat { + return collectionView?.contentOffset.x ?? 0 + } + + private var collectionViewWidth: CGFloat { + return collectionView?.bounds.size.width ?? 0 + } + + private var contentLength: CGFloat { + return collectionView?.contentSize.width ?? 0 + } + + private var draggingViewTopEdge: CGFloat? { + return draggingView.flatMap { $0.frame.minX } + } + + private var draggingViewEndEdge: CGFloat? { + return draggingView.flatMap { $0.frame.maxX } + } + + private func setUpDisplayLink() { + guard self.displayLink == nil else { return } + + let displayLink = CADisplayLink(target: self, selector: #selector(onContinuousScroll)) + displayLink.frameInterval = 1 + displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) + self.displayLink = displayLink + } + + private func invalidateDisplayLink() { + continuousScrollDirection = .none + displayLink?.invalidate() + displayLink = nil + } + + private func beginScrollIfNeeded() { + guard + let draggingViewTopEdge = draggingViewTopEdge, + let draggingViewEndEdge = draggingViewEndEdge + else { return } + + if draggingViewTopEdge <= offsetFromLeft + triggerInset { + continuousScrollDirection = .left + setUpDisplayLink() + } else if draggingViewEndEdge >= offsetFromLeft + collectionViewWidth - triggerInset { + continuousScrollDirection = .right + setUpDisplayLink() + } else { + invalidateDisplayLink() + } + } + + @objc private func onContinuousScroll() { + guard let draggingView = draggingView else { return } + + let percentage = calculateTriggerPercentage() + var scrollRate = continuousScrollDirection.scrollValue(scrollSpeedValue, percentage: percentage) + + let offset = offsetFromLeft + let length = collectionViewWidth + + if contentLength <= length { + return + } + + if offset + scrollRate <= 0 { + scrollRate = -offset + } else if offset + scrollRate >= contentLength - length { + scrollRate = contentLength - length - offset + } + + draggingView.x += scrollRate + + collectionView?.performBatchUpdates({ + self.collectionView?.contentOffset.x += scrollRate + }, completion: nil) + } + + private func calculateTriggerPercentage() -> CGFloat { + guard draggingView != nil else { return 0 } + + let offset = offsetFromLeft + let offsetEnd = offsetFromLeft + collectionViewWidth + + var percentage: CGFloat = 0 + + guard triggerInset != 0 else { + return 0 + } + + if self.continuousScrollDirection == .left { + if let fakeCellEdge = draggingViewTopEdge { + percentage = 1.0 - ((fakeCellEdge - offset) / triggerInset) + } + } else if continuousScrollDirection == .right { + if let draggingViewEdge = draggingViewEndEdge { + percentage = 1.0 - ((offsetEnd - draggingViewEdge) / triggerInset) + } + } + + percentage = min(1, max(0, percentage)) + return percentage + } } protocol MediaRibbonLayoutDelegate: UICollectionViewDelegateFlowLayout { From 14f914cb800eb1f2b395f3af1cf0cec491370123 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Tue, 13 Jun 2017 15:50:33 +0300 Subject: [PATCH 023/111] AI-6123: Fix bug with awkward scroll when reorder --- .../MediaPicker/Presenter/MediaPickerPresenter.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index aca59b74..2aee8c18 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -177,14 +177,14 @@ final class MediaPickerPresenter: MediaPickerModule { view?.onItemSelect = { [weak self] item in self?.interactor.selectItem(item) - self?.adjustViewForSelectedItem(item, animated: true) + self?.adjustViewForSelectedItem(item, animated: true, scrollToSelected: true) } view?.onItemMove = { [weak self] (sourceIndex, destinationIndex) in self?.interactor.moveItem(from: sourceIndex, to: destinationIndex) self?.interactor.selectedItem { item in if let item = item { - self?.adjustViewForSelectedItem(item, animated: true) + self?.adjustViewForSelectedItem(item, animated: true, scrollToSelected: false) } } self?.view?.moveItem(from: sourceIndex, to: destinationIndex) @@ -247,11 +247,13 @@ final class MediaPickerPresenter: MediaPickerModule { } } - private func adjustViewForSelectedItem(_ item: MediaPickerItem, animated: Bool) { + private func adjustViewForSelectedItem(_ item: MediaPickerItem, animated: Bool, scrollToSelected: Bool) { adjustPhotoTitleForItem(item) view?.setMode(.photoPreview(item)) - view?.scrollToItemThumbnail(item, animated: animated) + if scrollToSelected { + view?.scrollToItemThumbnail(item, animated: animated) + } } private func adjustPhotoTitleForItem(_ item: MediaPickerItem) { @@ -280,7 +282,7 @@ final class MediaPickerPresenter: MediaPickerModule { private func selectItem(_ item: MediaPickerItem) { view?.selectItem(item) - adjustViewForSelectedItem(item, animated: false) + adjustViewForSelectedItem(item, animated: false, scrollToSelected: true) } private func selectCamera() { From 618d7bfb3f92798b73ccf85768a07db0c672af12 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Mon, 19 Jun 2017 15:52:25 +0300 Subject: [PATCH 024/111] AI-6191: Fix duplicated photos --- .../View/ThumbnailsView/ThumbnailsViewLayout.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift index 13e384f9..3d47085a 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift @@ -121,7 +121,10 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { view.center = CGPoint(x: location.x + dragOffset.x, y: location.y + dragOffset.y) - if let newIndexPath = collectionView.indexPathForItem(at: location), delegate.canMove(to: newIndexPath) { + if let newIndexPath = collectionView.indexPathForItem(at: location), + delegate.canMove(to: newIndexPath), + draggingIndexPath != newIndexPath { + delegate.moveItem(from: draggingIndexPath, to: newIndexPath) collectionView.moveItem(at: draggingIndexPath, to: newIndexPath) self.draggingIndexPath = newIndexPath beginScrollIfNeeded() @@ -149,10 +152,6 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { }, completion: { _ in cell.isHidden = false - if indexPath != originalIndexPath { - delegate.moveItem(from: originalIndexPath, to: indexPath) - } - dragView.removeFromSuperview() self.draggingIndexPath = nil self.draggingView = nil From 95ed18ca4c1b34714402cf95205219bc3874db30 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Mon, 19 Jun 2017 18:13:03 +0300 Subject: [PATCH 025/111] AI-6188: Fix scrollView zoomScale --- Paparazzo/Core/PhotoTweakView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Paparazzo/Core/PhotoTweakView.swift b/Paparazzo/Core/PhotoTweakView.swift index 8a6b9fc7..5f067871 100644 --- a/Paparazzo/Core/PhotoTweakView.swift +++ b/Paparazzo/Core/PhotoTweakView.swift @@ -240,6 +240,8 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { return } + scrollView.zoomScale = 1 + // scale the image cropSize = CGSize( width: bounds.size.width, From 3af13304f7f16fcb55e5d639c21ffda6c57a1184 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Mon, 19 Jun 2017 18:48:17 +0300 Subject: [PATCH 026/111] AI-6188: Refactor --- Paparazzo/Core/PhotoTweakView.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Paparazzo/Core/PhotoTweakView.swift b/Paparazzo/Core/PhotoTweakView.swift index 5f067871..a6914764 100644 --- a/Paparazzo/Core/PhotoTweakView.swift +++ b/Paparazzo/Core/PhotoTweakView.swift @@ -97,6 +97,7 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { var cropAspectRatio = CGFloat(AspectRatio.defaultRatio.widthToHeightRatio()) { didSet { if cropAspectRatio != oldValue { + resetScale() calculateFrames() } } @@ -106,6 +107,7 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { func setImage(_ image: UIImage) { scrollView.imageView.image = image + resetScale() calculateFrames() notifyAboutCroppingParametersChange() } @@ -240,8 +242,6 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { return } - scrollView.zoomScale = 1 - // scale the image cropSize = CGSize( width: bounds.size.width, @@ -338,6 +338,10 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { scrollView.zoomScale = 1 } + private func resetScale() { + scrollView.zoomScale = 1 + } + private func notifyAboutCroppingParametersChange() { onCroppingParametersChange?(croppingParameters()) } From 4abbf8bec59d5d839bc3c06350522323c1ce0a8a Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Tue, 20 Jun 2017 10:13:21 +0300 Subject: [PATCH 027/111] AI-6188: Refactor after review --- Paparazzo/Core/PhotoTweakView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Paparazzo/Core/PhotoTweakView.swift b/Paparazzo/Core/PhotoTweakView.swift index a6914764..8fafa7e6 100644 --- a/Paparazzo/Core/PhotoTweakView.swift +++ b/Paparazzo/Core/PhotoTweakView.swift @@ -75,7 +75,7 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { override var frame: CGRect { didSet { - reset() + resetScrollViewState() calculateFrames() adjustRotation() } @@ -332,10 +332,10 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { } } - private func reset() { + private func resetScrollViewState() { scrollView.transform = .identity scrollView.minimumZoomScale = 1 - scrollView.zoomScale = 1 + resetScale() } private func resetScale() { From a30262bb2aa0e01dc927fdd22e85102d0cc2159e Mon Sep 17 00:00:00 2001 From: Mikhail Motylev Date: Wed, 28 Jun 2017 10:09:36 +0300 Subject: [PATCH 028/111] AI-6086: Mediapicker rotation fix (back to actual device orientation on pop from mask cropping) --- .../VIPER/MaskCropper/View/MaskCropperViewController.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift index 7d1aaa62..40404aaf 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperViewController.swift @@ -39,6 +39,12 @@ final class MaskCropperViewController: UIApplication.shared.setStatusBarHidden(true, with: .fade) } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + UIViewController.attemptRotationToDeviceOrientation() + } + override var prefersStatusBarHidden: Bool { return true } From bba8ca98bba2502f497f5199d50304c97ca03171 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Mon, 3 Jul 2017 12:06:15 +0300 Subject: [PATCH 029/111] AI-6314: Cancel drag when scroll large preview --- .../MediaPicker/View/ThumbnailsView/ThumbnailsView.swift | 2 ++ .../View/ThumbnailsView/ThumbnailsViewLayout.swift | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsView.swift index 046124db..7eb45385 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsView.swift @@ -82,6 +82,8 @@ final class ThumbnailsView: UIView, UICollectionViewDataSource, MediaRibbonLayou } func selectMediaItem(_ item: MediaPickerItem, animated: Bool = false) { + layout.cancelDrag() + if let indexPath = dataSource.indexPathForItem(item) { collectionView.selectItem(at: indexPath, animated: animated, scrollPosition: []) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift index 3d47085a..b9b59cda 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift @@ -24,6 +24,11 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { setUpGestureRecognizer() } + func cancelDrag() { + longPressGestureRecognizer?.isEnabled = false + longPressGestureRecognizer?.isEnabled = true + } + private func setUpGestureRecognizer() { if let collectionView = collectionView, longPressGestureRecognizer == nil { let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(onLongPress(_:))) @@ -72,6 +77,7 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { case .began: startDragAtLocation(location: location) case .changed: updateDragAtLocation(location: location) case .ended: endDragAtLocation(location: location) + case .cancelled: endDragAtLocation(location: location) default: break } @@ -121,7 +127,7 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { view.center = CGPoint(x: location.x + dragOffset.x, y: location.y + dragOffset.y) - if let newIndexPath = collectionView.indexPathForItem(at: location), + if let newIndexPath = collectionView.indexPathForItem(at: CGPoint(x: location.x, y: collectionView.height/2)), delegate.canMove(to: newIndexPath), draggingIndexPath != newIndexPath { delegate.moveItem(from: draggingIndexPath, to: newIndexPath) From 25daf50db30cc0a45cc667eccf7b50e05c969ff5 Mon Sep 17 00:00:00 2001 From: Mikhail Motylev Date: Mon, 3 Jul 2017 14:40:28 +0300 Subject: [PATCH 030/111] AI-6085: AI-6085: Updating media picker layout after rotation (ipad) --- .../VIPER/MediaPicker/View/MediaPickerViewController.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index 9bc9bc01..1bc1608c 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -98,6 +98,10 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) { super.didRotate(from: fromInterfaceOrientation) + + if shouldAutorotate { + layoutMediaPickerView(interfaceOrientation: interfaceOrientation) + } isBeingRotated = false } From 3411cf9d078b8312632e8e5f0cc53179e59e6749 Mon Sep 17 00:00:00 2001 From: Mikhail Motylev Date: Mon, 3 Jul 2017 17:37:36 +0300 Subject: [PATCH 031/111] AI-6085: AI-6085: Updating media picker layout after rotation (ipad) --- .../View/MediaPickerViewController.swift | 47 +++++-------------- 1 file changed, 13 insertions(+), 34 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index 1bc1608c..d89f3ff7 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -5,7 +5,6 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI typealias ThemeType = MediaPickerRootModuleUITheme - private var isBeingRotated: Bool = false private let mediaPickerView = MediaPickerView() private var layoutSubviewsPromise = Promise() @@ -26,7 +25,7 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - layoutMediaPickerView(interfaceOrientation: interfaceOrientation) + layoutMediaPickerView(bounds: UIScreen.main.bounds) navigationController?.setNavigationBarHidden(true, animated: animated) UIApplication.shared.setStatusBarHidden(true, with: .fade) @@ -77,37 +76,18 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - if !isBeingRotated { - layoutMediaPickerView(interfaceOrientation: interfaceOrientation) - } onPreviewSizeDetermined?(mediaPickerView.previewSize) layoutSubviewsPromise.fulfill() } - override func willAnimateRotation(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) { - if shouldAutorotate { - // Compensation animation for rotation. - UIView.animate( - withDuration: duration, - animations: { - self.layoutMediaPickerView(interfaceOrientation: toInterfaceOrientation) - }) - } - super.willAnimateRotation(to: toInterfaceOrientation, duration: duration) - } - - override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) { - super.didRotate(from: fromInterfaceOrientation) + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - if shouldAutorotate { - layoutMediaPickerView(interfaceOrientation: interfaceOrientation) - } - isBeingRotated = false - } - - override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) { - super.willRotate(to: toInterfaceOrientation, duration: duration) - isBeingRotated = true + coordinator.animate(alongsideTransition: { [weak self] context in + self?.layoutMediaPickerView(bounds: context.containerView.bounds) + }, + completion: nil) + + super.viewWillTransition(to: size, with: coordinator) } override open var shouldAutorotate: Bool { @@ -144,12 +124,12 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI get { return mediaPickerView.onShutterButtonTap } set { mediaPickerView.onShutterButtonTap = newValue } } - + var onPhotoLibraryButtonTap: (() -> ())? { get { return mediaPickerView.onPhotoLibraryButtonTap } set { mediaPickerView.onPhotoLibraryButtonTap = newValue } } - + var onFlashToggle: ((Bool) -> ())? { get { return mediaPickerView.onFlashToggle } set { mediaPickerView.onFlashToggle = newValue } @@ -279,7 +259,7 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI func setCameraToggleButtonVisible(_ visible: Bool) { mediaPickerView.setCameraToggleButtonVisible(visible) } - + func addItems(_ items: [MediaPickerItem], animated: Bool, completion: @escaping () -> ()) { mediaPickerView.addItems(items, animated: animated, completion: completion) } @@ -356,13 +336,12 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI // MARK: - Private - func layoutMediaPickerView(interfaceOrientation: UIInterfaceOrientation) { + func layoutMediaPickerView(bounds: CGRect) { // View is rotated, but mediaPickerView isn't. // It rotates in opposite direction and seems not rotated at all. // This allows to not force status bar orientation on this screen and keep UI same as // with forcing status bar orientation. mediaPickerView.transform = CGAffineTransform(interfaceOrientation: interfaceOrientation) - mediaPickerView.frame = view.bounds + mediaPickerView.frame = bounds } - } From 7951f67cca24235bcbaea01e590f4d6d8dc791e3 Mon Sep 17 00:00:00 2001 From: Mikhail Motylev Date: Tue, 4 Jul 2017 10:51:42 +0300 Subject: [PATCH 032/111] AI-6085: AI-6085: Using view bounds instead of screen bounds --- .../VIPER/MediaPicker/View/MediaPickerViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index d89f3ff7..290a0de7 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -25,10 +25,10 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - layoutMediaPickerView(bounds: UIScreen.main.bounds) - navigationController?.setNavigationBarHidden(true, animated: animated) UIApplication.shared.setStatusBarHidden(true, with: .fade) + + layoutMediaPickerView(bounds: view.bounds) } override func viewWillDisappear(_ animated: Bool) { From 2c1659c02657f6c2805532828707e6029a05ef75 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Tue, 4 Jul 2017 16:51:35 +0300 Subject: [PATCH 033/111] AI-5821: Add accessibilityIDs --- .../View/Controls/CameraControlsView.swift | 14 ++++++++++++++ .../View/Controls/PhotoControlsView.swift | 10 ++++++++++ .../View/MainView/MainCameraCell.swift | 2 ++ .../View/MainView/PhotoPreviewCell.swift | 3 +++ .../VIPER/MediaPicker/View/MediaPickerView.swift | 16 ++++++++++++++-- .../ThumbnailsView/CameraThumbnailCell.swift | 3 +++ .../ThumbnailsView/MediaItemThumbnailCell.swift | 1 + .../ThumbnailsView/ThumbnailsViewLayout.swift | 1 + 8 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift index beb4e5cb..5c3ac65a 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift @@ -80,6 +80,20 @@ final class CameraControlsView: UIView, ThemeConfigurable { addSubview(shutterButton) addSubview(flashButton) addSubview(cameraToggleButton) + + setupAccessibilityIdentifiers() + } + + private func setupAccessibilityIdentifiers() { + photoView.accessibilityIdentifier = "photoView" + shutterButton.accessibilityIdentifier = "shutterButton" + cameraToggleButton.accessibilityIdentifier = "cameraToggleButton" + flashButton.accessibilityIdentifier = "flashButton" + + photoView.isAccessibilityElement = true + shutterButton.isAccessibilityElement = true + cameraToggleButton.isAccessibilityElement = true + flashButton.isAccessibilityElement = true } required init?(coder aDecoder: NSCoder) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift index d1aeadfc..a7b361f0 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift @@ -30,6 +30,16 @@ final class PhotoControlsView: UIView, ThemeConfigurable { addSubview(removeButton) addSubview(cropButton) // в первой итерации не показываем + + setupAccessibilityIdentifiers() + } + + private func setupAccessibilityIdentifiers() { + removeButton.accessibilityIdentifier = "removeButton" + cropButton.accessibilityIdentifier = "cropButton" + + removeButton.isAccessibilityElement = true + cropButton.isAccessibilityElement = true } required init?(coder aDecoder: NSCoder) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/MainCameraCell.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/MainCameraCell.swift index e63fc757..4ebf4408 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/MainCameraCell.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/MainCameraCell.swift @@ -8,6 +8,8 @@ final class MainCameraCell: UICollectionViewCell { if let cameraView = cameraView { addSubview(cameraView) + accessibilityIdentifier = "MainCameraCell" + isAccessibilityElement = true } } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewCell.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewCell.swift index b603b7ad..85b635cc 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewCell.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewCell.swift @@ -52,6 +52,9 @@ final class PhotoPreviewCell: PhotoCollectionViewCell { func customizeWithItem(_ item: MediaPickerItem) { imageSource = item.image + + accessibilityIdentifier = item.identifier + "PhotoPreviewCell" + isAccessibilityElement = true } // MARK: - Private diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index e116dce0..1326bac1 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -10,7 +10,6 @@ final class MediaPickerView: UIView, ThemeConfigurable { private let cameraControlsView = CameraControlsView() private let photoControlsView = PhotoControlsView() - private let photoLibraryPeepholeView = UIImageView() private let closeButton = UIButton() private let continueButton = UIButton() private let photoTitleLabel = UILabel() @@ -72,7 +71,6 @@ final class MediaPickerView: UIView, ThemeConfigurable { photoTitleLabel.layer.masksToBounds = false photoTitleLabel.alpha = 0 - photoLibraryPeepholeView.contentMode = .scaleAspectFill thumbnailRibbonView.onPhotoItemSelect = { [weak self] mediaPickerItem in self?.onItemSelect?(mediaPickerItem) @@ -96,6 +94,8 @@ final class MediaPickerView: UIView, ThemeConfigurable { addSubview(continueButton) setMode(.camera) + + setupAccessibilityIdentifiers() } private func setupButtons() { @@ -118,6 +118,16 @@ final class MediaPickerView: UIView, ThemeConfigurable { ) } + private func setupAccessibilityIdentifiers() { + closeButton.accessibilityIdentifier = "closeButton" + continueButton.accessibilityIdentifier = "continueButton" + photoTitleLabel.accessibilityIdentifier = "photoTitleLabel" + + closeButton.isAccessibilityElement = true + continueButton.isAccessibilityElement = true + photoTitleLabel.isAccessibilityElement = true + } + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -435,6 +445,7 @@ final class MediaPickerView: UIView, ThemeConfigurable { func setPhotoTitle(_ title: String) { photoTitleLabel.text = title + photoTitleLabel.accessibilityValue = title layoutPhotoTitleLabel() } @@ -455,6 +466,7 @@ final class MediaPickerView: UIView, ThemeConfigurable { func setContinueButtonTitle(_ title: String) { continueButton.setTitle(title, for: .normal) + continueButton.accessibilityValue = title continueButton.size = CGSize(width: continueButton.sizeThatFits().width, height: continueButtonHeight) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/CameraThumbnailCell.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/CameraThumbnailCell.swift index e316a9a0..892bb9ec 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/CameraThumbnailCell.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/CameraThumbnailCell.swift @@ -69,6 +69,9 @@ final class CameraThumbnailCell: UICollectionViewCell { adjustBorderColor() addSubview(button) + + isAccessibilityElement = true + accessibilityIdentifier = "CameraThumbnailCell" } required init?(coder aDecoder: NSCoder) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/MediaItemThumbnailCell.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/MediaItemThumbnailCell.swift index 7a3f0a83..f7f06f4d 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/MediaItemThumbnailCell.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/MediaItemThumbnailCell.swift @@ -29,5 +29,6 @@ final class MediaItemThumbnailCell: PhotoCollectionViewCell, Customizable { func customizeWithItem(_ item: MediaPickerItem) { imageSource = item.image + accessibilityIdentifier = item.identifier + "MediaItemThumbnailCell" } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift index b9b59cda..b20ee636 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift @@ -127,6 +127,7 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { view.center = CGPoint(x: location.x + dragOffset.x, y: location.y + dragOffset.y) + // AI-6314: If we pass location inside indexPathForItem it can return nil if location is out of collectionView bounds if let newIndexPath = collectionView.indexPathForItem(at: CGPoint(x: location.x, y: collectionView.height/2)), delegate.canMove(to: newIndexPath), draggingIndexPath != newIndexPath { From cbe6d84c34ce84c46a699b34033a0db5263c0480 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Wed, 5 Jul 2017 12:09:44 +0300 Subject: [PATCH 034/111] AI-5281: Add accessibilityID for elements in MaskCropperControlsView --- .../MaskCropper/View/MaskCropperControlsView.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift index 19bbd3d9..b569a6c3 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift @@ -28,6 +28,16 @@ final class MaskCropperControlsView: UIView, ThemeConfigurable { action: #selector(onConfirmTap(_:)), for: .touchUpInside ) + + setupAccessibilityIdentifiers() + } + + private func setupAccessibilityIdentifiers() { + discardButton.accessibilityIdentifier = "discardButton" + confirmButton.accessibilityIdentifier = "confirmButton" + + discardButton.isAccessibilityElement = true + confirmButton.isAccessibilityElement = true } required init?(coder aDecoder: NSCoder) { From 9881d3afdb1c4e19fecb9f48c55b610acf6a45df Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Wed, 5 Jul 2017 12:13:36 +0300 Subject: [PATCH 035/111] SEL-23: Fix reorder photo bug --- .../ThumbnailsView/ThumbnailsViewLayout.swift | 61 ++++++++++++++----- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift index b9b59cda..492e61f5 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift @@ -119,22 +119,12 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { private func updateDragAtLocation(location: CGPoint) { guard - let view = draggingView, - let collectionView = collectionView, - let draggingIndexPath = draggingIndexPath, - let delegate = collectionView.delegate as? MediaRibbonLayoutDelegate + let view = draggingView else { return } view.center = CGPoint(x: location.x + dragOffset.x, y: location.y + dragOffset.y) - if let newIndexPath = collectionView.indexPathForItem(at: CGPoint(x: location.x, y: collectionView.height/2)), - delegate.canMove(to: newIndexPath), - draggingIndexPath != newIndexPath { - delegate.moveItem(from: draggingIndexPath, to: newIndexPath) - collectionView.moveItem(at: draggingIndexPath, to: newIndexPath) - self.draggingIndexPath = newIndexPath - beginScrollIfNeeded() - } + moveItem(to: location) } private func endDragAtLocation(location: CGPoint) { @@ -165,6 +155,47 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { }) } + private func indexPathForItemClosestTo(point: CGPoint) -> IndexPath? { + guard + let collectionView = collectionView, + let layoutAttributes = collectionView.collectionViewLayout.layoutAttributesForElements(in: collectionView.bounds) + else { return nil } + + var smallestDistance = CGFloat.greatestFiniteMagnitude + var indexPath: IndexPath? + + for attribute in layoutAttributes { + if attribute.frame.contains(point) { + return attribute.indexPath + } + + if smallestDistance > abs(attribute.frame.origin.x - point.x) { + smallestDistance = abs(attribute.frame.x - point.x) + indexPath = attribute.indexPath + } + } + + return indexPath + } + + private func moveItem(to location: CGPoint) { + guard + let collectionView = collectionView, + let draggingIndexPath = draggingIndexPath, + let delegate = collectionView.delegate as? MediaRibbonLayoutDelegate + else { return } + + // AI-6314: If we pass location inside indexPathForItem it can return nil if location is out of collectionView bounds + if let newIndexPath = indexPathForItemClosestTo(point: CGPoint(x: location.x, y: collectionView.height/2)), + delegate.canMove(to: newIndexPath), + draggingIndexPath != newIndexPath { + delegate.moveItem(from: draggingIndexPath, to: newIndexPath) + collectionView.moveItem(at: draggingIndexPath, to: newIndexPath) + self.draggingIndexPath = newIndexPath + beginScrollIfNeeded() + } + } + // MARK: Handle scrolling to the edges private var continuousScrollDirection: direction = .none @@ -268,10 +299,8 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { } draggingView.x += scrollRate - - collectionView?.performBatchUpdates({ - self.collectionView?.contentOffset.x += scrollRate - }, completion: nil) + self.collectionView?.contentOffset.x += scrollRate + moveItem(to: draggingView.center) } private func calculateTriggerPercentage() -> CGFloat { From 84ec964a894fbe78bee4bc5e670477356a2d6f97 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Wed, 5 Jul 2017 13:15:01 +0300 Subject: [PATCH 036/111] SEL-23: Refactor --- .../View/ThumbnailsView/ThumbnailsViewLayout.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift index 492e61f5..ddf00421 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift @@ -169,8 +169,9 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { return attribute.indexPath } - if smallestDistance > abs(attribute.frame.origin.x - point.x) { - smallestDistance = abs(attribute.frame.x - point.x) + let currentDistance = abs(attribute.frame.x - point.x) + if smallestDistance > currentDistance { + smallestDistance = currentDistance indexPath = attribute.indexPath } } From ce3db3581ca03348eb4bb8313ed72f81bfe5152c Mon Sep 17 00:00:00 2001 From: Mikhail Motylev Date: Wed, 5 Jul 2017 14:56:24 +0300 Subject: [PATCH 037/111] AI-6085: AI-6085: Fix mediaPickerView layout on view controller start (mainly for paparazzo demo project) --- .../MediaPicker/View/MediaPickerViewController.swift | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index 290a0de7..887ee065 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -9,16 +9,13 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI private var layoutSubviewsPromise = Promise() // MARK: - UIViewController - - override func loadView() { - view = UIView() - view.backgroundColor = .black - view.addSubview(mediaPickerView) - } - + override func viewDidLoad() { super.viewDidLoad() + automaticallyAdjustsScrollViewInsets = false + view.backgroundColor = .black + view.addSubview(mediaPickerView) onViewDidLoad?() } From 71864a899bfbb1696c228649d941dbe43e101914 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Thu, 6 Jul 2017 11:11:20 +0300 Subject: [PATCH 038/111] AI-5821: Add accesibilityID for elements inside cropping view --- .../View/ImageCroppingControlsView.swift | 15 +++++++++++++++ .../ImageCropping/View/ImageCroppingView.swift | 11 +++++++++++ 2 files changed, 26 insertions(+) diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift index ba9b9ffc..ad0cd952 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift @@ -63,6 +63,21 @@ final class ImageCroppingControlsView: UIView, ThemeConfigurable { addSubview(rotationCancelButton) addSubview(discardButton) addSubview(confirmButton) + setupAccessibilityIdentifiers() + } + + private func setupAccessibilityIdentifiers() { + rotationButton.accessibilityIdentifier = "rotationButton" + gridButton.accessibilityIdentifier = "gridButton" + rotationCancelButton.accessibilityIdentifier = "rotationCancelButton" + discardButton.accessibilityIdentifier = "discardButton" + confirmButton.accessibilityIdentifier = "confirmButton" + + rotationButton.isAccessibilityElement = true + gridButton.isAccessibilityElement = true + rotationCancelButton.isAccessibilityElement = true + discardButton.isAccessibilityElement = true + confirmButton.isAccessibilityElement = true } required init?(coder aDecoder: NSCoder) { diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift index 6ebc4c23..1f6fa805 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift @@ -64,6 +64,16 @@ final class ImageCroppingView: UIView, ThemeConfigurable { addSubview(controlsView) addSubview(titleLabel) addSubview(aspectRatioButton) + + setupAccessibilityIdentifiers() + } + + func setupAccessibilityIdentifiers() { + aspectRatioButton.accessibilityIdentifier = "aspectRationButton" + titleLabel.accessibilityIdentifier = "titleLabel" + + aspectRatioButton.isAccessibilityElement = true + titleLabel.isAccessibilityElement = true } required init?(coder aDecoder: NSCoder) { @@ -224,6 +234,7 @@ final class ImageCroppingView: UIView, ThemeConfigurable { } func setAspectRatioButtonTitle(_ title: String) { + aspectRatioButton.accessibilityValue = title aspectRatioButton.setTitle(title, for: .normal) } From 85c89160eeffd9e373140d19045a4d67ee5c6b29 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Thu, 6 Jul 2017 15:15:56 +0300 Subject: [PATCH 039/111] AI-5281: Add enum for accessibilityIds --- .../Core/Utilities/AccessibilityId.swift | 42 +++++++++++++++++++ .../View/ImageCroppingControlsView.swift | 16 +++---- .../View/ImageCroppingView.swift | 7 +--- .../View/MaskCropperControlsView.swift | 7 +--- .../View/Controls/CameraControlsView.swift | 13 ++---- .../View/Controls/PhotoControlsView.swift | 7 +--- .../View/MainView/MainCameraCell.swift | 3 +- .../View/MainView/PhotoPreviewCell.swift | 4 +- .../MediaPicker/View/MediaPickerView.swift | 10 ++--- .../ThumbnailsView/CameraThumbnailCell.swift | 3 +- .../MediaItemThumbnailCell.swift | 2 +- 11 files changed, 64 insertions(+), 50 deletions(-) create mode 100644 Paparazzo/Core/Utilities/AccessibilityId.swift diff --git a/Paparazzo/Core/Utilities/AccessibilityId.swift b/Paparazzo/Core/Utilities/AccessibilityId.swift new file mode 100644 index 00000000..d50c2891 --- /dev/null +++ b/Paparazzo/Core/Utilities/AccessibilityId.swift @@ -0,0 +1,42 @@ +internal enum AccessibilityId: String { + // CameraControls + case photoView + case shutterButton + case cameraToggleButton + case flashButton + + // Cells + case cameraThumbnailCell + case mainCameraCell + case mediaItemThumbnailCell + case photoPreviewCell + + // ImageCropping + case rotationButton + case gridButton + case rotationCancelButton + case confirmButton + case discardButton + case aspectRatioButton + case titleLabel + + // MediaPicker + case continueButton + case closeButton + + // PhotoControls + case removeButton + case cropButton +} + +extension UIView { + func setAccessibilityId(_ id: AccessibilityId) { + accessibilityIdentifier = id.rawValue + isAccessibilityElement = true + } + + func setAccessibilityId(_ id: String) { + accessibilityIdentifier = id + isAccessibilityElement = true + } +} diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift index ad0cd952..f715714d 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift @@ -67,17 +67,11 @@ final class ImageCroppingControlsView: UIView, ThemeConfigurable { } private func setupAccessibilityIdentifiers() { - rotationButton.accessibilityIdentifier = "rotationButton" - gridButton.accessibilityIdentifier = "gridButton" - rotationCancelButton.accessibilityIdentifier = "rotationCancelButton" - discardButton.accessibilityIdentifier = "discardButton" - confirmButton.accessibilityIdentifier = "confirmButton" - - rotationButton.isAccessibilityElement = true - gridButton.isAccessibilityElement = true - rotationCancelButton.isAccessibilityElement = true - discardButton.isAccessibilityElement = true - confirmButton.isAccessibilityElement = true + rotationButton.setAccessibilityId(.rotationButton) + gridButton.setAccessibilityId(.gridButton) + rotationCancelButton.setAccessibilityId(.rotationCancelButton) + discardButton.setAccessibilityId(.discardButton) + confirmButton.setAccessibilityId(.confirmButton) } required init?(coder aDecoder: NSCoder) { diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift index 1f6fa805..3b7e6f70 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift @@ -69,11 +69,8 @@ final class ImageCroppingView: UIView, ThemeConfigurable { } func setupAccessibilityIdentifiers() { - aspectRatioButton.accessibilityIdentifier = "aspectRationButton" - titleLabel.accessibilityIdentifier = "titleLabel" - - aspectRatioButton.isAccessibilityElement = true - titleLabel.isAccessibilityElement = true + aspectRatioButton.setAccessibilityId(.aspectRatioButton) + titleLabel.setAccessibilityId(.titleLabel) } required init?(coder aDecoder: NSCoder) { diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift index b569a6c3..0c68b8b0 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift @@ -33,11 +33,8 @@ final class MaskCropperControlsView: UIView, ThemeConfigurable { } private func setupAccessibilityIdentifiers() { - discardButton.accessibilityIdentifier = "discardButton" - confirmButton.accessibilityIdentifier = "confirmButton" - - discardButton.isAccessibilityElement = true - confirmButton.isAccessibilityElement = true + discardButton.setAccessibilityId(.discardButton) + confirmButton.setAccessibilityId(.confirmButton) } required init?(coder aDecoder: NSCoder) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift index 5c3ac65a..92c22ace 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift @@ -85,15 +85,10 @@ final class CameraControlsView: UIView, ThemeConfigurable { } private func setupAccessibilityIdentifiers() { - photoView.accessibilityIdentifier = "photoView" - shutterButton.accessibilityIdentifier = "shutterButton" - cameraToggleButton.accessibilityIdentifier = "cameraToggleButton" - flashButton.accessibilityIdentifier = "flashButton" - - photoView.isAccessibilityElement = true - shutterButton.isAccessibilityElement = true - cameraToggleButton.isAccessibilityElement = true - flashButton.isAccessibilityElement = true + photoView.setAccessibilityId(.photoView) + shutterButton.setAccessibilityId(.shutterButton) + cameraToggleButton.setAccessibilityId(.cameraToggleButton) + flashButton.setAccessibilityId(.flashButton) } required init?(coder aDecoder: NSCoder) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift index a7b361f0..4754ec66 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift @@ -35,11 +35,8 @@ final class PhotoControlsView: UIView, ThemeConfigurable { } private func setupAccessibilityIdentifiers() { - removeButton.accessibilityIdentifier = "removeButton" - cropButton.accessibilityIdentifier = "cropButton" - - removeButton.isAccessibilityElement = true - cropButton.isAccessibilityElement = true + removeButton.setAccessibilityId(.removeButton) + cropButton.setAccessibilityId(.cropButton) } required init?(coder aDecoder: NSCoder) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/MainCameraCell.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/MainCameraCell.swift index 4ebf4408..563abc23 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/MainCameraCell.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/MainCameraCell.swift @@ -8,8 +8,7 @@ final class MainCameraCell: UICollectionViewCell { if let cameraView = cameraView { addSubview(cameraView) - accessibilityIdentifier = "MainCameraCell" - isAccessibilityElement = true + setAccessibilityId(.mainCameraCell) } } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewCell.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewCell.swift index 85b635cc..cfc64d05 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewCell.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewCell.swift @@ -52,9 +52,7 @@ final class PhotoPreviewCell: PhotoCollectionViewCell { func customizeWithItem(_ item: MediaPickerItem) { imageSource = item.image - - accessibilityIdentifier = item.identifier + "PhotoPreviewCell" - isAccessibilityElement = true + setAccessibilityId(item.identifier + AccessibilityId.photoPreviewCell.rawValue) } // MARK: - Private diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index 1326bac1..8a9e9851 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -119,13 +119,9 @@ final class MediaPickerView: UIView, ThemeConfigurable { } private func setupAccessibilityIdentifiers() { - closeButton.accessibilityIdentifier = "closeButton" - continueButton.accessibilityIdentifier = "continueButton" - photoTitleLabel.accessibilityIdentifier = "photoTitleLabel" - - closeButton.isAccessibilityElement = true - continueButton.isAccessibilityElement = true - photoTitleLabel.isAccessibilityElement = true + closeButton.setAccessibilityId(.closeButton) + continueButton.setAccessibilityId(.continueButton) + photoTitleLabel.setAccessibilityId(.titleLabel) } required init?(coder aDecoder: NSCoder) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/CameraThumbnailCell.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/CameraThumbnailCell.swift index 892bb9ec..fab5e01e 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/CameraThumbnailCell.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/CameraThumbnailCell.swift @@ -70,8 +70,7 @@ final class CameraThumbnailCell: UICollectionViewCell { addSubview(button) - isAccessibilityElement = true - accessibilityIdentifier = "CameraThumbnailCell" + setAccessibilityId(.cameraThumbnailCell) } required init?(coder aDecoder: NSCoder) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/MediaItemThumbnailCell.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/MediaItemThumbnailCell.swift index f7f06f4d..2cb25282 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/MediaItemThumbnailCell.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/MediaItemThumbnailCell.swift @@ -29,6 +29,6 @@ final class MediaItemThumbnailCell: PhotoCollectionViewCell, Customizable { func customizeWithItem(_ item: MediaPickerItem) { imageSource = item.image - accessibilityIdentifier = item.identifier + "MediaItemThumbnailCell" + setAccessibilityId(item.identifier + AccessibilityId.mediaItemThumbnailCell.rawValue) } } From 4493e2af7aa79a908b7d6e24d8942ce47ef49236 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Thu, 6 Jul 2017 16:09:45 +0300 Subject: [PATCH 040/111] AI-5281: Fix naming and access level for enum --- Paparazzo/Core/Extensions/UIKitExtensions.swift | 5 +++++ Paparazzo/Core/Utilities/AccessibilityId.swift | 14 +------------- .../View/ImageCroppingControlsView.swift | 4 ++-- .../ImageCropping/View/ImageCroppingView.swift | 4 ++-- .../MaskCropper/View/MaskCropperControlsView.swift | 4 ++-- .../View/Controls/CameraControlsView.swift | 4 ++-- .../View/Controls/PhotoControlsView.swift | 4 ++-- .../View/MainView/PhotoPreviewCell.swift | 2 +- .../VIPER/MediaPicker/View/MediaPickerView.swift | 4 ++-- .../ThumbnailsView/MediaItemThumbnailCell.swift | 2 +- 10 files changed, 20 insertions(+), 27 deletions(-) diff --git a/Paparazzo/Core/Extensions/UIKitExtensions.swift b/Paparazzo/Core/Extensions/UIKitExtensions.swift index 372e1d20..2605f8f0 100644 --- a/Paparazzo/Core/Extensions/UIKitExtensions.swift +++ b/Paparazzo/Core/Extensions/UIKitExtensions.swift @@ -123,6 +123,11 @@ extension UIView { } ) } + + func setAccessibilityId(_ id: AccessibilityId) { + accessibilityIdentifier = id.rawValue + isAccessibilityElement = true + } } extension UICollectionView { diff --git a/Paparazzo/Core/Utilities/AccessibilityId.swift b/Paparazzo/Core/Utilities/AccessibilityId.swift index d50c2891..5c8f4205 100644 --- a/Paparazzo/Core/Utilities/AccessibilityId.swift +++ b/Paparazzo/Core/Utilities/AccessibilityId.swift @@ -1,4 +1,4 @@ -internal enum AccessibilityId: String { +public enum AccessibilityId: String { // CameraControls case photoView case shutterButton @@ -28,15 +28,3 @@ internal enum AccessibilityId: String { case removeButton case cropButton } - -extension UIView { - func setAccessibilityId(_ id: AccessibilityId) { - accessibilityIdentifier = id.rawValue - isAccessibilityElement = true - } - - func setAccessibilityId(_ id: String) { - accessibilityIdentifier = id - isAccessibilityElement = true - } -} diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift index f715714d..e3df0469 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingControlsView.swift @@ -63,10 +63,10 @@ final class ImageCroppingControlsView: UIView, ThemeConfigurable { addSubview(rotationCancelButton) addSubview(discardButton) addSubview(confirmButton) - setupAccessibilityIdentifiers() + setUpAccessibilityIdentifiers() } - private func setupAccessibilityIdentifiers() { + private func setUpAccessibilityIdentifiers() { rotationButton.setAccessibilityId(.rotationButton) gridButton.setAccessibilityId(.gridButton) rotationCancelButton.setAccessibilityId(.rotationCancelButton) diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift index 3b7e6f70..b504be72 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/ImageCroppingView.swift @@ -65,10 +65,10 @@ final class ImageCroppingView: UIView, ThemeConfigurable { addSubview(titleLabel) addSubview(aspectRatioButton) - setupAccessibilityIdentifiers() + setUpAccessibilityIdentifiers() } - func setupAccessibilityIdentifiers() { + private func setUpAccessibilityIdentifiers() { aspectRatioButton.setAccessibilityId(.aspectRatioButton) titleLabel.setAccessibilityId(.titleLabel) } diff --git a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift index 0c68b8b0..af95cffd 100644 --- a/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift +++ b/Paparazzo/Core/VIPER/MaskCropper/View/MaskCropperControlsView.swift @@ -29,10 +29,10 @@ final class MaskCropperControlsView: UIView, ThemeConfigurable { for: .touchUpInside ) - setupAccessibilityIdentifiers() + setUpAccessibilityIdentifiers() } - private func setupAccessibilityIdentifiers() { + private func setUpAccessibilityIdentifiers() { discardButton.setAccessibilityId(.discardButton) confirmButton.setAccessibilityId(.confirmButton) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift index 92c22ace..0598c193 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/CameraControlsView.swift @@ -81,10 +81,10 @@ final class CameraControlsView: UIView, ThemeConfigurable { addSubview(flashButton) addSubview(cameraToggleButton) - setupAccessibilityIdentifiers() + setUpAccessibilityIdentifiers() } - private func setupAccessibilityIdentifiers() { + private func setUpAccessibilityIdentifiers() { photoView.setAccessibilityId(.photoView) shutterButton.setAccessibilityId(.shutterButton) cameraToggleButton.setAccessibilityId(.cameraToggleButton) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift index 4754ec66..6b9ec4e2 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift @@ -31,10 +31,10 @@ final class PhotoControlsView: UIView, ThemeConfigurable { addSubview(removeButton) addSubview(cropButton) // в первой итерации не показываем - setupAccessibilityIdentifiers() + setUpAccessibilityIdentifiers() } - private func setupAccessibilityIdentifiers() { + private func setUpAccessibilityIdentifiers() { removeButton.setAccessibilityId(.removeButton) cropButton.setAccessibilityId(.cropButton) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewCell.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewCell.swift index cfc64d05..57567b28 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewCell.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MainView/PhotoPreviewCell.swift @@ -52,7 +52,7 @@ final class PhotoPreviewCell: PhotoCollectionViewCell { func customizeWithItem(_ item: MediaPickerItem) { imageSource = item.image - setAccessibilityId(item.identifier + AccessibilityId.photoPreviewCell.rawValue) + setAccessibilityId(.photoPreviewCell) } // MARK: - Private diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index 8a9e9851..becb3ec9 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -95,7 +95,7 @@ final class MediaPickerView: UIView, ThemeConfigurable { setMode(.camera) - setupAccessibilityIdentifiers() + setUpAccessibilityIdentifiers() } private func setupButtons() { @@ -118,7 +118,7 @@ final class MediaPickerView: UIView, ThemeConfigurable { ) } - private func setupAccessibilityIdentifiers() { + private func setUpAccessibilityIdentifiers() { closeButton.setAccessibilityId(.closeButton) continueButton.setAccessibilityId(.continueButton) photoTitleLabel.setAccessibilityId(.titleLabel) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/MediaItemThumbnailCell.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/MediaItemThumbnailCell.swift index 2cb25282..bc90fec8 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/MediaItemThumbnailCell.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/MediaItemThumbnailCell.swift @@ -29,6 +29,6 @@ final class MediaItemThumbnailCell: PhotoCollectionViewCell, Customizable { func customizeWithItem(_ item: MediaPickerItem) { imageSource = item.image - setAccessibilityId(item.identifier + AccessibilityId.mediaItemThumbnailCell.rawValue) + setAccessibilityId(.mediaItemThumbnailCell) } } From c76aa37c22d9f291f7c8d611e1f06375295df944 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Thu, 6 Jul 2017 17:27:18 +0300 Subject: [PATCH 041/111] AI-6372: Replace scale to nativeScale --- Paparazzo/Core/Helpers/CameraOutputGLKView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Paparazzo/Core/Helpers/CameraOutputGLKView.swift b/Paparazzo/Core/Helpers/CameraOutputGLKView.swift index df962ee3..4bf7f639 100644 --- a/Paparazzo/Core/Helpers/CameraOutputGLKView.swift +++ b/Paparazzo/Core/Helpers/CameraOutputGLKView.swift @@ -56,7 +56,7 @@ final class CameraOutputGLKView: GLKView { private func drawableBounds(for rect: CGRect) -> CGRect { - let screenScale = UIScreen.main.scale + let screenScale = UIScreen.main.nativeScale var drawableBounds = rect drawableBounds.size.width *= screenScale From af3f40debf3021cc486701e88adfeb5d839522b9 Mon Sep 17 00:00:00 2001 From: Mikhail Motylev Date: Fri, 7 Jul 2017 13:49:07 +0300 Subject: [PATCH 042/111] AI-6085: AI-6085: Additional Media picker layout fix for iPhone --- .../View/MediaPickerViewController.swift | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index 887ee065..063299f0 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -7,6 +7,7 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI private let mediaPickerView = MediaPickerView() private var layoutSubviewsPromise = Promise() + private var isAnimatingTransition: Bool = false // MARK: - UIViewController @@ -24,8 +25,6 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI navigationController?.setNavigationBarHidden(true, animated: animated) UIApplication.shared.setStatusBarHidden(true, with: .fade) - - layoutMediaPickerView(bounds: view.bounds) } override func viewWillDisappear(_ animated: Bool) { @@ -73,16 +72,24 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() + if !isAnimatingTransition { + layoutMediaPickerView(bounds: view.bounds) + } + onPreviewSizeDetermined?(mediaPickerView.previewSize) layoutSubviewsPromise.fulfill() } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + isAnimatingTransition = true + coordinator.animate(alongsideTransition: { [weak self] context in self?.layoutMediaPickerView(bounds: context.containerView.bounds) - }, - completion: nil) + }, + completion: { [weak self] _ in + self?.isAnimatingTransition = false + }) super.viewWillTransition(to: size, with: coordinator) } From d6b9a191e7b975bb3e87b2eb433bfe93246d8540 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Fri, 7 Jul 2017 16:45:55 +0300 Subject: [PATCH 043/111] AI-6404: Disconnect camera output after disappear --- .../MediaPicker/Presenter/MediaPickerPresenter.swift | 7 +++++++ .../MediaPicker/View/MediaPickerViewController.swift | 8 ++++++++ .../VIPER/MediaPicker/View/MediaPickerViewInput.swift | 2 ++ 3 files changed, 17 insertions(+) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 2aee8c18..1a7eec2b 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -242,9 +242,16 @@ final class MediaPickerPresenter: MediaPickerModule { self?.cameraModuleInput.setPreviewImagesSizeForNewPhotos(previewSize) } + view?.onViewWillAppear = { [weak self] animated in + self?.cameraModuleInput.setCameraOutputNeeded(true) + } view?.onViewDidAppear = { [weak self] animated in self?.cameraModuleInput.mainModuleDidAppear(animated: animated) } + + view?.onViewDidDisappear = { [weak self] animated in + self?.cameraModuleInput.setCameraOutputNeeded(false) + } } private func adjustViewForSelectedItem(_ item: MediaPickerItem, animated: Bool, scrollToSelected: Bool) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index 887ee065..b307e6c7 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -26,6 +26,7 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI UIApplication.shared.setStatusBarHidden(true, with: .fade) layoutMediaPickerView(bounds: view.bounds) + onViewWillAppear?(animated) } override func viewWillDisappear(_ animated: Bool) { @@ -38,6 +39,11 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI } } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + onViewDidDisappear?(animated) + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -173,7 +179,9 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI } var onViewDidLoad: (() -> ())? + var onViewWillAppear: ((_ animated: Bool) -> ())? var onViewDidAppear: ((_ animated: Bool) -> ())? + var onViewDidDisappear: ((_ animated: Bool) -> ())? var onPreviewSizeDetermined: ((_ previewSize: CGSize) -> ())? func setMode(_ mode: MediaPickerViewMode) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift index 429757c2..df367ad9 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift @@ -76,6 +76,8 @@ protocol MediaPickerViewInput: class { var onViewDidLoad: (() -> ())? { get set } var onViewDidAppear: ((_ animated: Bool) -> ())? { get set } + var onViewWillAppear: ((_ animated: Bool) -> ())? { get set } + var onViewDidDisappear: ((_ animated: Bool) -> ())? { get set } var onPreviewSizeDetermined: ((_ previewSize: CGSize) -> ())? { get set } } From 3993d429dfdf56302a582568156ac8c63941b4f2 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Fri, 7 Jul 2017 17:08:32 +0300 Subject: [PATCH 044/111] AI-6218: Fix scroll issues --- .../MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift index ddf00421..f300f09a 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift @@ -193,8 +193,8 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { delegate.moveItem(from: draggingIndexPath, to: newIndexPath) collectionView.moveItem(at: draggingIndexPath, to: newIndexPath) self.draggingIndexPath = newIndexPath - beginScrollIfNeeded() } + beginScrollIfNeeded() } // MARK: Handle scrolling to the edges From 8bc166db6505ba4cfa7262e32cc73d334ffc895a Mon Sep 17 00:00:00 2001 From: Mikhail Motylev Date: Tue, 11 Jul 2017 10:40:03 +0300 Subject: [PATCH 045/111] AI-6097: AI-6097: Decreasing memory spike during image crop action --- Paparazzo/Core/Extensions/UIKitExtensions.swift | 11 ++++++++--- Paparazzo/Core/PhotoTweakView.swift | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Paparazzo/Core/Extensions/UIKitExtensions.swift b/Paparazzo/Core/Extensions/UIKitExtensions.swift index 2605f8f0..a3efecd9 100644 --- a/Paparazzo/Core/Extensions/UIKitExtensions.swift +++ b/Paparazzo/Core/Extensions/UIKitExtensions.swift @@ -103,9 +103,14 @@ extension UIView { layer.anchorPoint = anchorPoint } - func snapshot() -> UIImage? { - UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0) - drawHierarchy(in: bounds, afterScreenUpdates: true) + func snapshot(withScale scale: CGFloat = 0) -> UIImage? { + + UIGraphicsBeginImageContextWithOptions(bounds.size, false, scale) + + if let context = UIGraphicsGetCurrentContext() { + self.layer.render(in: context) + } + let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image diff --git a/Paparazzo/Core/PhotoTweakView.swift b/Paparazzo/Core/PhotoTweakView.swift index 8fafa7e6..d88e4ec6 100644 --- a/Paparazzo/Core/PhotoTweakView.swift +++ b/Paparazzo/Core/PhotoTweakView.swift @@ -195,7 +195,7 @@ final class PhotoTweakView: UIView, UIScrollViewDelegate { let gridWasHidden = gridView.isHidden gridView.isHidden = true - let previewImage = snapshot().flatMap { snapshot -> CGImage? in + let previewImage = snapshot(withScale: 1).flatMap { snapshot -> CGImage? in let cropRect = CGRect( x: (bounds.left + (bounds.size.width - cropSize.width) / 2) * snapshot.scale, From 2cfed76fe766a3ccfe52b3a8ecc99eebe83046d0 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Tue, 11 Jul 2017 11:06:22 +0300 Subject: [PATCH 046/111] SEL-23: Disable user interaction while drag --- .../MediaPicker/View/MediaPickerView.swift | 6 ++++++ .../View/ThumbnailsView/ThumbnailsView.swift | 18 ++++++++++++++++++ .../ThumbnailsView/ThumbnailsViewLayout.swift | 7 +++++++ 3 files changed, 31 insertions(+) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index becb3ec9..70dc9dc7 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -185,6 +185,12 @@ final class MediaPickerView: UIView, ThemeConfigurable { bottom: cameraControlsView.top, height: photoRibbonHeight ) + thumbnailRibbonView.onDragStart = { [weak self] in + self?.isUserInteractionEnabled = false + } + thumbnailRibbonView.onDragFinish = { [weak self] in + self?.isUserInteractionEnabled = true + } layoutCloseAndContinueButtons() layoutPhotoTitleLabel() diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsView.swift index 7eb45385..5905d761 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsView.swift @@ -4,6 +4,24 @@ import UIKit final class ThumbnailsView: UIView, UICollectionViewDataSource, MediaRibbonLayoutDelegate, ThemeConfigurable { typealias ThemeType = MediaPickerRootModuleUITheme + + var onDragStart: (() -> ())? { + get { + return layout.onDragStart + } + set { + layout.onDragStart = newValue + } + } + + var onDragFinish: (() -> ())? { + get { + return layout.onDragFinish + } + set { + layout.onDragFinish = newValue + } + } private let layout: ThumbnailsViewLayout private let collectionView: UICollectionView diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift index f300f09a..3a12b711 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift @@ -10,6 +10,9 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { private var draggingView: UIView? private var dragOffset = CGPoint.zero + var onDragStart: (() -> ())? + var onDragFinish: (() -> ())? + override init() { super.init() } @@ -114,6 +117,8 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { }, completion: nil ) + + onDragStart?() } } @@ -153,6 +158,8 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { self.draggingView = nil self.invalidateLayout() }) + + onDragFinish?() } private func indexPathForItemClosestTo(point: CGPoint) -> IndexPath? { From 6127456801494e8614834d155960a8c6a59710cc Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Tue, 11 Jul 2017 11:34:37 +0300 Subject: [PATCH 047/111] SEL-23: Move onDragFinish inside animation completion block --- .../View/ThumbnailsView/ThumbnailsViewLayout.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift index 3a12b711..d552d266 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/ThumbnailsView/ThumbnailsViewLayout.swift @@ -157,9 +157,9 @@ final class ThumbnailsViewLayout: UICollectionViewFlowLayout { self.draggingIndexPath = nil self.draggingView = nil self.invalidateLayout() + self.onDragFinish?() + }) - - onDragFinish?() } private func indexPathForItemClosestTo(point: CGPoint) -> IndexPath? { From 4cf735f7e67893c2928c847c5adfae85f0bdf3bd Mon Sep 17 00:00:00 2001 From: Mikhail Motylev Date: Wed, 12 Jul 2017 12:26:17 +0300 Subject: [PATCH 048/111] AI-6021: AI-6021: Crop callbacks have been added --- .../VIPER/MediaPicker/Module/MediaPickerModule.swift | 2 ++ .../MediaPicker/Presenter/MediaPickerPresenter.swift | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift index 446f2ece..acaf148f 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift @@ -21,6 +21,8 @@ public protocol MediaPickerModule: class { var onItemsAdd: (([MediaPickerItem]) -> ())? { get set } var onItemUpdate: ((MediaPickerItem) -> ())? { get set } var onItemRemove: ((MediaPickerItem) -> ())? { get set } + var onCropFinish: (() -> ())? { get set } + var onCropCancel: (() -> ())? { get set } var onFinish: (([MediaPickerItem]) -> ())? { get set } var onCancel: (() -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 1a7eec2b..22046cb7 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -27,6 +27,8 @@ final class MediaPickerPresenter: MediaPickerModule { var onItemsAdd: (([MediaPickerItem]) -> ())? var onItemUpdate: ((MediaPickerItem) -> ())? var onItemRemove: ((MediaPickerItem) -> ())? + var onCropFinish: (() -> ())? + var onCropCancel: (() -> ())? var onFinish: (([MediaPickerItem]) -> ())? var onCancel: (() -> ())? @@ -371,11 +373,15 @@ final class MediaPickerPresenter: MediaPickerModule { croppingOverlayProvider: croppingOverlayProvider) { module in module.onDiscard = { + + self?.onCropCancel?() self?.removeSelectedItem() module.dismissModule() } module.onConfirm = { image in + + self?.onCropFinish?() let croppedItem = MediaPickerItem( identifier: item.identifier, image: image, @@ -425,11 +431,14 @@ final class MediaPickerPresenter: MediaPickerModule { self?.router.showCroppingModule(forImage: item.image, canvasSize: cropCanvasSize) { module in module.onDiscard = { [weak self] in + + self?.onCropCancel?() self?.router.focusOnCurrentModule() } module.onConfirm = { [weak self] croppedImageSource in + self?.onCropFinish?() let croppedItem = MediaPickerItem( identifier: item.identifier, image: croppedImageSource, From d0f7eb77e6984645193501637497a8a5d0b87685 Mon Sep 17 00:00:00 2001 From: Mikhail Motylev Date: Mon, 17 Jul 2017 15:27:50 +0300 Subject: [PATCH 049/111] AI-6097: AI-6097: Retain cycle fix --- .../VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 22046cb7..5f5c7947 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -372,11 +372,11 @@ final class MediaPickerPresenter: MediaPickerModule { data: data, croppingOverlayProvider: croppingOverlayProvider) { module in - module.onDiscard = { + module.onDiscard = { [weak module] in self?.onCropCancel?() self?.removeSelectedItem() - module.dismissModule() + module?.dismissModule() } module.onConfirm = { image in From 6b6d0b54be1d69b9f44b68e44baf8fa1669c7eb9 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Mon, 31 Jul 2017 18:51:30 +0300 Subject: [PATCH 050/111] SEL-197: Add activity support on the continue button --- .../Example/Presenter/ExamplePresenter.swift | 7 ++ .../Module/MediaPickerModule.swift | 9 +++ .../Presenter/MediaPickerPresenter.swift | 22 +++++- .../View/Controls/ButtonWithActivity.swift | 71 +++++++++++++++++++ .../MediaPicker/View/MediaPickerView.swift | 16 ++++- .../View/MediaPickerViewController.swift | 4 ++ .../View/MediaPickerViewInput.swift | 3 +- 7 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 Paparazzo/Core/VIPER/MediaPicker/View/Controls/ButtonWithActivity.swift diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index 5c83819e..f97bf754 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -142,6 +142,13 @@ final class ExamplePresenter { module?.dismissModule() } + module.onContinueButtonTap = { [weak module] in + module?.setContinueButtonStyle(.spinner) + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(3.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: { + module?.finish() + }) + } + module.onFinish = { [weak module] items in debugPrint("media picker did finish with \(items.count) items:") items.forEach { debugPrint($0) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift index acaf148f..79495d19 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift @@ -3,14 +3,22 @@ public enum MediaPickerCropMode { case custom(CroppingOverlayProvider) } +public enum MediaPickerContinueButtonStyle { + case normal + case spinner +} + public protocol MediaPickerModule: class { func focusOnModule() func dismissModule() + func finish() + func setContinueButtonTitle(_: String) func setContinueButtonEnabled(_: Bool) func setContinueButtonVisible(_: Bool) + func setContinueButtonStyle(_: MediaPickerContinueButtonStyle) func setAccessDeniedTitle(_: String) func setAccessDeniedMessage(_: String) @@ -23,6 +31,7 @@ public protocol MediaPickerModule: class { var onItemRemove: ((MediaPickerItem) -> ())? { get set } var onCropFinish: (() -> ())? { get set } var onCropCancel: (() -> ())? { get set } + var onContinueButtonTap: (() -> ())? { get set } var onFinish: (([MediaPickerItem]) -> ())? { get set } var onCancel: (() -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 5f5c7947..4cd42560 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -29,6 +29,7 @@ final class MediaPickerPresenter: MediaPickerModule { var onItemRemove: ((MediaPickerItem) -> ())? var onCropFinish: (() -> ())? var onCropCancel: (() -> ())? + var onContinueButtonTap: (() -> ())? var onFinish: (([MediaPickerItem]) -> ())? var onCancel: (() -> ())? @@ -45,6 +46,10 @@ final class MediaPickerPresenter: MediaPickerModule { view?.setContinueButtonVisible(visible) } + func setContinueButtonStyle(_ style: MediaPickerContinueButtonStyle) { + view?.setContinueButtonStyle(style) + } + public func setAccessDeniedTitle(_ title: String) { cameraModuleInput.setAccessDeniedTitle(title) } @@ -82,6 +87,13 @@ final class MediaPickerPresenter: MediaPickerModule { func dismissModule() { router.dismissCurrentModule() } + + func finish() { + cameraModuleInput.setFlashEnabled(false, completion: nil) + interactor.items { [weak self] items, _ in + self?.onFinish?(items) + } + } // MARK: - Private @@ -222,9 +234,13 @@ final class MediaPickerPresenter: MediaPickerModule { } view?.onContinueButtonTap = { [weak self] in - self?.cameraModuleInput.setFlashEnabled(false, completion: nil) - self?.interactor.items { items, _ in - self?.onFinish?(items) + if let onContinueButtonTap = self?.onContinueButtonTap { + onContinueButtonTap() + } else { + self?.cameraModuleInput.setFlashEnabled(false, completion: nil) + self?.interactor.items { items, _ in + self?.onFinish?(items) + } } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/ButtonWithActivity.swift b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/ButtonWithActivity.swift new file mode 100644 index 00000000..7d929130 --- /dev/null +++ b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/ButtonWithActivity.swift @@ -0,0 +1,71 @@ +import UIKit + +final class ButtonWithActivity: UIButton { + + // MARK: - Subviews + private let activity = UIActivityIndicatorView(activityIndicatorStyle: .gray) + + // MARK: - State + private var cachedTitle: String? = nil + + var style: MediaPickerContinueButtonStyle = .normal { + didSet { + switch style { + + case .normal: + activity.stopAnimating() + super.setTitle(cachedTitle, for: .normal) + isUserInteractionEnabled = true + + case .spinner: + activity.startAnimating() + cachedTitle = title(for: .normal) + super.setTitle(nil, for: .normal) + isUserInteractionEnabled = false + } + } + } + + + // MARK: - Init + init() { + super.init(frame: .zero) + + addSubview(activity) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Title + override func setTitle(_ title: String?, for state: UIControlState) { + switch style { + case .normal: + super.setTitle(title, for: state) + case .spinner: + if state == .normal { + cachedTitle = title + } else { + super.setTitle(title, for: state) + } + } + + } + + // MARK: - Layout + override func sizeThatFits(_ size: CGSize) -> CGSize { + switch style { + case .normal: + return super.sizeThatFits(size) + case .spinner: + return size.intersectionWidth(self.height) + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + activity.center = CGPoint(x: bounds.midX, y: bounds.midY) + } +} diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index 70dc9dc7..1467212a 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -11,7 +11,7 @@ final class MediaPickerView: UIView, ThemeConfigurable { private let photoControlsView = PhotoControlsView() private let closeButton = UIButton() - private let continueButton = UIButton() + private let continueButton = ButtonWithActivity() private let photoTitleLabel = UILabel() private let flashView = UIView() @@ -377,6 +377,20 @@ final class MediaPickerView: UIView, ThemeConfigurable { continueButton.isHidden = !visible } + func setContinueButtonStyle(_ style: MediaPickerContinueButtonStyle) { + UIView.animate( + withDuration: 0.3, + animations: { + self.continueButton.style = style + self.continueButton.size = CGSize( + width: self.continueButton.sizeThatFits().width, + height: self.continueButtonHeight + ) + self.layoutCloseAndContinueButtons() + } + ) + } + func addItems(_ items: [MediaPickerItem], animated: Bool, completion: @escaping () -> ()) { photoPreviewView.addItems(items) thumbnailRibbonView.addItems(items, animated: animated, completion: completion) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index 0dd0f743..0e526a4b 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -232,6 +232,10 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI mediaPickerView.setContinueButtonVisible(visible) } + func setContinueButtonStyle(_ style: MediaPickerContinueButtonStyle) { + mediaPickerView.setContinueButtonStyle(style) + } + func adjustForDeviceOrientation(_ orientation: DeviceOrientation) { UIView.animate(withDuration: 0.25) { self.mediaPickerView.adjustForDeviceOrientation(orientation) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift index df367ad9..7158cf72 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift @@ -24,7 +24,8 @@ protocol MediaPickerViewInput: class { func setPhotoTitleAlpha(_: CGFloat) func setContinueButtonTitle(_: String) func setContinueButtonEnabled(_: Bool) - + func setContinueButtonStyle(_ style: MediaPickerContinueButtonStyle) + func setLatestLibraryPhoto(_: ImageSource?) func setFlashButtonVisible(_: Bool) From 50795f273153a648af8f0456fd11794d992cdddd Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 1 Aug 2017 11:36:01 +0300 Subject: [PATCH 051/111] SEL-197: Improve codestyle --- .../Example/Presenter/ExamplePresenter.swift | 4 ++-- .../VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift | 5 +---- Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift | 2 ++ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index f97bf754..4cbd84ba 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -144,9 +144,9 @@ final class ExamplePresenter { module.onContinueButtonTap = { [weak module] in module?.setContinueButtonStyle(.spinner) - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(3.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: { + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { module?.finish() - }) + } } module.onFinish = { [weak module] items in diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 4cd42560..74a41453 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -237,10 +237,7 @@ final class MediaPickerPresenter: MediaPickerModule { if let onContinueButtonTap = self?.onContinueButtonTap { onContinueButtonTap() } else { - self?.cameraModuleInput.setFlashEnabled(false, completion: nil) - self?.interactor.items { items, _ in - self?.onFinish?(items) - } + self?.finish() } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index 1467212a..1f2462bb 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -378,6 +378,8 @@ final class MediaPickerView: UIView, ThemeConfigurable { } func setContinueButtonStyle(_ style: MediaPickerContinueButtonStyle) { + guard continueButton.style != style else { return } + UIView.animate( withDuration: 0.3, animations: { From cce89170e92c4f57a0d817f3f57de3f959bf3ecf Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Wed, 9 Aug 2017 18:32:26 +0300 Subject: [PATCH 052/111] SEL-222: Add indexes in module callbacks --- .../Interactor/MediaPickerInteractor.swift | 10 ++++- .../MediaPickerInteractorImpl.swift | 21 ++++++---- .../Module/MediaPickerModule.swift | 6 +-- .../Presenter/MediaPickerPresenter.swift | 40 +++++++++++++------ 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift index 69bf38ec..30496ca6 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift @@ -2,8 +2,14 @@ import ImageSource protocol MediaPickerInteractor: class { - func addItems(_: [MediaPickerItem], completion: @escaping (_ addedItems: [MediaPickerItem], _ canAddItems: Bool) -> ()) - func addPhotoLibraryItems(_: [PhotoLibraryItem], completion: @escaping (_ addedItems: [MediaPickerItem], _ canAddItems: Bool) -> ()) + func addItems( + _ items: [MediaPickerItem], + completion: @escaping (_ addedItems: [MediaPickerItem], _ canAddItems: Bool, _ startIndex: Int) + -> ()) + func addPhotoLibraryItems( + _: [PhotoLibraryItem], + completion: @escaping (_ addedItems: [MediaPickerItem], _ canAddItems: Bool, _ startIndex: Int) + -> ()) func updateItem(_: MediaPickerItem, completion: @escaping () -> ()) // `completion` вызывается с соседним item'ом — это item, который нужно выделить после того, как удалили `item` diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 08993e8b..403ff32d 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -48,16 +48,23 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { latestLibraryPhotoProvider.observePhoto(handler: handler) } - func addItems(_ items: [MediaPickerItem], completion: @escaping (_ addedItems: [MediaPickerItem], _ canAddItems: Bool) -> ()) { - + func addItems( + _ items: [MediaPickerItem], + completion: @escaping (_ addedItems: [MediaPickerItem], _ canAddItems: Bool, _ startIndex: Int) + -> ()) + { let numberOfItemsToAdd = min(items.count, maxItemsCount.flatMap { $0 - self.items.count } ?? Int.max) let itemsToAdd = items[0.. ()) { + func addPhotoLibraryItems( + _: [PhotoLibraryItem], + completion: @escaping (_ addedItems: [MediaPickerItem], _ canAddItems: Bool, _ startIndex: Int) + -> ()) + { let mediaPickerItems = photoLibraryItems.map { MediaPickerItem( @@ -68,8 +75,8 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { self.photoLibraryItems.append(contentsOf: photoLibraryItems) - addItems(mediaPickerItems) { addedItems, canAddMoreItems in - completion(addedItems, canAddMoreItems) + addItems(mediaPickerItems) { addedItems, canAddMoreItems, startIndex in + completion(addedItems, canAddMoreItems, startIndex) } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift index 79495d19..95093732 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift @@ -26,9 +26,9 @@ public protocol MediaPickerModule: class { func setCropMode(_: MediaPickerCropMode) - var onItemsAdd: (([MediaPickerItem]) -> ())? { get set } - var onItemUpdate: ((MediaPickerItem) -> ())? { get set } - var onItemRemove: ((MediaPickerItem) -> ())? { get set } + var onItemsAdd: (([MediaPickerItem], Int) -> ())? { get set } + var onItemUpdate: ((MediaPickerItem, Int?) -> ())? { get set } + var onItemRemove: ((MediaPickerItem, Int?) -> ())? { get set } var onCropFinish: (() -> ())? { get set } var onCropCancel: (() -> ())? { get set } var onContinueButtonTap: (() -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 74a41453..5ee8bdf6 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -24,9 +24,9 @@ final class MediaPickerPresenter: MediaPickerModule { // MARK: - MediaPickerModule - var onItemsAdd: (([MediaPickerItem]) -> ())? - var onItemUpdate: ((MediaPickerItem) -> ())? - var onItemRemove: ((MediaPickerItem) -> ())? + var onItemsAdd: (([MediaPickerItem], Int) -> ())? + var onItemUpdate: ((MediaPickerItem, Int?) -> ())? + var onItemRemove: ((MediaPickerItem, Int?) -> ())? var onCropFinish: (() -> ())? var onCropCancel: (() -> ())? var onContinueButtonTap: (() -> ())? @@ -297,8 +297,14 @@ final class MediaPickerPresenter: MediaPickerModule { } private func addItems(_ items: [MediaPickerItem], fromCamera: Bool, completion: (() -> ())? = nil) { - interactor.addItems(items) { [weak self] addedItems, canAddItems in - self?.handleItemsAdded(addedItems, fromCamera: fromCamera, canAddMoreItems: canAddItems, completion: completion) + interactor.addItems(items) { [weak self] addedItems, canAddItems, startIndex in + self?.handleItemsAdded( + addedItems, + fromCamera: fromCamera, + canAddMoreItems: canAddItems, + startIndex: startIndex, + completion: completion + ) } } @@ -313,7 +319,13 @@ final class MediaPickerPresenter: MediaPickerModule { view?.scrollToCameraThumbnail(animated: false) } - private func handleItemsAdded(_ items: [MediaPickerItem], fromCamera: Bool, canAddMoreItems: Bool, completion: (() -> ())? = nil) { + private func handleItemsAdded( + _ items: [MediaPickerItem], + fromCamera: Bool, + canAddMoreItems: Bool, + startIndex: Int, + completion: (() -> ())? = nil) + { guard items.count > 0 else { completion?(); return } @@ -348,7 +360,7 @@ final class MediaPickerPresenter: MediaPickerModule { self?.setTitleForPhotoWithIndex(items.count - 1) } - onItemsAdd?(items) + onItemsAdd?(items, startIndex) } private func removeSelectedItem() { @@ -368,7 +380,9 @@ final class MediaPickerPresenter: MediaPickerModule { self?.view?.setPhotoTitleAlpha(0) } - self?.onItemRemove?(item) + self?.interactor.indexOfItem(item) { index in + self?.onItemRemove?(item, index) + } } } } @@ -425,8 +439,8 @@ final class MediaPickerPresenter: MediaPickerModule { switch result { case .selectedItems(let photoLibraryItems): - self?.interactor.addPhotoLibraryItems(photoLibraryItems) { addedItems, canAddItems in - self?.handleItemsAdded(addedItems, fromCamera: false, canAddMoreItems: canAddItems) + self?.interactor.addPhotoLibraryItems(photoLibraryItems) { addedItems, canAddItems, startIndex in + self?.handleItemsAdded(addedItems, fromCamera: false, canAddMoreItems: canAddItems, startIndex: startIndex) } case .cancelled: break @@ -461,8 +475,10 @@ final class MediaPickerPresenter: MediaPickerModule { self?.interactor.updateItem(croppedItem) { self?.view?.updateItem(croppedItem) self?.adjustPhotoTitleForItem(croppedItem) - self?.onItemUpdate?(croppedItem) - self?.router.focusOnCurrentModule() + self?.interactor.indexOfItem(item) { index in + self?.onItemUpdate?(croppedItem, index) + self?.router.focusOnCurrentModule() + } } } } From f2f4ba48bf34362509f5542dde2838742748824f Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Wed, 9 Aug 2017 19:02:31 +0300 Subject: [PATCH 053/111] SEL-222: Add reorder callback --- Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift | 1 + .../Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift index 95093732..3391b714 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift @@ -28,6 +28,7 @@ public protocol MediaPickerModule: class { var onItemsAdd: (([MediaPickerItem], Int) -> ())? { get set } var onItemUpdate: ((MediaPickerItem, Int?) -> ())? { get set } + var onItemMove: ((Int, Int) -> ())? { get set } var onItemRemove: ((MediaPickerItem, Int?) -> ())? { get set } var onCropFinish: (() -> ())? { get set } var onCropCancel: (() -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 5ee8bdf6..757b7b6c 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -26,6 +26,7 @@ final class MediaPickerPresenter: MediaPickerModule { var onItemsAdd: (([MediaPickerItem], Int) -> ())? var onItemUpdate: ((MediaPickerItem, Int?) -> ())? + var onItemMove: ((Int, Int) -> ())? var onItemRemove: ((MediaPickerItem, Int?) -> ())? var onCropFinish: (() -> ())? var onCropCancel: (() -> ())? @@ -196,6 +197,7 @@ final class MediaPickerPresenter: MediaPickerModule { view?.onItemMove = { [weak self] (sourceIndex, destinationIndex) in self?.interactor.moveItem(from: sourceIndex, to: destinationIndex) + self?.onItemMove?(sourceIndex, destinationIndex) self?.interactor.selectedItem { item in if let item = item { self?.adjustViewForSelectedItem(item, animated: true, scrollToSelected: false) From 8893328105d0a2f2f03f84cb9d8f3be9d7521376 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Wed, 9 Aug 2017 19:35:49 +0300 Subject: [PATCH 054/111] SEL-222: Add names for callback touples --- .../Core/VIPER/MediaPicker/Module/MediaPickerModule.swift | 8 ++++---- .../MediaPicker/Presenter/MediaPickerPresenter.swift | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift index 3391b714..ffb5d428 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift @@ -26,10 +26,10 @@ public protocol MediaPickerModule: class { func setCropMode(_: MediaPickerCropMode) - var onItemsAdd: (([MediaPickerItem], Int) -> ())? { get set } - var onItemUpdate: ((MediaPickerItem, Int?) -> ())? { get set } - var onItemMove: ((Int, Int) -> ())? { get set } - var onItemRemove: ((MediaPickerItem, Int?) -> ())? { get set } + var onItemsAdd: (([MediaPickerItem], _ index: Int) -> ())? { get set } + var onItemUpdate: ((MediaPickerItem, _ index: Int?) -> ())? { get set } + var onItemMove: ((_ sourceIndex: Int, _ destinationIndex: Int) -> ())? { get set } + var onItemRemove: ((MediaPickerItem, _ index: Int?) -> ())? { get set } var onCropFinish: (() -> ())? { get set } var onCropCancel: (() -> ())? { get set } var onContinueButtonTap: (() -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 757b7b6c..6d33817a 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -24,10 +24,10 @@ final class MediaPickerPresenter: MediaPickerModule { // MARK: - MediaPickerModule - var onItemsAdd: (([MediaPickerItem], Int) -> ())? - var onItemUpdate: ((MediaPickerItem, Int?) -> ())? - var onItemMove: ((Int, Int) -> ())? - var onItemRemove: ((MediaPickerItem, Int?) -> ())? + var onItemsAdd: (([MediaPickerItem], _ index: Int) -> ())? + var onItemUpdate: ((MediaPickerItem, _ index: Int?) -> ())? + var onItemMove: ((_ sourceIndex: Int, _ destinationIndex: Int) -> ())? + var onItemRemove: ((MediaPickerItem, _ index: Int?) -> ())? var onCropFinish: (() -> ())? var onCropCancel: (() -> ())? var onContinueButtonTap: (() -> ())? From fd4e4dfbe9dde373421ee6c682c9361650f8f20a Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Thu, 10 Aug 2017 01:05:43 +0300 Subject: [PATCH 055/111] SEL-222: Add comment for add item touple label --- .../Core/VIPER/MediaPicker/Module/MediaPickerModule.swift | 3 ++- .../VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift index ffb5d428..a6793bf4 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift @@ -26,7 +26,8 @@ public protocol MediaPickerModule: class { func setCropMode(_: MediaPickerCropMode) - var onItemsAdd: (([MediaPickerItem], _ index: Int) -> ())? { get set } + // startIndex - index of element in previous array of MediaPickerItem, new elements were added after that index + var onItemsAdd: (([MediaPickerItem], _ startIndex: Int) -> ())? { get set } var onItemUpdate: ((MediaPickerItem, _ index: Int?) -> ())? { get set } var onItemMove: ((_ sourceIndex: Int, _ destinationIndex: Int) -> ())? { get set } var onItemRemove: ((MediaPickerItem, _ index: Int?) -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 6d33817a..a7bf00d3 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -24,7 +24,7 @@ final class MediaPickerPresenter: MediaPickerModule { // MARK: - MediaPickerModule - var onItemsAdd: (([MediaPickerItem], _ index: Int) -> ())? + var onItemsAdd: (([MediaPickerItem], _ startIndex: Int) -> ())? var onItemUpdate: ((MediaPickerItem, _ index: Int?) -> ())? var onItemMove: ((_ sourceIndex: Int, _ destinationIndex: Int) -> ())? var onItemRemove: ((MediaPickerItem, _ index: Int?) -> ())? From 82248c60dfe53fb7c204479c9d8ac3824b70d6ac Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Fri, 11 Aug 2017 17:18:05 +0300 Subject: [PATCH 056/111] SEL-303: Fix photo adding from library --- .../MediaPicker/Interactor/MediaPickerInteractorImpl.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 403ff32d..84c2819b 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -61,7 +61,7 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { } func addPhotoLibraryItems( - _: [PhotoLibraryItem], + _ photoLibraryItems: [PhotoLibraryItem], completion: @escaping (_ addedItems: [MediaPickerItem], _ canAddItems: Bool, _ startIndex: Int) -> ()) { From 03ee316bdc194f24f574c1605eb455ce2324a534 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 15 Aug 2017 13:52:41 +0300 Subject: [PATCH 057/111] SEL-250: Fix button with activity rotation --- .../VIPER/MediaPicker/View/MediaPickerView.swift | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index 1f2462bb..744dd8bf 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -384,10 +384,17 @@ final class MediaPickerView: UIView, ThemeConfigurable { withDuration: 0.3, animations: { self.continueButton.style = style - self.continueButton.size = CGSize( - width: self.continueButton.sizeThatFits().width, - height: self.continueButtonHeight - ) + if self.deviceOrientation == .portrait { + self.continueButton.size = CGSize( + width: self.continueButton.sizeThatFits().width, + height: self.continueButtonHeight + ) + } else { + self.continueButton.size = CGSize( + width: self.continueButtonHeight, + height: self.continueButton.sizeThatFits().width + ) + } self.layoutCloseAndContinueButtons() } ) From f0a93d28d0ca04bda263a475b139c446d2e91c6a Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 15 Aug 2017 15:35:05 +0300 Subject: [PATCH 058/111] SEL-293: Add autocorrect button --- .../Core/Utilities/AccessibilityId.swift | 1 + .../View/Controls/PhotoControlsView.swift | 62 ++++++++++++++----- .../MediaPicker/View/MediaPickerView.swift | 14 ++++- 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/Paparazzo/Core/Utilities/AccessibilityId.swift b/Paparazzo/Core/Utilities/AccessibilityId.swift index 5c8f4205..865954f3 100644 --- a/Paparazzo/Core/Utilities/AccessibilityId.swift +++ b/Paparazzo/Core/Utilities/AccessibilityId.swift @@ -26,5 +26,6 @@ public enum AccessibilityId: String { // PhotoControls case removeButton + case autocorrectButton case cropButton } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift index 6b9ec4e2..88a0638c 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift @@ -2,16 +2,31 @@ import UIKit final class PhotoControlsView: UIView, ThemeConfigurable { + struct ModeOptions: OptionSet { + let rawValue: Int + + static let hasRemoveButton = ModeOptions(rawValue: 1 << 0) + static let hasAutocorrectButton = ModeOptions(rawValue: 1 << 1) + static let hasCropButton = ModeOptions(rawValue: 1 << 2) + + static let allButtons: ModeOptions = [.hasRemoveButton, .hasAutocorrectButton, .hasCropButton] + } + typealias ThemeType = MediaPickerRootModuleUITheme // MARK: - Subviews private let removeButton = UIButton() + private let autocorrectButton = UIButton() private let cropButton = UIButton() + private var buttons = [UIButton]() + // MARK: UIView override init(frame: CGRect) { + self.mode = [.hasRemoveButton, .hasCropButton] + super.init(frame: frame) backgroundColor = .white @@ -22,6 +37,12 @@ final class PhotoControlsView: UIView, ThemeConfigurable { for: .touchUpInside ) + autocorrectButton.addTarget( + self, + action: #selector(onAutocorrectButtonTap(_:)), + for: .touchUpInside + ) + cropButton.addTarget( self, action: #selector(onCropButtonTap(_:)), @@ -29,13 +50,17 @@ final class PhotoControlsView: UIView, ThemeConfigurable { ) addSubview(removeButton) - addSubview(cropButton) // в первой итерации не показываем + addSubview(autocorrectButton) + addSubview(cropButton) + + buttons = [removeButton, autocorrectButton, cropButton] setUpAccessibilityIdentifiers() } private func setUpAccessibilityIdentifiers() { removeButton.setAccessibilityId(.removeButton) + autocorrectButton.setAccessibilityId(.autocorrectButton) cropButton.setAccessibilityId(.cropButton) } @@ -46,14 +71,13 @@ final class PhotoControlsView: UIView, ThemeConfigurable { override func layoutSubviews() { super.layoutSubviews() - removeButton.size = CGSize.minimumTapAreaSize - cropButton.size = CGSize.minimumTapAreaSize - - if cropButton.isHidden { - removeButton.center = bounds.center - } else { - removeButton.center = CGPoint(x: bounds.left + bounds.size.width * 0.25, y: bounds.centerY) - cropButton.center = CGPoint(x: bounds.right - bounds.size.width * 0.25, y: bounds.centerY) + let visibleButtons = buttons.filter { $0.isHidden == false } + visibleButtons.enumerated().forEach { index, button in + button.size = CGSize.minimumTapAreaSize + button.center = CGPoint( + x: (width * (2.0 * CGFloat(index) + 1.0)) / (2.0 * CGFloat(visibleButtons.count)), + y: bounds.centerY + ) } } @@ -67,25 +91,35 @@ final class PhotoControlsView: UIView, ThemeConfigurable { // MARK: - PhotoControlsView var onRemoveButtonTap: (() -> ())? + var onAutocorrectButtonTap: (() -> ())? var onCropButtonTap: (() -> ())? var onCameraButtonTap: (() -> ())? + var mode: ModeOptions { + didSet { + removeButton.isHidden = !mode.contains(.hasRemoveButton) + autocorrectButton.isHidden = !mode.contains(.hasAutocorrectButton) + cropButton.isHidden = !mode.contains(.hasCropButton) + setNeedsLayout() + } + } + func setControlsTransform(_ transform: CGAffineTransform) { removeButton.transform = transform + autocorrectButton.transform = transform cropButton.transform = transform } - func setShowsCropButton(_ showsCropButton: Bool) { - cropButton.isHidden = !showsCropButton - setNeedsLayout() - } - // MARK: - Private @objc private func onRemoveButtonTap(_: UIButton) { onRemoveButtonTap?() } + @objc private func onAutocorrectButtonTap(_: UIButton) { + onAutocorrectButtonTap?() + } + @objc private func onCropButtonTap(_: UIButton) { onCropButtonTap?() } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index 744dd8bf..7645810a 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -500,7 +500,19 @@ final class MediaPickerView: UIView, ThemeConfigurable { } func setShowsCropButton(_ showsCropButton: Bool) { - photoControlsView.setShowsCropButton(showsCropButton) + if showsCropButton { + photoControlsView.mode.insert(.hasCropButton) + } else { + photoControlsView.mode.remove(.hasCropButton) + } + } + + func setShowsAutocorrectButton(_ showsAutocorrectButton: Bool) { + if showsAutocorrectButton { + photoControlsView.mode.insert(.hasAutocorrectButton) + } else { + photoControlsView.mode.remove(.hasAutocorrectButton) + } } func setShowsPreview(_ showsPreview: Bool) { From 9127f650459d3d48f020b8e4da2a4e67a18bbe85 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 15 Aug 2017 15:57:36 +0300 Subject: [PATCH 059/111] SEL-326: Fix indexes in callbacks --- .../Presenter/MediaPickerPresenter.swift | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index a7bf00d3..e92e098f 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -370,19 +370,18 @@ final class MediaPickerPresenter: MediaPickerModule { interactor.selectedItem { [weak self] item in guard let item = item else { return } - self?.interactor.removeItem(item) { adjacentItem, canAddItems in - - self?.view?.removeItem(item) - self?.view?.setCameraButtonVisible(canAddItems) - - if let adjacentItem = adjacentItem { - self?.view?.selectItem(adjacentItem) - } else { - self?.view?.setMode(.camera) - self?.view?.setPhotoTitleAlpha(0) - } - - self?.interactor.indexOfItem(item) { index in + self?.interactor.indexOfItem(item) { index in + self?.interactor.removeItem(item) { adjacentItem, canAddItems in + self?.view?.removeItem(item) + self?.view?.setCameraButtonVisible(canAddItems) + + if let adjacentItem = adjacentItem { + self?.view?.selectItem(adjacentItem) + } else { + self?.view?.setMode(.camera) + self?.view?.setPhotoTitleAlpha(0) + } + self?.onItemRemove?(item, index) } } @@ -477,7 +476,7 @@ final class MediaPickerPresenter: MediaPickerModule { self?.interactor.updateItem(croppedItem) { self?.view?.updateItem(croppedItem) self?.adjustPhotoTitleForItem(croppedItem) - self?.interactor.indexOfItem(item) { index in + self?.interactor.indexOfItem(croppedItem) { index in self?.onItemUpdate?(croppedItem, index) self?.router.focusOnCurrentModule() } From ecd66c09168384ff6a9a91b1866d91194455b431 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 15 Aug 2017 16:24:20 +0300 Subject: [PATCH 060/111] SEL-293: Add images for autocorrect button --- .../autocorrect_active.imageset/Contents.json | 23 ++++++++++++++++++ .../autocorrect_active.png | Bin 0 -> 359 bytes .../autocorrect_active@2x.png | Bin 0 -> 405 bytes .../autocorrect_active@3x.png | Bin 0 -> 776 bytes .../Contents.json | 23 ++++++++++++++++++ .../autocorrect.png | Bin 0 -> 365 bytes .../autocorrect@2x.png | Bin 0 -> 405 bytes .../autocorrect@3x.png | Bin 0 -> 771 bytes Paparazzo/Core/MediaPickerUITheme.swift | 2 ++ .../View/Controls/PhotoControlsView.swift | 3 +++ .../View/MediaPickerRootModuleUITheme.swift | 2 ++ 11 files changed, 53 insertions(+) create mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/Contents.json create mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active.png create mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active@2x.png create mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active@3x.png create mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/Contents.json create mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect.png create mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect@2x.png create mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect@3x.png diff --git a/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/Contents.json b/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/Contents.json new file mode 100644 index 00000000..c1acf23b --- /dev/null +++ b/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "autocorrect_active.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "autocorrect_active@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "autocorrect_active@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active.png b/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active.png new file mode 100644 index 0000000000000000000000000000000000000000..6ff493dd05aa9a35799f92ac064bb55d3368ade7 GIT binary patch literal 359 zcmV-t0hs=YP)SqYIJ?OP zAi$oAft%e0XrfW_<=_+FY0of;W@nWfCVg^N&o3Mel zEkIFzJ1FK7DnGHF3Pg1%;oIL!b6002ovPDHLk FV1lxeopJyG literal 0 HcmV?d00001 diff --git a/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active@2x.png b/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4e2648b9ad39e2cc76d766ce0eda0f506974b9cb GIT binary patch literal 405 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw3=&b&bO2Ij0X`wFK$_wIe}>in8CJna2m>gx z1}q5VLJ&mFIuKXpZN+1tM!}LGzhDN&xj$P!x9_P;?P7j0^v z^|Ucl#*Cjo%~3yU>A{>21zR>x+r0HfBJYKB^8V_YlM`xfy=DqtyPSDv=@~nR}Oywe6R*?wWn`yFBmB_E+_KPFBB{XrHT$I`b^);@5M#4u!qx z5fLk`t#96BmVK$MhlBgr&BY6H^enrp{@#AXw|e7>?%R_$?AR{Zo!@eF&8n^crhHjz zKfm_%ex4d`pNND*OvYmD$0D|LZ8*`q=1YCV%(K%vH{O`J(V%bXa{K@99>m+Ytp6E) bFZUDE!Z*_WXMg4Dg2LC+)z4*}Q$iB})LOv` literal 0 HcmV?d00001 diff --git a/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active@3x.png b/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5865485ffdadd5790a9dcb90750e1f4d58937ab4 GIT binary patch literal 776 zcmeAS@N?(olHy`uVBq!ia0vp^Q6S903?%u>HW~n_MFBn`u0Wb$|9^(n{~1>OXIKkk z0NHE6B0zBn`VSTX3PQvo3brAs-w0N}9;P0s0AeQG9H0o$5Qrdz1d70w0X4$eXJCea zl|68576m%ev?Rzcn1N;0XX}OwaseOv-v47PG<;N-e}TRH!&_cmg_mo;Mc+2Ry3?4c ze^&w93j5mc0%o%&7^;gF}LjYcE02(s;pk;x3wW=-P2c3YpW;yi#oGUak|#lA4fu$zi-Y;cpj*d z6QZ_J)6mqr7Gs}` zK9@&H)m01EI0F;g9$W4r`E$p!lbfnOClsgJ%=?gfqejv!wyl}%NB`$-}3)~mJ`XYJnE_1mYfX@=(dSDUl$y*Hopn)xsvhkB`b S_B5cw89ZJ6T-G@yGywp01BS5x literal 0 HcmV?d00001 diff --git a/Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect@2x.png b/Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f09a65756f100e0f191f24b1146fa904b954e802 GIT binary patch literal 405 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw3=&b&bO2Ij0X`wFK>Gjx|0X6T#>U1F62dSA z1E5G~Xedww4y>%Kfb83Ms>*;m1xtebf*Ba+{%rl+zNa#^i}~s8oi<`xixTpHl7Bs2 z978H@-Mx03>xcpm%Y`HNwtWBd|NOg6Ws+UD6*l|7wQ0T9oNz$HG;?>@3zKUfZk(Cd zzg_Xw)5c60Gk*RwNByX!2Xj6YY}q_*^VS!Mycf>N`>ShCPN=o@nkjhga^{_-XYACb zR=fP(8P2xIdP}R?n@OgrheagTwqMe^Yxd3W^1L_OU)Ae5S^Zw3eXcU<%(JA6U(fA2 z6!xY^M69&7zIl^b_NBHS4(?+&7ca=sv+S<=d;1aJ>WwS9Z%^K^W4mN`e#_A{tG51| z@@1|4{My(1d1|9gTAH9?f<`f5O3qM f{%82T+)qpk-$?hL{gtZ=3HW~n_`2jv5u0Yzs!NC{|Ouzub29n6! z|Ns9(L`+Rhf#NDEDnJHAC6EhbSX)~INec@LAZc!H4itndfG7i!4Gj$tGl8VGwl+i! zkOVpaB5r1824nzL!u5VKOkM$WqDe`RUoZp9s?XL97vusy_Pzhd_{cE7kiD+_!`fP2 zU4@s%-=c4uU)?#IX_r|6+Y0-Bfw$i+C$U;hJozEJoq>VzzNd?0NJZS+8^`k=8;H0i zdKImBwZnSvce|(i{%`JOOe|`Uyf|S|{OTV%oIj%lrY}?YzEy3X&K-kFz~14KDm;V-Fd$9^Ag&f zw=K=St8p+-@%Y5od8j8{+JD3XlU55JLQS?9N5YtfPUYh%jdw_Uy&cULEO z8gtpU74_RrG<`btFKk(_QQ15uk#CWL3zoZG{-F9b=H@c?2BBR%7iI=t)7d_Ark%b} zBpIdWd^+`6lwPsaQlr%1oj&*7?wx$+s(bmA* Date: Tue, 15 Aug 2017 19:12:55 +0300 Subject: [PATCH 061/111] SEL-294: Add autocorrection support --- .../Example/Presenter/ExamplePresenter.swift | 1 + .../Assembly/MediaPickerAssemblyImpl.swift | 2 ++ .../Interactor/Filters/Filter.swift | 5 ++++ .../Interactor/MediaPickerInteractor.swift | 2 ++ .../MediaPickerInteractorImpl.swift | 23 +++++++++++++++++++ .../MediaPicker/Module/MediaPickerData.swift | 6 +++++ .../Presenter/MediaPickerPresenter.swift | 18 +++++++++++++++ .../MediaPicker/View/MediaPickerView.swift | 5 ++++ .../View/MediaPickerViewController.swift | 8 +++++++ .../View/MediaPickerViewInput.swift | 1 + .../MediaPickerMarshrouteAssemblyImpl.swift | 2 ++ 11 files changed, 73 insertions(+) create mode 100644 Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index 4cbd84ba..601646dd 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -120,6 +120,7 @@ final class ExamplePresenter { selectedItem: items.last, maxItemsCount: 20, cropEnabled: true, + autocorrectEnabled: true, cropCanvasSize: cropCanvasSize ) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift index 50ef206e..1e00e1b0 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Assembly/MediaPickerAssemblyImpl.swift @@ -20,6 +20,7 @@ public final class MediaPickerAssemblyImpl: BasePaparazzoAssembly, MediaPickerAs { let interactor = MediaPickerInteractorImpl( items: data.items, + autocorrectionFilters: data.autocorrectionFilters, selectedItem: data.selectedItem, maxItemsCount: data.maxItemsCount, cropCanvasSize: data.cropCanvasSize, @@ -47,6 +48,7 @@ public final class MediaPickerAssemblyImpl: BasePaparazzoAssembly, MediaPickerAs viewController.setCameraView(cameraView) viewController.setTheme(theme) viewController.setShowsCropButton(data.cropEnabled) + viewController.setShowsAutocorrectButton(data.autocorrectEnabled) presenter.view = viewController diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift new file mode 100644 index 00000000..e4909064 --- /dev/null +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift @@ -0,0 +1,5 @@ +import Foundation + +public protocol Filter { + func apply(_ sourceImage: MediaPickerItem, completion: @escaping ((_ resultImage: MediaPickerItem) -> Void)) +} diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift index 30496ca6..53ebdc71 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift @@ -34,4 +34,6 @@ protocol MediaPickerInteractor: class { func setCropMode(_: MediaPickerCropMode) func cropMode(completion: @escaping (MediaPickerCropMode) -> ()) + + func autocorrectItem(_: MediaPickerItem, completion: @escaping (_ updatedItem: MediaPickerItem) -> ()) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 84c2819b..410205d2 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -9,12 +9,14 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { private let cropCanvasSize: CGSize private var items = [MediaPickerItem]() + private var autocorrectionFilters = [Filter]() private var photoLibraryItems = [PhotoLibraryItem]() private var selectedItem: MediaPickerItem? private var mode: MediaPickerCropMode = .normal init( items: [MediaPickerItem], + autocorrectionFilters: [Filter], selectedItem: MediaPickerItem?, maxItemsCount: Int?, cropCanvasSize: CGSize, @@ -22,6 +24,7 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { latestLibraryPhotoProvider: PhotoLibraryLatestPhotoProvider ) { self.items = items + self.autocorrectionFilters = autocorrectionFilters self.selectedItem = selectedItem self.maxItemsCount = maxItemsCount self.cropCanvasSize = cropCanvasSize @@ -149,6 +152,26 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { completion(cropCanvasSize) } + func autocorrectItem(_ item: MediaPickerItem, completion: @escaping (_ updatedItem: MediaPickerItem) -> ()) { + DispatchQueue.global(qos: .userInitiated).async { + var item = item + + let filtersGroup = DispatchGroup() + self.autocorrectionFilters.forEach { filter in + filtersGroup.enter() + filter.apply(item) { resultItem in + item = resultItem + filtersGroup.leave() + } + filtersGroup.wait() + } + + DispatchQueue.main.async { + completion(item) + } + } + } + // MARK: - Private private func canAddItems() -> Bool { diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift index 8116d75b..cbd49916 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerData.swift @@ -2,24 +2,30 @@ import UIKit public struct MediaPickerData { public let items: [MediaPickerItem] + public let autocorrectionFilters: [Filter] public let selectedItem: MediaPickerItem? public let maxItemsCount: Int? public let cropEnabled: Bool + public let autocorrectEnabled: Bool public let cropCanvasSize: CGSize public let initialActiveCameraType: CameraType public init( items: [MediaPickerItem], + autocorrectionFilters: [Filter] = [], selectedItem: MediaPickerItem?, maxItemsCount: Int?, cropEnabled: Bool, + autocorrectEnabled: Bool = false, cropCanvasSize: CGSize, initialActiveCameraType: CameraType = .back) { self.items = items + self.autocorrectionFilters = autocorrectionFilters self.selectedItem = selectedItem self.maxItemsCount = maxItemsCount self.cropEnabled = cropEnabled + self.autocorrectEnabled = autocorrectEnabled self.cropCanvasSize = cropCanvasSize self.initialActiveCameraType = initialActiveCameraType } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index e92e098f..31785433 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -251,6 +251,24 @@ final class MediaPickerPresenter: MediaPickerModule { } } + view?.onAutocorrectButtonTap = { [weak self] in + self?.interactor.selectedItem { item in + guard let selectedItem = item else { + return + } + + self?.interactor.autocorrectItem(selectedItem) { updatedItem in + self?.interactor.updateItem(updatedItem) { + self?.view?.updateItem(updatedItem) + self?.adjustPhotoTitleForItem(updatedItem) + self?.interactor.indexOfItem(updatedItem) { index in + self?.onItemUpdate?(updatedItem, index) + } + } + } + } + } + view?.onRemoveButtonTap = { [weak self] in self?.removeSelectedItem() } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index 7645810a..733b29e0 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -261,6 +261,11 @@ final class MediaPickerView: UIView, ThemeConfigurable { set { photoControlsView.onRemoveButtonTap = newValue } } + var onAutocorrectButtonTap: (() -> ())? { + get { return photoControlsView.onAutocorrectButtonTap } + set { photoControlsView.onAutocorrectButtonTap = newValue } + } + var onCropButtonTap: (() -> ())? { get { return photoControlsView.onCropButtonTap } set { photoControlsView.onCropButtonTap = newValue } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index 0e526a4b..a0e3e2b8 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -161,6 +161,10 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI set { mediaPickerView.onRemoveButtonTap = newValue } } + var onAutocorrectButtonTap: (() -> ())? { + get { return mediaPickerView.onAutocorrectButtonTap } + set { mediaPickerView.onAutocorrectButtonTap = newValue } + } var onCropButtonTap: (() -> ())? { get { return mediaPickerView.onCropButtonTap } set { mediaPickerView.onCropButtonTap = newValue } @@ -347,6 +351,10 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI mediaPickerView.setShowsCropButton(showsCropButton) } + func setShowsAutocorrectButton(_ showsAutocorrectButton: Bool) { + mediaPickerView.setShowsAutocorrectButton(showsAutocorrectButton) + } + func setShowPreview(_ showPreview: Bool) { mediaPickerView.setShowsPreview(showPreview) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift index 7158cf72..e8de0215 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift @@ -68,6 +68,7 @@ protocol MediaPickerViewInput: class { // MARK: - Selected photo actions var onRemoveButtonTap: (() -> ())? { get set } + var onAutocorrectButtonTap: (() -> ())? { get set } var onCropButtonTap: (() -> ())? { get set } var onCameraThumbnailTap: (() -> ())? { get set } diff --git a/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift b/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift index a16a2d01..2e1440d1 100644 --- a/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift +++ b/Paparazzo/Marshroute/MediaPicker/Assembly/MediaPickerMarshrouteAssemblyImpl.swift @@ -22,6 +22,7 @@ public final class MediaPickerMarshrouteAssemblyImpl: BasePaparazzoAssembly, Med { let interactor = MediaPickerInteractorImpl( items: data.items, + autocorrectionFilters: data.autocorrectionFilters, selectedItem: data.selectedItem, maxItemsCount: data.maxItemsCount, cropCanvasSize: data.cropCanvasSize, @@ -48,6 +49,7 @@ public final class MediaPickerMarshrouteAssemblyImpl: BasePaparazzoAssembly, Med viewController.setCameraView(cameraView) viewController.setTheme(theme) viewController.setShowsCropButton(data.cropEnabled) + viewController.setShowsAutocorrectButton(data.autocorrectEnabled) presenter.view = viewController From fe18b8af90861a7fa3e90ae342891b781752d6c3 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 15 Aug 2017 20:21:34 +0300 Subject: [PATCH 062/111] SEL-294: Add support for autocorrection filters --- .../Interactor/MediaPickerInteractor.swift | 3 +- .../MediaPickerInteractorImpl.swift | 20 +++++++-- .../MediaPicker/Module/MediaPickerItem.swift | 4 +- .../Presenter/MediaPickerPresenter.swift | 43 ++++++++++++++----- .../View/Controls/PhotoControlsView.swift | 4 ++ .../MediaPicker/View/MediaPickerView.swift | 9 ++++ .../View/MediaPickerViewController.swift | 4 ++ .../View/MediaPickerViewInput.swift | 6 +++ 8 files changed, 77 insertions(+), 16 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift index 53ebdc71..dc5c0b28 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift @@ -35,5 +35,6 @@ protocol MediaPickerInteractor: class { func setCropMode(_: MediaPickerCropMode) func cropMode(completion: @escaping (MediaPickerCropMode) -> ()) - func autocorrectItem(_: MediaPickerItem, completion: @escaping (_ updatedItem: MediaPickerItem) -> ()) + func autocorrectItem(completion: @escaping (_ updatedItem: MediaPickerItem) -> ()) + func undoAutocorrectItem(completion: @escaping (_ originalItem: MediaPickerItem?) -> ()) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 410205d2..5466af26 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -152,10 +152,19 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { completion(cropCanvasSize) } - func autocorrectItem(_ item: MediaPickerItem, completion: @escaping (_ updatedItem: MediaPickerItem) -> ()) { + func autocorrectItem(completion: @escaping (_ updatedItem: MediaPickerItem) -> ()) { + guard var selectedItem = selectedItem else { + return + } + + let originalItem = selectedItem + var item = MediaPickerItem( + identifier: selectedItem.identifier, + image: selectedItem.image, + source: selectedItem.source + )// todo: use image from filters + DispatchQueue.global(qos: .userInitiated).async { - var item = item - let filtersGroup = DispatchGroup() self.autocorrectionFilters.forEach { filter in filtersGroup.enter() @@ -167,11 +176,16 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { } DispatchQueue.main.async { + item.originalItem = originalItem completion(item) } } } + func undoAutocorrectItem(completion: @escaping (_ originalItem: MediaPickerItem?) -> ()) { + completion(selectedItem?.originalItem) + } + // MARK: - Private private func canAddItems() -> Bool { diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerItem.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerItem.swift index 6faa0a59..77082ba5 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerItem.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerItem.swift @@ -1,7 +1,7 @@ import ImageSource /// Главная модель, представляющая фотку в пикере -public struct MediaPickerItem: Equatable { +public class MediaPickerItem: Equatable { public enum Source { case camera @@ -13,6 +13,8 @@ public struct MediaPickerItem: Equatable { let identifier: String + var originalItem: MediaPickerItem? = nil + public init(identifier: String = NSUUID().uuidString, image: ImageSource, source: Source) { self.identifier = identifier self.image = image diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 31785433..3cdd314b 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -192,6 +192,7 @@ final class MediaPickerPresenter: MediaPickerModule { view?.onItemSelect = { [weak self] item in self?.interactor.selectItem(item) + self?.updateAutocorrectionStatusForItem(item) self?.adjustViewForSelectedItem(item, animated: true, scrollToSelected: true) } @@ -200,6 +201,7 @@ final class MediaPickerPresenter: MediaPickerModule { self?.onItemMove?(sourceIndex, destinationIndex) self?.interactor.selectedItem { item in if let item = item { + self?.updateAutocorrectionStatusForItem(item) self?.adjustViewForSelectedItem(item, animated: true, scrollToSelected: false) } } @@ -252,18 +254,17 @@ final class MediaPickerPresenter: MediaPickerModule { } view?.onAutocorrectButtonTap = { [weak self] in - self?.interactor.selectedItem { item in - guard let selectedItem = item else { - return - } - - self?.interactor.autocorrectItem(selectedItem) { updatedItem in - self?.interactor.updateItem(updatedItem) { - self?.view?.updateItem(updatedItem) - self?.adjustPhotoTitleForItem(updatedItem) - self?.interactor.indexOfItem(updatedItem) { index in - self?.onItemUpdate?(updatedItem, index) + self?.interactor.selectedItem { selectedItem in + if selectedItem?.originalItem == nil { + self?.interactor.autocorrectItem { updatedItem in + self?.updateItem(updatedItem) + } + } else { + self?.interactor.undoAutocorrectItem { originalItem in + guard let originalItem = originalItem else { + return } + self?.updateItem(originalItem) } } } @@ -289,6 +290,17 @@ final class MediaPickerPresenter: MediaPickerModule { } } + private func updateItem(_ updatedItem: MediaPickerItem) { + interactor.updateItem(updatedItem) { [weak self] in + self?.view?.updateItem(updatedItem) + self?.adjustPhotoTitleForItem(updatedItem) + self?.interactor.indexOfItem(updatedItem) { index in + self?.updateAutocorrectionStatusForItem(updatedItem) + self?.onItemUpdate?(updatedItem, index) + } + } + } + private func adjustViewForSelectedItem(_ item: MediaPickerItem, animated: Bool, scrollToSelected: Bool) { adjustPhotoTitleForItem(item) @@ -298,6 +310,14 @@ final class MediaPickerPresenter: MediaPickerModule { } } + private func updateAutocorrectionStatusForItem(_ item: MediaPickerItem) { + if item.originalItem == nil { + view?.setAutocorrectionStatus(.original) + } else { + view?.setAutocorrectionStatus(.corrected) + } + } + private func adjustPhotoTitleForItem(_ item: MediaPickerItem) { interactor.indexOfItem(item) { [weak self] index in if let index = index { @@ -330,6 +350,7 @@ final class MediaPickerPresenter: MediaPickerModule { private func selectItem(_ item: MediaPickerItem) { view?.selectItem(item) + updateAutocorrectionStatusForItem(item) adjustViewForSelectedItem(item, animated: false, scrollToSelected: true) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift index 3faec4db..5c99c501 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/Controls/PhotoControlsView.swift @@ -113,6 +113,10 @@ final class PhotoControlsView: UIView, ThemeConfigurable { cropButton.transform = transform } + func setAutocorrectButtonSelected(_ selected: Bool) { + autocorrectButton.isSelected = selected + } + // MARK: - Private @objc private func onRemoveButtonTap(_: UIButton) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index 733b29e0..918d6551 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -321,6 +321,15 @@ final class MediaPickerView: UIView, ThemeConfigurable { adjustForDeviceOrientation(deviceOrientation) } + func setAutocorrectionStatus(_ status: MediaPickerAutocorrectionStatus) { + switch status { + case .original: + photoControlsView.setAutocorrectButtonSelected(false) + case .corrected: + photoControlsView.setAutocorrectButtonSelected(true) + } + } + func setCameraControlsEnabled(_ enabled: Bool) { cameraControlsView.setCameraControlsEnabled(enabled) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index a0e3e2b8..9a4bd5cc 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -204,6 +204,10 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI } } + func setAutocorrectionStatus(_ status: MediaPickerAutocorrectionStatus) { + mediaPickerView.setAutocorrectionStatus(status) + } + func setCameraOutputParameters(_ parameters: CameraOutputParameters) { mediaPickerView.setCameraOutputParameters(parameters) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift index e8de0215..c85b95e0 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift @@ -11,9 +11,15 @@ enum MediaPickerTitleStyle { case light } +enum MediaPickerAutocorrectionStatus { + case original + case corrected +} + protocol MediaPickerViewInput: class { func setMode(_: MediaPickerViewMode) + func setAutocorrectionStatus(_: MediaPickerAutocorrectionStatus) func adjustForDeviceOrientation(_: DeviceOrientation) func setCameraOutputParameters(_: CameraOutputParameters) From 2cf9586601ece6bc2df52d88e1ce480ece631a3b Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Wed, 16 Aug 2017 12:40:24 +0300 Subject: [PATCH 063/111] SEL-294: Improve filters codestyle --- .../Interactor/Filters/Filter.swift | 2 +- .../Interactor/MediaPickerInteractor.swift | 3 +-- .../Interactor/MediaPickerInteractorImpl.swift | 18 +++++++----------- .../MediaPicker/Module/MediaPickerItem.swift | 2 +- .../Presenter/MediaPickerPresenter.swift | 13 +++++-------- 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift index e4909064..5a6f2a19 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift @@ -1,5 +1,5 @@ import Foundation public protocol Filter { - func apply(_ sourceImage: MediaPickerItem, completion: @escaping ((_ resultImage: MediaPickerItem) -> Void)) + func apply(_ sourceItem: MediaPickerItem, completion: @escaping ((_ resultItem: MediaPickerItem) -> Void)) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift index dc5c0b28..909da7ab 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift @@ -35,6 +35,5 @@ protocol MediaPickerInteractor: class { func setCropMode(_: MediaPickerCropMode) func cropMode(completion: @escaping (MediaPickerCropMode) -> ()) - func autocorrectItem(completion: @escaping (_ updatedItem: MediaPickerItem) -> ()) - func undoAutocorrectItem(completion: @escaping (_ originalItem: MediaPickerItem?) -> ()) + func autocorrectItem(completion: @escaping (_ updatedItem: MediaPickerItem?) -> ()) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 5466af26..3f4f5a7e 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -152,16 +152,16 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { completion(cropCanvasSize) } - func autocorrectItem(completion: @escaping (_ updatedItem: MediaPickerItem) -> ()) { - guard var selectedItem = selectedItem else { + func autocorrectItem(completion: @escaping (_ updatedItem: MediaPickerItem?) -> ()) { + guard let originalItem = selectedItem else { + completion(nil) return } - let originalItem = selectedItem var item = MediaPickerItem( - identifier: selectedItem.identifier, - image: selectedItem.image, - source: selectedItem.source + identifier: originalItem.identifier, + image: originalItem.image, + source: originalItem.source )// todo: use image from filters DispatchQueue.global(qos: .userInitiated).async { @@ -175,17 +175,13 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { filtersGroup.wait() } - DispatchQueue.main.async { + filtersGroup.notify(queue: DispatchQueue.main) { item.originalItem = originalItem completion(item) } } } - func undoAutocorrectItem(completion: @escaping (_ originalItem: MediaPickerItem?) -> ()) { - completion(selectedItem?.originalItem) - } - // MARK: - Private private func canAddItems() -> Bool { diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerItem.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerItem.swift index 77082ba5..35354ed9 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerItem.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerItem.swift @@ -1,7 +1,7 @@ import ImageSource /// Главная модель, представляющая фотку в пикере -public class MediaPickerItem: Equatable { +public final class MediaPickerItem: Equatable { public enum Source { case camera diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 3cdd314b..51d5b043 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -255,16 +255,13 @@ final class MediaPickerPresenter: MediaPickerModule { view?.onAutocorrectButtonTap = { [weak self] in self?.interactor.selectedItem { selectedItem in - if selectedItem?.originalItem == nil { - self?.interactor.autocorrectItem { updatedItem in - self?.updateItem(updatedItem) - } + if let originalItem = selectedItem?.originalItem{ + self?.updateItem(originalItem) } else { - self?.interactor.undoAutocorrectItem { originalItem in - guard let originalItem = originalItem else { - return + self?.interactor.autocorrectItem { updatedItem in + if let updatedItem = updatedItem { + self?.updateItem(updatedItem) } - self?.updateItem(originalItem) } } } From f180b451d5b4f37d53b7a46704b2b449c7a453ac Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Wed, 16 Aug 2017 13:33:59 +0300 Subject: [PATCH 064/111] SEL-294: Refactor interactor interface --- .../Interactor/MediaPickerInteractor.swift | 32 +- .../MediaPickerInteractorImpl.swift | 60 ++-- .../Presenter/MediaPickerPresenter.swift | 298 +++++++++--------- 3 files changed, 181 insertions(+), 209 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift index 909da7ab..84dedd2c 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift @@ -2,38 +2,40 @@ import ImageSource protocol MediaPickerInteractor: class { + var items: [MediaPickerItem] { get } + var cropCanvasSize: CGSize { get } + var photoLibraryItems: [PhotoLibraryItem] { get } + var selectedItem: MediaPickerItem? { get } + func addItems( _ items: [MediaPickerItem], - completion: @escaping (_ addedItems: [MediaPickerItem], _ canAddItems: Bool, _ startIndex: Int) + completion: @escaping (_ addedItems: [MediaPickerItem], _ startIndex: Int) -> ()) func addPhotoLibraryItems( - _: [PhotoLibraryItem], - completion: @escaping (_ addedItems: [MediaPickerItem], _ canAddItems: Bool, _ startIndex: Int) + _ photoLibraryItems: [PhotoLibraryItem], + completion: @escaping (_ addedItems: [MediaPickerItem], _ startIndex: Int) -> ()) - func updateItem(_: MediaPickerItem, completion: @escaping () -> ()) - // `completion` вызывается с соседним item'ом — это item, который нужно выделить после того, как удалили `item` - func removeItem(_: MediaPickerItem, completion: @escaping (_ adjacentItem: MediaPickerItem?, _ canAddItems: Bool) -> ()) + func updateItem(_ item: MediaPickerItem) + + // returns the nearby item - the item to select after removing the original item + func removeItem(_ item: MediaPickerItem) -> MediaPickerItem? func selectItem(_: MediaPickerItem?) - func selectedItem(completion: @escaping (MediaPickerItem?) -> ()) func moveItem(from sourceIndex: Int, to destinationIndex: Int) - func items(completion: @escaping (_ mediaPickerItems: [MediaPickerItem], _ canAddItems: Bool) -> ()) - func photoLibraryItems(completion: @escaping ([PhotoLibraryItem]) -> ()) - - func indexOfItem(_: MediaPickerItem, completion: @escaping (Int?) -> ()) + func indexOfItem(_ item: MediaPickerItem) -> Int? - func numberOfItemsAvailableForAdding(completion: @escaping (Int?) -> ()) - - func cropCanvasSize(completion: @escaping (CGSize) -> ()) + func numberOfItemsAvailableForAdding() -> Int? func observeDeviceOrientation(handler: @escaping (DeviceOrientation) -> ()) func observeLatestPhotoLibraryItem(handler: @escaping (ImageSource?) -> ()) func setCropMode(_: MediaPickerCropMode) - func cropMode(completion: @escaping (MediaPickerCropMode) -> ()) + func cropMode() -> MediaPickerCropMode + + func canAddItems() -> Bool func autocorrectItem(completion: @escaping (_ updatedItem: MediaPickerItem?) -> ()) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 3f4f5a7e..419b0538 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -6,12 +6,12 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { private let deviceOrientationService: DeviceOrientationService private let maxItemsCount: Int? - private let cropCanvasSize: CGSize + let cropCanvasSize: CGSize - private var items = [MediaPickerItem]() + private(set) var items = [MediaPickerItem]() private var autocorrectionFilters = [Filter]() - private var photoLibraryItems = [PhotoLibraryItem]() - private var selectedItem: MediaPickerItem? + private(set) var photoLibraryItems = [PhotoLibraryItem]() + private(set) var selectedItem: MediaPickerItem? private var mode: MediaPickerCropMode = .normal init( @@ -38,8 +38,8 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { self.mode = mode } - func cropMode(completion: @escaping (MediaPickerCropMode) -> ()) { - completion(mode) + func cropMode() -> MediaPickerCropMode { + return mode } func observeDeviceOrientation(handler: @escaping (DeviceOrientation) -> ()) { @@ -53,19 +53,19 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { func addItems( _ items: [MediaPickerItem], - completion: @escaping (_ addedItems: [MediaPickerItem], _ canAddItems: Bool, _ startIndex: Int) + completion: @escaping (_ addedItems: [MediaPickerItem], _ startIndex: Int) -> ()) { let numberOfItemsToAdd = min(items.count, maxItemsCount.flatMap { $0 - self.items.count } ?? Int.max) let itemsToAdd = items[0.. ()) { @@ -78,12 +78,12 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { self.photoLibraryItems.append(contentsOf: photoLibraryItems) - addItems(mediaPickerItems) { addedItems, canAddMoreItems, startIndex in - completion(addedItems, canAddMoreItems, startIndex) + addItems(mediaPickerItems) { addedItems, startIndex in + completion(addedItems, startIndex) } } - func updateItem(_ item: MediaPickerItem, completion: @escaping () -> ()) { + func updateItem(_ item: MediaPickerItem) { if let index = items.index(of: item) { items[index] = item @@ -92,11 +92,9 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { if let selectedItem = selectedItem, item == selectedItem { self.selectedItem = item } - - completion() } - func removeItem(_ item: MediaPickerItem, completion: @escaping (_ adjacentItem: MediaPickerItem?, _ canAddItems: Bool) -> ()) { + func removeItem(_ item: MediaPickerItem) -> MediaPickerItem? { var adjacentItem: MediaPickerItem? @@ -117,39 +115,27 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { photoLibraryItems.remove(at: matchingPhotoLibraryItemIndex) } - completion(adjacentItem, canAddItems()) + return adjacentItem } func selectItem(_ item: MediaPickerItem?) { selectedItem = item } - func selectedItem(completion: @escaping (MediaPickerItem?) -> ()) { - completion(selectedItem) - } - func moveItem(from sourceIndex: Int, to destinationIndex: Int) { items.moveElement(from: sourceIndex, to: destinationIndex) } - func items(completion: @escaping (_ mediaPickerItems: [MediaPickerItem], _ canAddItems: Bool) -> ()) { - completion(items, canAddItems()) - } - - func photoLibraryItems(completion: @escaping ([PhotoLibraryItem]) -> ()) { - completion(photoLibraryItems) - } - - func indexOfItem(_ item: MediaPickerItem, completion: @escaping (Int?) -> ()) { - completion(items.index(of: item)) + func indexOfItem(_ item: MediaPickerItem) -> Int? { + return items.index(of: item) } - func numberOfItemsAvailableForAdding(completion: @escaping (Int?) -> ()) { - completion(maxItemsCount.flatMap { $0 - items.count }) + func numberOfItemsAvailableForAdding() -> Int? { + return maxItemsCount.flatMap { $0 - items.count } } - func cropCanvasSize(completion: @escaping (CGSize) -> ()) { - completion(cropCanvasSize) + func canAddItems() -> Bool { + return maxItemsCount.flatMap { self.items.count < $0 } ?? true } func autocorrectItem(completion: @escaping (_ updatedItem: MediaPickerItem?) -> ()) { @@ -181,10 +167,4 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { } } } - - // MARK: - Private - - private func canAddItems() -> Bool { - return maxItemsCount.flatMap { self.items.count < $0 } ?? true - } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 51d5b043..161eb8fe 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -91,9 +91,7 @@ final class MediaPickerPresenter: MediaPickerModule { func finish() { cameraModuleInput.setFlashEnabled(false, completion: nil) - interactor.items { [weak self] items, _ in - self?.onFinish?(items) - } + onFinish?(interactor.items) } // MARK: - Private @@ -134,19 +132,20 @@ final class MediaPickerPresenter: MediaPickerModule { self?.view?.setLatestLibraryPhoto(image) } - interactor.items { [weak self] items, canAddMoreItems in - guard items.count > 0 else { return } + let items = interactor.items + if items.count > 0 { + + let canAddMoreItems = interactor.canAddItems() + view?.setCameraButtonVisible(canAddMoreItems) - self?.view?.setCameraButtonVisible(canAddMoreItems) - self?.view?.addItems(items, animated: false) { - self?.interactor.selectedItem { selectedItem in - if let selectedItem = selectedItem { - self?.selectItem(selectedItem) - } else if canAddMoreItems { - self?.selectCamera() - } else if let lastItem = items.last { - self?.selectItem(lastItem) - } + view?.addItems(items, animated: false) { [weak self] in + let selectedItem = self?.interactor.selectedItem + if let selectedItem = selectedItem { + self?.selectItem(selectedItem) + } else if canAddMoreItems { + self?.selectCamera() + } else if let lastItem = items.last { + self?.selectItem(lastItem) } } } @@ -199,11 +198,9 @@ final class MediaPickerPresenter: MediaPickerModule { view?.onItemMove = { [weak self] (sourceIndex, destinationIndex) in self?.interactor.moveItem(from: sourceIndex, to: destinationIndex) self?.onItemMove?(sourceIndex, destinationIndex) - self?.interactor.selectedItem { item in - if let item = item { - self?.updateAutocorrectionStatusForItem(item) - self?.adjustViewForSelectedItem(item, animated: true, scrollToSelected: false) - } + if let item = self?.interactor.selectedItem { + self?.updateAutocorrectionStatusForItem(item) + self?.adjustViewForSelectedItem(item, animated: true, scrollToSelected: false) } self?.view?.moveItem(from: sourceIndex, to: destinationIndex) } @@ -246,22 +243,18 @@ final class MediaPickerPresenter: MediaPickerModule { } view?.onCropButtonTap = { [weak self] in - self?.interactor.selectedItem { item in - if let item = item { - self?.showCroppingModule(forItem: item) - } + if let item = self?.interactor.selectedItem { + self?.showCroppingModule(forItem: item) } } view?.onAutocorrectButtonTap = { [weak self] in - self?.interactor.selectedItem { selectedItem in - if let originalItem = selectedItem?.originalItem{ - self?.updateItem(originalItem) - } else { - self?.interactor.autocorrectItem { updatedItem in - if let updatedItem = updatedItem { - self?.updateItem(updatedItem) - } + if let originalItem = self?.interactor.selectedItem?.originalItem { + self?.updateItem(originalItem) + } else { + self?.interactor.autocorrectItem { updatedItem in + if let updatedItem = updatedItem { + self?.updateItem(updatedItem) } } } @@ -288,14 +281,12 @@ final class MediaPickerPresenter: MediaPickerModule { } private func updateItem(_ updatedItem: MediaPickerItem) { - interactor.updateItem(updatedItem) { [weak self] in - self?.view?.updateItem(updatedItem) - self?.adjustPhotoTitleForItem(updatedItem) - self?.interactor.indexOfItem(updatedItem) { index in - self?.updateAutocorrectionStatusForItem(updatedItem) - self?.onItemUpdate?(updatedItem, index) - } - } + interactor.updateItem(updatedItem) + view?.updateItem(updatedItem) + adjustPhotoTitleForItem(updatedItem) + let index = interactor.indexOfItem(updatedItem) + updateAutocorrectionStatusForItem(updatedItem) + onItemUpdate?(updatedItem, index) } private func adjustViewForSelectedItem(_ item: MediaPickerItem, animated: Bool, scrollToSelected: Bool) { @@ -316,15 +307,13 @@ final class MediaPickerPresenter: MediaPickerModule { } private func adjustPhotoTitleForItem(_ item: MediaPickerItem) { - interactor.indexOfItem(item) { [weak self] index in - if let index = index { - self?.setTitleForPhotoWithIndex(index) - self?.view?.setPhotoTitleAlpha(1) - - item.image.imageSize { size in - let isPortrait = size.flatMap { $0.height > $0.width } ?? true - self?.view?.setPhotoTitleStyle(isPortrait ? .light : .dark) - } + if let index = interactor.indexOfItem(item) { + setTitleForPhotoWithIndex(index) + view?.setPhotoTitleAlpha(1) + + item.image.imageSize { [weak self] size in + let isPortrait = size.flatMap { $0.height > $0.width } ?? true + self?.view?.setPhotoTitleStyle(isPortrait ? .light : .dark) } } } @@ -334,11 +323,12 @@ final class MediaPickerPresenter: MediaPickerModule { } private func addItems(_ items: [MediaPickerItem], fromCamera: Bool, completion: (() -> ())? = nil) { - interactor.addItems(items) { [weak self] addedItems, canAddItems, startIndex in + interactor.addItems(items) { [weak self] addedItems, startIndex in + guard let strongSelf = self else { return } self?.handleItemsAdded( addedItems, fromCamera: fromCamera, - canAddMoreItems: canAddItems, + canAddMoreItems: strongSelf.interactor.canAddItems(), startIndex: startIndex, completion: completion ) @@ -369,6 +359,11 @@ final class MediaPickerPresenter: MediaPickerModule { view?.addItems(items, animated: fromCamera) { [weak self, view] in + guard let strongSelf = self else { + completion?() + return + } + view?.setCameraButtonVisible(canAddMoreItems) if canAddMoreItems { @@ -379,110 +374,107 @@ final class MediaPickerPresenter: MediaPickerModule { view?.selectItem(lastItem) view?.scrollToItemThumbnail(lastItem, animated: true) - self?.interactor.cropMode { [weak self] mode in - switch mode { - case .normal: - break - case .custom(let provider): - self?.showMaskCropper( - croppingOverlayProvider: provider, - item: lastItem - ) - } - completion?() + let mode = strongSelf.interactor.cropMode() + switch mode { + case .normal: + break + case .custom(let provider): + self?.showMaskCropper( + croppingOverlayProvider: provider, + item: lastItem + ) } + completion?() } } - interactor.items { [weak self] items, _ in - self?.setTitleForPhotoWithIndex(items.count - 1) - } + setTitleForPhotoWithIndex(interactor.items.count - 1) onItemsAdd?(items, startIndex) } private func removeSelectedItem() { - interactor.selectedItem { [weak self] item in - guard let item = item else { return } - - self?.interactor.indexOfItem(item) { index in - self?.interactor.removeItem(item) { adjacentItem, canAddItems in - self?.view?.removeItem(item) - self?.view?.setCameraButtonVisible(canAddItems) - - if let adjacentItem = adjacentItem { - self?.view?.selectItem(adjacentItem) - } else { - self?.view?.setMode(.camera) - self?.view?.setPhotoTitleAlpha(0) - } - - self?.onItemRemove?(item, index) - } - } + guard let item = interactor.selectedItem else { return } + + let index = interactor.indexOfItem(item) + let adjacentItem = interactor.removeItem(item) + view?.removeItem(item) + view?.setCameraButtonVisible(interactor.canAddItems()) + + if let adjacentItem = adjacentItem { + view?.selectItem(adjacentItem) + } else { + view?.setMode(.camera) + view?.setPhotoTitleAlpha(0) } + + onItemRemove?(item, index) } private func showMaskCropper(croppingOverlayProvider: CroppingOverlayProvider, item: MediaPickerItem) { - interactor.cropCanvasSize { [weak self] cropCanvasSize in - - let data = MaskCropperData( - imageSource: item.image, - cropCanvasSize: cropCanvasSize - ) - self?.router.showMaskCropper( - data: data, - croppingOverlayProvider: croppingOverlayProvider) { module in + let cropCanvasSize = interactor.cropCanvasSize + + let data = MaskCropperData( + imageSource: item.image, + cropCanvasSize: cropCanvasSize + ) + router.showMaskCropper( + data: data, + croppingOverlayProvider: croppingOverlayProvider) { [weak self] module in + + module.onDiscard = { [weak module] in - module.onDiscard = { [weak module] in - - self?.onCropCancel?() - self?.removeSelectedItem() - module?.dismissModule() - } + self?.onCropCancel?() + self?.removeSelectedItem() + module?.dismissModule() + } + + module.onConfirm = { image in - module.onConfirm = { image in - - self?.onCropFinish?() - let croppedItem = MediaPickerItem( - identifier: item.identifier, - image: image, - source: item.source - ) - - self?.onFinish?([croppedItem]) - } - } + self?.onCropFinish?() + let croppedItem = MediaPickerItem( + identifier: item.identifier, + image: image, + source: item.source + ) + + self?.onFinish?([croppedItem]) + } } } private func showPhotoLibrary() { - interactor.numberOfItemsAvailableForAdding { [weak self] maxItemsCount in - self?.interactor.photoLibraryItems { photoLibraryItems in + let maxItemsCount = interactor.numberOfItemsAvailableForAdding() + let photoLibraryItems = interactor.photoLibraryItems + + let data = PhotoLibraryData( + selectedItems: [], + maxSelectedItemsCount: maxItemsCount + ) + + router.showPhotoLibrary(data: data) { [weak self] module in + + guard let strongSelf = self else { return } + + module.onFinish = { result in + self?.router.focusOnCurrentModule() - let data = PhotoLibraryData( - selectedItems: [], - maxSelectedItemsCount: maxItemsCount - ) - - self?.router.showPhotoLibrary(data: data) { module in - - module.onFinish = { result in - self?.router.focusOnCurrentModule() - - switch result { - case .selectedItems(let photoLibraryItems): - self?.interactor.addPhotoLibraryItems(photoLibraryItems) { addedItems, canAddItems, startIndex in - self?.handleItemsAdded(addedItems, fromCamera: false, canAddMoreItems: canAddItems, startIndex: startIndex) - } - case .cancelled: - break - } + switch result { + case .selectedItems(let photoLibraryItems): + self?.interactor.addPhotoLibraryItems(photoLibraryItems) { addedItems, startIndex in + self?.handleItemsAdded( + addedItems, + fromCamera: false, + canAddMoreItems: strongSelf.interactor.canAddItems(), + startIndex: startIndex + ) } + case .cancelled: + break } } } @@ -490,33 +482,31 @@ final class MediaPickerPresenter: MediaPickerModule { private func showCroppingModule(forItem item: MediaPickerItem) { - interactor.cropCanvasSize { [weak self] cropCanvasSize in + let cropCanvasSize = interactor.cropCanvasSize + + router.showCroppingModule(forImage: item.image, canvasSize: cropCanvasSize) { [weak self] module in - self?.router.showCroppingModule(forImage: item.image, canvasSize: cropCanvasSize) { module in + module.onDiscard = { [weak self] in - module.onDiscard = { [weak self] in - - self?.onCropCancel?() - self?.router.focusOnCurrentModule() - } + self?.onCropCancel?() + self?.router.focusOnCurrentModule() + } + + module.onConfirm = { [weak self] croppedImageSource in - module.onConfirm = { [weak self] croppedImageSource in - - self?.onCropFinish?() - let croppedItem = MediaPickerItem( - identifier: item.identifier, - image: croppedImageSource, - source: item.source - ) - - self?.interactor.updateItem(croppedItem) { - self?.view?.updateItem(croppedItem) - self?.adjustPhotoTitleForItem(croppedItem) - self?.interactor.indexOfItem(croppedItem) { index in - self?.onItemUpdate?(croppedItem, index) - self?.router.focusOnCurrentModule() - } - } + self?.onCropFinish?() + let croppedItem = MediaPickerItem( + identifier: item.identifier, + image: croppedImageSource, + source: item.source + ) + + self?.interactor.updateItem(croppedItem) + self?.view?.updateItem(croppedItem) + self?.adjustPhotoTitleForItem(croppedItem) + if let index = self?.interactor.indexOfItem(croppedItem) { + self?.onItemUpdate?(croppedItem, index) + self?.router.focusOnCurrentModule() } } } From f4024fe5cb38dff2eaf3c3cadc743d98428ba63d Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Wed, 16 Aug 2017 19:09:31 +0300 Subject: [PATCH 065/111] SEL-294: Fix add more items check --- .../VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 161eb8fe..c9ea16b8 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -133,16 +133,16 @@ final class MediaPickerPresenter: MediaPickerModule { } let items = interactor.items + if items.count > 0 { - let canAddMoreItems = interactor.canAddItems() - view?.setCameraButtonVisible(canAddMoreItems) + view?.setCameraButtonVisible(interactor.canAddItems()) view?.addItems(items, animated: false) { [weak self] in let selectedItem = self?.interactor.selectedItem if let selectedItem = selectedItem { self?.selectItem(selectedItem) - } else if canAddMoreItems { + } else if self?.interactor.canAddItems() == true { self?.selectCamera() } else if let lastItem = items.last { self?.selectItem(lastItem) From e072691e6ea460230cbab46061749b65e4a5b68e Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Wed, 16 Aug 2017 19:16:29 +0300 Subject: [PATCH 066/111] SEL-294: Improve codestyle --- .../Interactor/MediaPickerInteractor.swift | 10 +++--- .../MediaPickerInteractorImpl.swift | 16 ++++----- .../Presenter/MediaPickerPresenter.swift | 33 +++++++++---------- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift index 84dedd2c..7dd073fb 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift @@ -8,13 +8,11 @@ protocol MediaPickerInteractor: class { var selectedItem: MediaPickerItem? { get } func addItems( - _ items: [MediaPickerItem], - completion: @escaping (_ addedItems: [MediaPickerItem], _ startIndex: Int) - -> ()) + _ items: [MediaPickerItem] + ) -> (addedItems: [MediaPickerItem], startIndex: Int) func addPhotoLibraryItems( - _ photoLibraryItems: [PhotoLibraryItem], - completion: @escaping (_ addedItems: [MediaPickerItem], _ startIndex: Int) - -> ()) + _ photoLibraryItems: [PhotoLibraryItem] + ) -> (addedItems: [MediaPickerItem], startIndex: Int) func updateItem(_ item: MediaPickerItem) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 419b0538..9a5911f2 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -52,21 +52,19 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { } func addItems( - _ items: [MediaPickerItem], - completion: @escaping (_ addedItems: [MediaPickerItem], _ startIndex: Int) - -> ()) + _ items: [MediaPickerItem] + ) -> (addedItems: [MediaPickerItem], startIndex: Int) { let numberOfItemsToAdd = min(items.count, maxItemsCount.flatMap { $0 - self.items.count } ?? Int.max) let itemsToAdd = items[0.. ()) + _ photoLibraryItems: [PhotoLibraryItem] + ) -> (addedItems: [MediaPickerItem], startIndex: Int) { let mediaPickerItems = photoLibraryItems.map { @@ -78,9 +76,7 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { self.photoLibraryItems.append(contentsOf: photoLibraryItems) - addItems(mediaPickerItems) { addedItems, startIndex in - completion(addedItems, startIndex) - } + return addItems(mediaPickerItems) } func updateItem(_ item: MediaPickerItem) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index c9ea16b8..3b9a2f57 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -323,16 +323,14 @@ final class MediaPickerPresenter: MediaPickerModule { } private func addItems(_ items: [MediaPickerItem], fromCamera: Bool, completion: (() -> ())? = nil) { - interactor.addItems(items) { [weak self] addedItems, startIndex in - guard let strongSelf = self else { return } - self?.handleItemsAdded( - addedItems, - fromCamera: fromCamera, - canAddMoreItems: strongSelf.interactor.canAddItems(), - startIndex: startIndex, - completion: completion - ) - } + let (addedItems, startIndex) = interactor.addItems(items) + handleItemsAdded( + addedItems, + fromCamera: fromCamera, + canAddMoreItems: interactor.canAddItems(), + startIndex: startIndex, + completion: completion + ) } private func selectItem(_ item: MediaPickerItem) { @@ -465,14 +463,13 @@ final class MediaPickerPresenter: MediaPickerModule { switch result { case .selectedItems(let photoLibraryItems): - self?.interactor.addPhotoLibraryItems(photoLibraryItems) { addedItems, startIndex in - self?.handleItemsAdded( - addedItems, - fromCamera: false, - canAddMoreItems: strongSelf.interactor.canAddItems(), - startIndex: startIndex - ) - } + let (addedItems, startIndex) = strongSelf.interactor.addPhotoLibraryItems(photoLibraryItems) + self?.handleItemsAdded( + addedItems, + fromCamera: false, + canAddMoreItems: strongSelf.interactor.canAddItems(), + startIndex: startIndex + ) case .cancelled: break } From 65b8fb0b66476d17cdbf8b496044bb57e4c07982 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Thu, 17 Aug 2017 16:46:31 +0300 Subject: [PATCH 067/111] SEL-296: Change mediaPickerItem to imageSource --- .../Interactor/Filters/Filter.swift | 4 ++-- .../MediaPickerInteractorImpl.swift | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift index 5a6f2a19..0af54572 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift @@ -1,5 +1,5 @@ -import Foundation +import ImageSource public protocol Filter { - func apply(_ sourceItem: MediaPickerItem, completion: @escaping ((_ resultItem: MediaPickerItem) -> Void)) + func apply(_ sourceImage: ImageSource, completion: @escaping ((_ sourceImage: ImageSource) -> Void)) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 9a5911f2..ad4f6301 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -140,26 +140,27 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { return } - var item = MediaPickerItem( - identifier: originalItem.identifier, - image: originalItem.image, - source: originalItem.source - )// todo: use image from filters + var image = originalItem.image DispatchQueue.global(qos: .userInitiated).async { let filtersGroup = DispatchGroup() self.autocorrectionFilters.forEach { filter in filtersGroup.enter() filter.apply(item) { resultItem in - item = resultItem + image = resultItem filtersGroup.leave() } filtersGroup.wait() } + var updatedItem = MediaPickerItem( + identifier: originalItem.identifier, + image: image, + source: originalItem.source + ) - filtersGroup.notify(queue: DispatchQueue.main) { - item.originalItem = originalItem - completion(item) + DispatchQueue.main.async { + updatedItem.originalItem = originalItem + completion(updatedItem) } } } From 64ea366b22e8d7c11b149be2bf6280e453155768 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Thu, 17 Aug 2017 16:55:25 +0300 Subject: [PATCH 068/111] SEL-296: Fix filters --- .../MediaPicker/Interactor/MediaPickerInteractorImpl.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index ad4f6301..a92e7090 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -146,7 +146,7 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { let filtersGroup = DispatchGroup() self.autocorrectionFilters.forEach { filter in filtersGroup.enter() - filter.apply(item) { resultItem in + filter.apply(image) { resultItem in image = resultItem filtersGroup.leave() } From bffeaa4458ea5b95db405dbd2df9dcc18a1ec76c Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Thu, 17 Aug 2017 17:15:48 +0300 Subject: [PATCH 069/111] SEL-296: Improve codestyle --- .../Interactor/MediaPickerInteractorImpl.swift | 12 ++++++------ .../VIPER/MediaPicker/Module/MediaPickerItem.swift | 10 ++++++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index a92e7090..9e7feb3c 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -152,14 +152,14 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { } filtersGroup.wait() } - var updatedItem = MediaPickerItem( - identifier: originalItem.identifier, - image: image, - source: originalItem.source - ) DispatchQueue.main.async { - updatedItem.originalItem = originalItem + let updatedItem = MediaPickerItem( + identifier: originalItem.identifier, + image: image, + source: originalItem.source, + originalItem: originalItem + ) completion(updatedItem) } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerItem.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerItem.swift index 35354ed9..815508f5 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerItem.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerItem.swift @@ -13,12 +13,18 @@ public final class MediaPickerItem: Equatable { let identifier: String - var originalItem: MediaPickerItem? = nil + let originalItem: MediaPickerItem? - public init(identifier: String = NSUUID().uuidString, image: ImageSource, source: Source) { + public init( + identifier: String = NSUUID().uuidString, + image: ImageSource, + source: Source, + originalItem: MediaPickerItem? = nil) + { self.identifier = identifier self.image = image self.source = source + self.originalItem = originalItem } public static func ==(item1: MediaPickerItem, item2: MediaPickerItem) -> Bool { From cf98d3ff1e041e1c2e8dddb3482668f30eebc8cb Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Sun, 20 Aug 2017 03:11:43 +0300 Subject: [PATCH 070/111] SEL-361: Implement focus by tap --- .../project.pbxproj | 11 ++- Paparazzo/Core/MediaPickerUITheme.swift | 1 + .../Core/Services/Camera/CameraService.swift | 3 + .../Services/Camera/CameraServiceImpl.swift | 22 ++++++ .../Camera/Interactor/CameraInteractor.swift | 2 + .../Interactor/CameraInteractorImpl.swift | 9 +++ .../Camera/Presenter/CameraPresenter.swift | 6 ++ .../Core/VIPER/Camera/View/CameraView.swift | 31 ++++++++ .../VIPER/Camera/View/CameraViewInput.swift | 4 + .../VIPER/Camera/View/FocusIndicator.swift | 77 +++++++++++++++++++ .../View/MediaPickerRootModuleUITheme.swift | 1 + 11 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift diff --git a/Example/PaparazzoExample.xcodeproj/project.pbxproj b/Example/PaparazzoExample.xcodeproj/project.pbxproj index ad42161c..fa4f0067 100644 --- a/Example/PaparazzoExample.xcodeproj/project.pbxproj +++ b/Example/PaparazzoExample.xcodeproj/project.pbxproj @@ -375,7 +375,8 @@ TargetAttributes = { 251E57BF1E65651F0009A288 = { CreatedOnToolsVersion = 8.2.1; - ProvisioningStyle = Manual; + DevelopmentTeam = 5PHGGKL9UQ; + ProvisioningStyle = Automatic; }; 25A489B01E656A2B00CC431B = { CreatedOnToolsVersion = 8.2.1; @@ -758,12 +759,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5PHGGKL9UQ; INFOPLIST_FILE = PaparazzoExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ru.avito.PaparazzoExample; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 3.0; }; name = Debug; @@ -775,12 +778,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5PHGGKL9UQ; INFOPLIST_FILE = PaparazzoExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ru.avito.PaparazzoExample; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 3.0; }; name = Release; diff --git a/Paparazzo/Core/MediaPickerUITheme.swift b/Paparazzo/Core/MediaPickerUITheme.swift index d4f1d4da..ffa4da2b 100644 --- a/Paparazzo/Core/MediaPickerUITheme.swift +++ b/Paparazzo/Core/MediaPickerUITheme.swift @@ -14,6 +14,7 @@ public struct PaparazzoUITheme: public var shutterButtonColor = UIColor(red: 0, green: 170.0/255, blue: 1, alpha: 1) public var shutterButtonDisabledColor = UIColor.lightGray public var mediaRibbonSelectionColor = UIColor(red: 0, green: 170.0/255, blue: 1, alpha: 1) + public var focusIndicatorColor = UIColor(red: 0, green: 170.0/255, blue: 1, alpha: 1) public var removePhotoIcon = PaparazzoUITheme.image(named: "delete") public var autocorrectPhotoIconInactive = PaparazzoUITheme.image(named: "autocorrect_inactive") diff --git a/Paparazzo/Core/Services/Camera/CameraService.swift b/Paparazzo/Core/Services/Camera/CameraService.swift index 842968e5..e43deccf 100644 --- a/Paparazzo/Core/Services/Camera/CameraService.swift +++ b/Paparazzo/Core/Services/Camera/CameraService.swift @@ -15,6 +15,9 @@ protocol CameraService: class { func takePhoto(completion: @escaping (PhotoFromCamera?) -> ()) func setCaptureSessionRunning(_: Bool) + var isFocusSupported: Bool { get } + func focusOnPoint(_ focusPoint: CGPoint) + func canToggleCamera(completion: @escaping (Bool) -> ()) func toggleCamera(completion: @escaping (_ newOutputOrientation: ExifOrientation) -> ()) } diff --git a/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift b/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift index 411c0ba8..3c31b993 100644 --- a/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift +++ b/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift @@ -129,6 +129,28 @@ final class CameraServiceImpl: CameraService { } } + var isFocusSupported: Bool { + return self.activeCamera?.isFocusPointOfInterestSupported == true + } + + func focusOnPoint(_ focusPoint: CGPoint) { + guard let activeCamera = self.activeCamera, activeCamera.isFocusPointOfInterestSupported else { + return + } + + do { + try activeCamera.lockForConfiguration() + activeCamera.focusPointOfInterest = focusPoint + activeCamera.focusMode = .continuousAutoFocus + activeCamera.exposurePointOfInterest = focusPoint + activeCamera.exposureMode = .continuousAutoExposure + activeCamera.unlockForConfiguration() + } + catch { + debugPrint("Couldn't focus camera: \(error)") + } + } + func canToggleCamera(completion: @escaping (Bool) -> ()) { completion(frontCamera != nil && backCamera != nil) } diff --git a/Paparazzo/Core/VIPER/Camera/Interactor/CameraInteractor.swift b/Paparazzo/Core/VIPER/Camera/Interactor/CameraInteractor.swift index 1deab752..5848d90a 100644 --- a/Paparazzo/Core/VIPER/Camera/Interactor/CameraInteractor.swift +++ b/Paparazzo/Core/VIPER/Camera/Interactor/CameraInteractor.swift @@ -18,6 +18,8 @@ protocol CameraInteractor: class { func setPreviewImagesSizeForNewPhotos(_: CGSize) func observeDeviceOrientation(handler: @escaping (DeviceOrientation) -> ()) + + func focusCameraOnPoint(_: CGPoint) -> Bool } struct CameraOutputParameters { diff --git a/Paparazzo/Core/VIPER/Camera/Interactor/CameraInteractorImpl.swift b/Paparazzo/Core/VIPER/Camera/Interactor/CameraInteractorImpl.swift index 766fe2df..3deece06 100644 --- a/Paparazzo/Core/VIPER/Camera/Interactor/CameraInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/Camera/Interactor/CameraInteractorImpl.swift @@ -79,4 +79,13 @@ final class CameraInteractorImpl: CameraInteractor { deviceOrientationService.onOrientationChange = handler handler(deviceOrientationService.currentOrientation) } + + func focusCameraOnPoint(_ focusPoint: CGPoint) -> Bool { + if cameraService.isFocusSupported { + cameraService.focusOnPoint(focusPoint) + return true + } else { + return false + } + } } diff --git a/Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift b/Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift index 15d9cc4f..82f4b40d 100644 --- a/Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift +++ b/Paparazzo/Core/VIPER/Camera/Presenter/CameraPresenter.swift @@ -95,6 +95,12 @@ final class CameraPresenter: CameraModuleInput { } } + view?.onFocusTap = { [weak self] focusPoint, touchPoint in + if self?.interactor.focusCameraOnPoint(focusPoint) == true { + self?.view?.displayFocus(onPoint: touchPoint) + } + } + interactor.observeDeviceOrientation { [weak self] deviceOrientation in self?.view?.adjustForDeviceOrientation(deviceOrientation) } diff --git a/Paparazzo/Core/VIPER/Camera/View/CameraView.swift b/Paparazzo/Core/VIPER/Camera/View/CameraView.swift index 1cc23085..43d7fe15 100644 --- a/Paparazzo/Core/VIPER/Camera/View/CameraView.swift +++ b/Paparazzo/Core/VIPER/Camera/View/CameraView.swift @@ -1,5 +1,6 @@ import ImageSource import UIKit +import AVFoundation final class CameraView: UIView, CameraViewInput, ThemeConfigurable { @@ -8,6 +9,7 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { private let accessDeniedView = AccessDeniedView() private var cameraOutputView: CameraOutputView? private var outputParameters: CameraOutputParameters? + private let focusIndicator = FocusIndicator() // MARK: - Init @@ -34,8 +36,26 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { cameraOutputView?.frame = bounds } + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + let screenSize = bounds.size + if let touchPoint = touches.first?.location(in: self) { + let focusOriginX = touchPoint.y / screenSize.height + let focusOriginY = 1.0 - touchPoint.x / screenSize.width + let focusPoint = CGPoint(x: focusOriginX, y: focusOriginY) + + onFocusTap?(focusPoint, touchPoint) + } + } + // MARK: - CameraViewInput + var onFocusTap: ((_ focusPoint: CGPoint, _ touchPoint: CGPoint) -> Void)? + + func displayFocus(onPoint focusPoint: CGPoint) { + removeExistingFocusIndicatorIfNeeded() + focusIndicator.animate(in: layer, focusPoint: focusPoint) + } + var onAccessDeniedButtonTap: (() -> ())? { get { return accessDeniedView.onButtonTap } set { accessDeniedView.onButtonTap = newValue } @@ -103,6 +123,7 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { func setTheme(_ theme: ThemeType) { accessDeniedView.setTheme(theme) + focusIndicator.setTheme(theme) } // MARK: - Dispose bag @@ -112,4 +133,14 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { func addDisposable(_ object: AnyObject) { disposables.append(object) } + + // MARK: - Private + // MARK: Focus + + private func removeExistingFocusIndicatorIfNeeded() { + if focusIndicator.superlayer != nil { + focusIndicator.removeAllAnimations() + focusIndicator.removeFromSuperlayer() + } + } } diff --git a/Paparazzo/Core/VIPER/Camera/View/CameraViewInput.swift b/Paparazzo/Core/VIPER/Camera/View/CameraViewInput.swift index 992d445e..ebdf715c 100644 --- a/Paparazzo/Core/VIPER/Camera/View/CameraViewInput.swift +++ b/Paparazzo/Core/VIPER/Camera/View/CameraViewInput.swift @@ -5,6 +5,10 @@ protocol CameraViewInput: class { func setOutputParameters(_: CameraOutputParameters) func setOutputOrientation(_: ExifOrientation) + // MARK: - Focus + var onFocusTap: ((_ focusPoint: CGPoint, _ touchPoint: CGPoint) -> Void)? { get set } + func displayFocus(onPoint: CGPoint) + // MARK: - Access denied view var onAccessDeniedButtonTap: (() -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift b/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift new file mode 100644 index 00000000..35394550 --- /dev/null +++ b/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift @@ -0,0 +1,77 @@ +import UIKit + +final class FocusIndicator: CALayer, ThemeConfigurable { + + typealias ThemeType = MediaPickerRootModuleUITheme + + private let shapeLayer = CAShapeLayer() + + override init() { + super.init() + + let radius = CGFloat(30) + + let circlePath = UIBezierPath( + arcCenter: CGPoint(x: radius, y: radius), + radius: radius, + startAngle: 0, + endAngle: CGFloat(M_PI * 2), + clockwise: true + ) + + shapeLayer.path = circlePath.cgPath + + shapeLayer.fillColor = UIColor.clear.cgColor + shapeLayer.strokeColor = UIColor.clear.cgColor + shapeLayer.lineWidth = 2.0 + + let shapeContainterLayer = CALayer() + + addSublayer(shapeLayer) + + bounds = CGRect(x: 0, y: 0, width: 2 * radius, height: 2 * radius) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - ThemeConfigurable + + func setTheme(_ theme: ThemeType) { + shapeLayer.strokeColor = theme.focusIndicatorColor.cgColor + } + + func animate(in superlayer: CALayer, focusPoint: CGPoint) { + CATransaction.begin() + CATransaction.setDisableActions(true) + position = focusPoint + + superlayer.addSublayer(self) + CATransaction.setCompletionBlock { + self.removeFromSuperlayer() + } + + self.add(FocusIndicatorAnimation(), forKey: nil) + + CATransaction.commit() + } +} + +final class FocusIndicatorAnimation: CABasicAnimation { + override init() { + super.init() + keyPath = "transform.scale" + fromValue = 0.8 + toValue = 1.0 + duration = 0.3 + autoreverses = true + isRemovedOnCompletion = false + timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerRootModuleUITheme.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerRootModuleUITheme.swift index eeb3196a..de8cd693 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerRootModuleUITheme.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerRootModuleUITheme.swift @@ -2,6 +2,7 @@ public protocol MediaPickerRootModuleUITheme: AccessDeniedViewTheme { var shutterButtonColor: UIColor { get } var shutterButtonDisabledColor: UIColor { get } + var focusIndicatorColor: UIColor { get } var mediaRibbonSelectionColor: UIColor { get } var cameraContinueButtonTitleColor: UIColor { get } var cameraContinueButtonTitleHighlightedColor: UIColor { get } From 9c7a0467a89704f059ec0a0c73cd1bce682579db Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Sun, 20 Aug 2017 03:21:42 +0300 Subject: [PATCH 071/111] SEL-361: Add opacity animation --- .../VIPER/Camera/View/FocusIndicator.swift | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift b/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift index 35394550..5498342b 100644 --- a/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift +++ b/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift @@ -52,13 +52,15 @@ final class FocusIndicator: CALayer, ThemeConfigurable { self.removeFromSuperlayer() } - self.add(FocusIndicatorAnimation(), forKey: nil) + self.add(FocusIndicatorScaleAnimation(), forKey: nil) + self.add(FocusIndicatorOpacityAnimation(), forKey: nil) + opacity = 0 CATransaction.commit() } } -final class FocusIndicatorAnimation: CABasicAnimation { +final class FocusIndicatorScaleAnimation: CABasicAnimation { override init() { super.init() keyPath = "transform.scale" @@ -75,3 +77,19 @@ final class FocusIndicatorAnimation: CABasicAnimation { } } +final class FocusIndicatorOpacityAnimation: CABasicAnimation { + override init() { + super.init() + keyPath = "opacity" + fromValue = 0 + toValue = 1.0 + duration = 0.3 + autoreverses = true + isRemovedOnCompletion = false + timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} From 94e96e3146684c87880705db240935c857d21e33 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Sun, 20 Aug 2017 03:24:46 +0300 Subject: [PATCH 072/111] SEL-361: Reverse example proj file --- Example/PaparazzoExample.xcodeproj/project.pbxproj | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Example/PaparazzoExample.xcodeproj/project.pbxproj b/Example/PaparazzoExample.xcodeproj/project.pbxproj index fa4f0067..ad42161c 100644 --- a/Example/PaparazzoExample.xcodeproj/project.pbxproj +++ b/Example/PaparazzoExample.xcodeproj/project.pbxproj @@ -375,8 +375,7 @@ TargetAttributes = { 251E57BF1E65651F0009A288 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = 5PHGGKL9UQ; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; 25A489B01E656A2B00CC431B = { CreatedOnToolsVersion = 8.2.1; @@ -759,14 +758,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 5PHGGKL9UQ; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = PaparazzoExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ru.avito.PaparazzoExample; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 3.0; }; name = Debug; @@ -778,14 +775,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 5PHGGKL9UQ; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = PaparazzoExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ru.avito.PaparazzoExample; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 3.0; }; name = Release; From 308212b674261dac01384c34087124445c046509 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Sun, 20 Aug 2017 20:34:25 +0300 Subject: [PATCH 073/111] SEL-361: Improve codestyle --- .../Core/Services/Camera/CameraService.swift | 3 +-- .../Services/Camera/CameraServiceImpl.swift | 10 ++++------ .../Interactor/CameraInteractorImpl.swift | 7 +------ .../Core/VIPER/Camera/View/CameraView.swift | 17 ++++++----------- .../Core/VIPER/Camera/View/FocusIndicator.swift | 5 +++++ 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/Paparazzo/Core/Services/Camera/CameraService.swift b/Paparazzo/Core/Services/Camera/CameraService.swift index e43deccf..20dafce4 100644 --- a/Paparazzo/Core/Services/Camera/CameraService.swift +++ b/Paparazzo/Core/Services/Camera/CameraService.swift @@ -15,8 +15,7 @@ protocol CameraService: class { func takePhoto(completion: @escaping (PhotoFromCamera?) -> ()) func setCaptureSessionRunning(_: Bool) - var isFocusSupported: Bool { get } - func focusOnPoint(_ focusPoint: CGPoint) + func focusOnPoint(_ focusPoint: CGPoint) -> Bool func canToggleCamera(completion: @escaping (Bool) -> ()) func toggleCamera(completion: @escaping (_ newOutputOrientation: ExifOrientation) -> ()) diff --git a/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift b/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift index 3c31b993..7d1ec550 100644 --- a/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift +++ b/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift @@ -129,13 +129,9 @@ final class CameraServiceImpl: CameraService { } } - var isFocusSupported: Bool { - return self.activeCamera?.isFocusPointOfInterestSupported == true - } - - func focusOnPoint(_ focusPoint: CGPoint) { + func focusOnPoint(_ focusPoint: CGPoint) -> Bool { guard let activeCamera = self.activeCamera, activeCamera.isFocusPointOfInterestSupported else { - return + return false } do { @@ -145,9 +141,11 @@ final class CameraServiceImpl: CameraService { activeCamera.exposurePointOfInterest = focusPoint activeCamera.exposureMode = .continuousAutoExposure activeCamera.unlockForConfiguration() + return true } catch { debugPrint("Couldn't focus camera: \(error)") + return false } } diff --git a/Paparazzo/Core/VIPER/Camera/Interactor/CameraInteractorImpl.swift b/Paparazzo/Core/VIPER/Camera/Interactor/CameraInteractorImpl.swift index 3deece06..b203e4bd 100644 --- a/Paparazzo/Core/VIPER/Camera/Interactor/CameraInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/Camera/Interactor/CameraInteractorImpl.swift @@ -81,11 +81,6 @@ final class CameraInteractorImpl: CameraInteractor { } func focusCameraOnPoint(_ focusPoint: CGPoint) -> Bool { - if cameraService.isFocusSupported { - cameraService.focusOnPoint(focusPoint) - return true - } else { - return false - } + return cameraService.focusOnPoint(focusPoint) } } diff --git a/Paparazzo/Core/VIPER/Camera/View/CameraView.swift b/Paparazzo/Core/VIPER/Camera/View/CameraView.swift index 43d7fe15..5b0af2de 100644 --- a/Paparazzo/Core/VIPER/Camera/View/CameraView.swift +++ b/Paparazzo/Core/VIPER/Camera/View/CameraView.swift @@ -37,7 +37,13 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { } override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + let screenSize = bounds.size + guard screenSize.width != 0 && screenSize.height != 0 else { + return + } + if let touchPoint = touches.first?.location(in: self) { let focusOriginX = touchPoint.y / screenSize.height let focusOriginY = 1.0 - touchPoint.x / screenSize.width @@ -52,7 +58,6 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { var onFocusTap: ((_ focusPoint: CGPoint, _ touchPoint: CGPoint) -> Void)? func displayFocus(onPoint focusPoint: CGPoint) { - removeExistingFocusIndicatorIfNeeded() focusIndicator.animate(in: layer, focusPoint: focusPoint) } @@ -133,14 +138,4 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { func addDisposable(_ object: AnyObject) { disposables.append(object) } - - // MARK: - Private - // MARK: Focus - - private func removeExistingFocusIndicatorIfNeeded() { - if focusIndicator.superlayer != nil { - focusIndicator.removeAllAnimations() - focusIndicator.removeFromSuperlayer() - } - } } diff --git a/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift b/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift index 5498342b..a06db44e 100644 --- a/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift +++ b/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift @@ -43,6 +43,11 @@ final class FocusIndicator: CALayer, ThemeConfigurable { } func animate(in superlayer: CALayer, focusPoint: CGPoint) { + if self.superlayer != nil { + self.removeFromSuperlayer() + self.removeAllAnimations() + } + CATransaction.begin() CATransaction.setDisableActions(true) position = focusPoint From 61664ec58593d0bd26309a3d2583663ac7d22480 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Wed, 23 Aug 2017 12:45:45 +0300 Subject: [PATCH 074/111] SEL-361: Change focus indicator logic --- Paparazzo/Core/VIPER/Camera/View/CameraView.swift | 13 ++++++++++--- .../Core/VIPER/Camera/View/FocusIndicator.swift | 10 +++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Paparazzo/Core/VIPER/Camera/View/CameraView.swift b/Paparazzo/Core/VIPER/Camera/View/CameraView.swift index 5b0af2de..643b67a5 100644 --- a/Paparazzo/Core/VIPER/Camera/View/CameraView.swift +++ b/Paparazzo/Core/VIPER/Camera/View/CameraView.swift @@ -9,7 +9,8 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { private let accessDeniedView = AccessDeniedView() private var cameraOutputView: CameraOutputView? private var outputParameters: CameraOutputParameters? - private let focusIndicator = FocusIndicator() + private var focusIndicator: FocusIndicator? + private var theme: ThemeType? // MARK: - Init @@ -58,7 +59,12 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { var onFocusTap: ((_ focusPoint: CGPoint, _ touchPoint: CGPoint) -> Void)? func displayFocus(onPoint focusPoint: CGPoint) { - focusIndicator.animate(in: layer, focusPoint: focusPoint) + focusIndicator?.hide() + focusIndicator = FocusIndicator() + if let theme = theme { + focusIndicator?.setTheme(theme) + } + focusIndicator?.animate(in: layer, focusPoint: focusPoint) } var onAccessDeniedButtonTap: (() -> ())? { @@ -127,8 +133,9 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { // MARK: - ThemeConfigurable func setTheme(_ theme: ThemeType) { + self.theme = theme accessDeniedView.setTheme(theme) - focusIndicator.setTheme(theme) + focusIndicator?.setTheme(theme) } // MARK: - Dispose bag diff --git a/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift b/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift index a06db44e..5b92a604 100644 --- a/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift +++ b/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift @@ -43,11 +43,6 @@ final class FocusIndicator: CALayer, ThemeConfigurable { } func animate(in superlayer: CALayer, focusPoint: CGPoint) { - if self.superlayer != nil { - self.removeFromSuperlayer() - self.removeAllAnimations() - } - CATransaction.begin() CATransaction.setDisableActions(true) position = focusPoint @@ -63,6 +58,11 @@ final class FocusIndicator: CALayer, ThemeConfigurable { CATransaction.commit() } + + func hide() { + removeAllAnimations() + removeFromSuperlayer() + } } final class FocusIndicatorScaleAnimation: CABasicAnimation { From 3299cc957d13410e98685fc10b64facd2caa5bb3 Mon Sep 17 00:00:00 2001 From: Vladimir Ignatov Date: Wed, 23 Aug 2017 16:13:33 +0300 Subject: [PATCH 075/111] add voice over support for PhotoLibraryItemCell --- .../Core/VIPER/PhotoLibrary/View/PhotoLibraryItemCell.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryItemCell.swift b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryItemCell.swift index 0bdda7e3..15b334de 100644 --- a/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryItemCell.swift +++ b/Paparazzo/Core/VIPER/PhotoLibrary/View/PhotoLibraryItemCell.swift @@ -10,6 +10,7 @@ final class PhotoLibraryItemCell: PhotoCollectionViewCell, Customizable { override init(frame: CGRect) { super.init(frame: frame) contentView.insertSubview(cloudIconView, at: 0) + imageView.isAccessibilityElement = true } required init?(coder aDecoder: NSCoder) { From e407ff3a4a989a94fc1816971073671746c2cae0 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Thu, 24 Aug 2017 12:44:32 +0300 Subject: [PATCH 076/111] SEL-387: Add blur icon --- .../autocorrect_active.png | Bin 359 -> 721 bytes .../autocorrect_active@2x.png | Bin 405 -> 1703 bytes .../autocorrect_active@3x.png | Bin 776 -> 2701 bytes .../Contents.json | 6 +++--- .../autocorrect.png | Bin 365 -> 0 bytes .../autocorrect@2x.png | Bin 405 -> 0 bytes .../autocorrect@3x.png | Bin 771 -> 0 bytes .../autocorrect_inactive.png | Bin 0 -> 727 bytes .../autocorrect_inactive@2x.png | Bin 0 -> 1953 bytes .../autocorrect_inactive@3x.png | Bin 0 -> 2982 bytes 10 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect.png delete mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect@2x.png delete mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect@3x.png create mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect_inactive.png create mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect_inactive@2x.png create mode 100644 Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect_inactive@3x.png diff --git a/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active.png b/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active.png index 6ff493dd05aa9a35799f92ac064bb55d3368ade7..0ab8cc16c1eb8bd3883286146bba0d10bb11c04a 100644 GIT binary patch delta 698 zcmV;r0!97j0?`GKBYyw^b5ch_0Itp)=>Px%i%CR5R7ef&RzHjkQ5gTdbvc_{q0opw zm%B!&6oM$6LV^M(D`Wqp3X zH^;fmd$Tj=B3Jm5Z|3bczdvu@+xcdI|FP&-AhA%^{N4*xb4Ii0|;zb1^Z3l@o;L&J=D zJgTW{!U(${aevc>DA-|M>#8S=u;O%y{5`&FefWeIUSkxD^+mGYouBkPS_aOP2>a) zxc3=G(L%~qdzDruV#e!;e4asnCs4Ct1~+RVG3uk7bELGiP3++EYIQd84^=1UBo4XG9NZs2@{NB18+MpO z?b!hH1M?xp-}21;#A+V5GgmNQGf&g-2G6-s%rjUL4eO;@jiY@u+sLz?QN<+d&v?Gg gdXuJYUtO$+pYjXgSbr1+rvLx|07*qoM6N<$f>e=LMgRZ+ delta 333 zcmV-T0kZzl1?K{gBYy!dNklSqYIJ?OPAi$oAft%e0XrfW_<$vH4-)YgnXEjd|Z))RA zO!hJK-x`_%tOL(!#ff9+6A|++C}R8a!DZPYHROR~{OdFaPVz!>M9f=>(#{*3ebytX zbF9Og@fed(j%8m3=Ibe-0b&M4l$)@Dwk<$Wemf}U5-LBjo(e>DDB`!ztI)PS6jGBq z7uqgoIcd;&+(!4L;yqV^j{O5shsqP1L1m8(pgKCeT0v!hD)t&z-wZ0>QH;Bw7Vug^ ft>9JD^MAEB?sLZocR)DJ00000NkvXXu0mjfAC;U& diff --git a/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active@2x.png b/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active@2x.png index 4e2648b9ad39e2cc76d766ce0eda0f506974b9cb..b02ad442a39dafce1a482b51ddb3b7dc7cb9dbe2 100644 GIT binary patch delta 1698 zcmV;T23`4;1E&p;8Gi-<0027t*>V5?00DDSM?wIu&K&6g00v=6L_t(&1?^gGs1-#R ze&%?urja0cDYZ!bkqFtJs}b}8hGimG{J1GXBx(KVM}|lk5lB%HltdYrM56uc+~Y;O zk(GuZiBVQaf>>a;Bn`AL{4Bj^dfuJg**VwKJv;kx&X3Ludw=Gg*_rowo|)a**_{Kl zt=$9d9%%PKy9e4mP}>82dOhU$Usy#yLb2805WmT$nu=G^^HIeBF*`%tD=47{z3P)@ z@o1jloq7(2u%E)NA13YsMqw_Nsjr5W7m{s`;C_7@uHthl=n7Q~hUwHWJ%bM1gQe>G zFke_wyx;*n5r3EPB^68yRh*@`y956!4lmj8&cxD!32qaT)#0 zMq)3}Ul@#}8O?>vOPScTpsutj(25nT@D4QY4f$6&L9N7cC8KRjd?^E`GBK$uZ3?vF z1ef(Q6!4hkuCRf#vCPo5CS`IiGHD}i3$kMF;fn6zn1AgeXVaky+wibjGopq*{T@4L zby!EPhQ)O|Xv83mPv$RJX5=9pCixpe_yd@Zd6=!Pj7aCO?!*b~W1ahgdJ5+_^4%2u z!-(xXPv(0`m-SeR?XzMPC3IkEL$N|yIz;M98!_oHjS4UCu|YnkpL_)?yiJmKgxoHZ zd@pC@-+vNwmAcXfoUg@9tj&R+(@%baD|!_PM)#oHy=!?ne(TLs+DO~j)<9zCWcl2~ z{d#72-;N7-DZI}~DjUf@y@MV2Xc+Gzm!GqFwH(W;JNpQi^(ZbIg`5!!9L05bGApq# z#?c^-#1;&}%4pdIn`h{pfQQ20oOo7pd3Z4+FMk9vB$mW9Hiv%vyiN&_O>n=yog=%^ z!QRNr&WwsvnA$b5;`o9km>Dd+U~?&NCw8>Zb%s)jDY3zKE@R!y3wA0?C-@^ho=e9UEuzLNd9R3UY;nwL z>wjfiCfLFug{2dG9?ui2E5sY5wKr)_93j|9zyi*WKVKf!Q< zmkN01CM8~rU6A0io<=@115WbXt)e*-RlQCSY~f&RCKn>YEs^<*uzX>RY%doX(Lx|9 zYhG%w_d0+sH>dbo*GKUEwz$l3O<%~hR;BdJ$;FDaiLcDhE#Geh~7Jqs?{<0u0H(OZWNiG=p)rPo) zo2=g|=Xc!bK*!vi!g_9U!5XJ*fGVa~zg3RQ!R6L~PjbP)FE*COE!J<9Q_CUd)__lP z!3xKmSPwXUtK67FEw>T*_y~5Ny?=|Kb@H|&Qb=*qU@x*QYoE!sxSo@obgw$h04KvB zUEENKwn6K~Ew;>HM;{p#C7cR&1WjOt=Mo_lQOU)2IVHgI6FiE0M~dGK z#WWt|7RdYr82MHaOu-ftF2c#f`~;i7DQvazt9XrXD$!?JTU?kUSb`bb)_>sd6LG$w z=OI{K4SRC2#D(h!$z9>g9qRlnI z5Dvn{)BFVsz;d;Pzj7E;{4QOjXo)Qtv26)C#3czmm9XBm)%NQSoWUNRnCG1Qfd92x zoy%sL1Rc=hxZkji-UF?o0bAr1Cr&#ZFaIS<&^nWL_+ov%VU&toy$v#Eli&-=yDo&UlXWLzDyP217;@7@}u^GB(Gk;W*H=AyXn8Z|$$sw$KR5(o* sa*2Px;TbZ+`4=1HrEja9+nG7 z?rr)0r~d!>cbm#2yKXCN_J3>BdaXI(fQD)2?ywgo*FM}hGp~QU;;W~PnKEYl{ArH* zQA-czd??tmdD`Z!FA{k#oRjxg*PNVCYwI;r@Y?0fJ4?^lsZFhR`MoooZIShsR<$>i zOj8ewNUUwYq;=Qqo8RSmZ??ax*K@M^y+r$5Wz?BxNf*DKtJ`%b>`jk|SZQs2^Cq+G zOKm+I+{bP%UXY__*LX^Joz}VW#>|ZdeM^_y|9|%&-o|D9&+vP>pO_ZDk?ueH RD_0j3!k(^vF6*2UngGQm!5#nr diff --git a/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active@3x.png b/Paparazzo/Assets/Assets.xcassets/autocorrect_active.imageset/autocorrect_active@3x.png index 5865485ffdadd5790a9dcb90750e1f4d58937ab4..8e0b62de620880239d9ab904843795342d76acd0 100644 GIT binary patch literal 2701 zcmV;83Uc*{P)Px9u)EPlNduOu zsi>4#gMS#KF+oN7`h%cAObMFzcFRXgOBHPsE7b@{M2uE~O+f`QD5aG`yLL%fhiX*2Ln9>aW?8nl%9&GssAsD{wPd_IlP z4hZ@-CF((%M16Fb`HhN~&Mm*tn|LN2pE>(n}F8x6s3UGFtK@ zP-;dOwf3TA&qK@pdzbSHOKucabGM3{d@MxXhSO%KQ(uKIThwL*=SrTES~-Q@%14o* z`=Hq@rP~PNgTY3S*0xze9?4rmE2q#0>2_keFs7HGH8fzh1Y3iyils{)$y-7zpU_SI zGDN;Srj?=VAn;vp#QV*iXu;RL;kOSYuar?TE7yL}QX7y3oI1Kq_1N{83+zFwe&i)=sqM9*e!9 zZ$ab@RxGX4;_FfFm#l0CGbThILfLXjpSu+!^k$w$hv>HuW4dCznFeS%88fQl>0IUp zwSp_lrB+5m&(Rb*mzEmcms_W0;pE?ShzPF+!#G=HL(gEQ`EqBGMJ4&5g0fI%)Z~K= z>IR0KE%yp(h`s`j3oV`ogS!UW@(S~BEAHY*SyHByZQH^z40beQFW;`C$sY!vpJLM= z@LG&8vYDf3@mh3Iw<#T^Yx*ea{JDx}=H@4KlTQMRC$ODpu`%dI2Fxd|m~W@W8zHv7 z$0!#uvmy5LzN1uv zXEwAtl-lGgk)m7i%ZQzgF66Hra(M~o(V~TzEi%!Axr;1v4I1gb9+bm~2{tW$B-zhl zwnB$3Xpw^Y5^kifQL+5ou`-BOM*$8U>6Ra z&3l39999G4YzwEMUt`wcZYx#>M=%LC+h&h(u%nKiU!j{k6|LkO7N+oy^ye|7##X!x zj$kqbY(O0~;UHW*nqQ#}Z2;tx6~2x5@zJeDol>A1V%umT5Dvn{qxl=&-OI<}S6hZg z(Ux!kW9FG;pS|9gdU|YDkLMgxBWd8E5F+ljQ{P{8|OA59_~U9 z@j0k@j#azCJ?Ir?uNA9;BbWudZF4Z;BpjX0U1-L#Eo}|DRfj6$Ct*+Gn39mY&_tK0 zI$yxTtlcVB9XG_T7e#80srk4Gy@}s}3zQ42S`D5BbX@>r6_~RVV&7ubDjbEYgSiQP zBv_vzL%XQS%Ft?C`20+i!z7HU?X@V;)NvfU32kCOg0ImudUzc5mirXppcmd~t|=L~ z39Z-8G3}*3b0{T-@|`XmgiF*k*J#=}j@^W&*doP!&DBcm?zjm}^opwD z80A;l)m${!RNP#J{+Cr7)4MFc%1)e%%{3)ASD~M_>N9kvbFsOmsZOJvZc_wb5Kt@zRB*%8bWxbFTy~q?qyqzl8g+tVYqi}UFH=)Cd^?Kve zaF61H4w#C~8e%_9fzTXPa(CBVXr>>=sxfqRELN33680pHDf#&MinsW2eBI8q>NYgL zsK5>5Z|vIkq4rc1r02$(ZBZCIH-z(9^Jb zULG;FZ7voH_S>Nndm+}j7DC}9+&q{cq5I4ceC56#!(-@rh<$76s+2WlW_B3 ze)?zuBEzCq(7)+c@e_Oqi+wDs^1wGv=taHQi;2=qV#*N@{ z1-k`=1ibWfN(f)>jWLns-5voKe`~t^2`#ky&F}E{ngj*b;SDgKO^6p83Nfuq0CCM2 zmxQE=h5QOF7zWHO_$uC(0Ouz3*Pls<7ZVDx+!U)^unPyz=C6;A0^7kocwg5;_(~?t zd)$tNu!5j{ zoyF_E$(MrVH&Msg9m?Gfkw50^S(OfTNey z2npdUG%rS(SEI~%9ZxlM1JXC-F~-lnMMqi6zw{WT?;|uN8dk^hBe@fswD1QY!anS~ zTa)F{j!Vb33$bticMBZG>=4GuYcPb%?IF{Ag%-X|eh*sO&1gv%$LJ>oI9PzLV%{*zCud+v)!I zPoT)JX7*dq1#fXew}G`-TK0OhvOes6kAgvk%D5LF+dX(#^SM;{Gdj6ELPrUP54_7o#ruQ)m*@-}pM*^w z>JOJ{g?;q_#Gc3YC&c9uHW~n_MFBn`u0Wb$|9^(n{~1>OXIKkk z0NHE6B0zBn`VSTX3PQvo3brAs-w0N}9;P0s0AeQG9H0o$5Qrdz1d70w0X4$eXJCea zl|68576m%ev?Rzcn1N;0XX}OwaseOv-v47PG<;N-e}TRH!&_cmg_mo;Mc+2Ry3?4c ze^&w93j5mc0%o%&7^;gF}LjYcE02(s;pk;x3wW=-P2c3YpW;yi#oGUak|#lA4fu$zi-Y;cpj*d z6QZ_J)6mqr7Gs}` zK9@&H)m01EI0F;g9$W4r`E$p!lbfnOClsgJ%=?gfqejv!wyl}%NB`$-}3)~mJ`XYJnE_1mYfX@=(dSDUl$y*Hopn)xsvhkB`b S_B5cw89ZJ6T-G@yGywp01BS5x diff --git a/Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect@2x.png b/Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect@2x.png deleted file mode 100644 index f09a65756f100e0f191f24b1146fa904b954e802..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 405 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw3=&b&bO2Ij0X`wFK>Gjx|0X6T#>U1F62dSA z1E5G~Xedww4y>%Kfb83Ms>*;m1xtebf*Ba+{%rl+zNa#^i}~s8oi<`xixTpHl7Bs2 z978H@-Mx03>xcpm%Y`HNwtWBd|NOg6Ws+UD6*l|7wQ0T9oNz$HG;?>@3zKUfZk(Cd zzg_Xw)5c60Gk*RwNByX!2Xj6YY}q_*^VS!Mycf>N`>ShCPN=o@nkjhga^{_-XYACb zR=fP(8P2xIdP}R?n@OgrheagTwqMe^Yxd3W^1L_OU)Ae5S^Zw3eXcU<%(JA6U(fA2 z6!xY^M69&7zIl^b_NBHS4(?+&7ca=sv+S<=d;1aJ>WwS9Z%^K^W4mN`e#_A{tG51| z@@1|4{My(1d1|9gTAH9?f<`f5O3qM f{%82T+)qpk-$?hL{gtZ=3HW~n_`2jv5u0Yzs!NC{|Ouzub29n6! z|Ns9(L`+Rhf#NDEDnJHAC6EhbSX)~INec@LAZc!H4itndfG7i!4Gj$tGl8VGwl+i! zkOVpaB5r1824nzL!u5VKOkM$WqDe`RUoZp9s?XL97vusy_Pzhd_{cE7kiD+_!`fP2 zU4@s%-=c4uU)?#IX_r|6+Y0-Bfw$i+C$U;hJozEJoq>VzzNd?0NJZS+8^`k=8;H0i zdKImBwZnSvce|(i{%`JOOe|`Uyf|S|{OTV%oIj%lrY}?YzEy3X&K-kFz~14KDm;V-Fd$9^Ag&f zw=K=St8p+-@%Y5od8j8{+JD3XlU55JLQS?9N5YtfPUYh%jdw_Uy&cULEO z8gtpU74_RrG<`btFKk(_QQ15uk#CWL3zoZG{-F9b=H@c?2BBR%7iI=t)7d_Ark%b} zBpIdWd^+`6lwPsaQlr%1oj&*7?wx$+s(bmA*Px%kx4{BR7ef&RzGMHQ5?=)5)n#x zy}NsNk;rvi^&M~==LPdp=b6oBpQY33V2x}rz@2zJKHg|Fc3IO2ZDZWeTb}1_B8M!?8p8^I(EvM; zLv0JSBjkf-vw5gvZ0mq3m5L4V55PLnX!MD1N-q1Xaf}#ZS^qDARW0E`^3XmH66Bb` zECpeV>bYEQ3W9wIuZ9tcA(osl?7(d)7K=mue*Zo0-Cin{S_l&(hFEgQ4Z{bHWA<;w zY9|6g=LWFq33A9iH>7uor+5@1E(G4`F-}nAkV}n|>e*i4R^thM1!xyr_wp&P7T~;Y6vZfqBI8T5w>ds zN8rB1&@7Nk4Qi<n>cf;AZvfKeFJjH zr3SV9x>hUxt2`bAT7**|ad>za{U77s#R^Uzeut|%A9!Mj6`Yk*b200dN~IWtY7|0l zp+BwKde%5bj4-X0%jM`GH?8{z(K(1$AvA(KzjPGa!Mto1Vk)M;vPx+TS-JgRA>e5npt%z9YavomM+ zY!kDSoc-qd{r~@&Idf*t+3T)Lhjb65dm!Bd=^jY;z*Rj^R#vuFuB$TtuZi;V@~28l zO2*`pc&c?~q!JL3i!w4YKD&JRa*p5-HX3e^u*gh^tEcPNJ`KXMf zToRW&E(ua+pK9Y|1o4vJ`2$lQT3FptB?<^=N_=ChUE}3mr zLqkL50|Nt}Iu!!=2~03k`9;L8)0`2zj_JBnr?o}gNkoT8FI0K6B~AVhx=u1}Ac7IQ z7_sj|R7Oqi+Yb=?axCH^4w03V&sKF7kp4|NBUT%~`ANTrWkp8p9tdgQjXZx>#v`ce zG^%=;;)9LJ&GFjVmOCS0+bC9j2u|ej^AlumQ+1kInC5cd{vE50JD<+Fk@m8p*GN7y z<9FK6J9X;Rpu1ioaXku&_=vQH>{cxnvO`=;O~r~6sVi-yt)#^lKDS6bSs`inFr%{q zf}32#o@dMWxhriH<-R>VY0{)KR9>ta+*w#y*h>B}BL{IRC1pxiRaMO#9v(i5;5>!5 z8?h_mV0#vGPw%3_(^82M5Ale*yREJ5PtQgMg$YmThK7c$OP4O~a)~_-!Bqy>o>i2O z<5Ed{3x;5MHZmwo*g;=k-+FMC3l=2990V3|FLoMWdsblz4;K{`9Y*Z4RLtfoYj1B4 zU$}7LuxGiaWOlwGx_=X!!&qE8sY!G$&)AVmwaO< zQ5AhpX_;Oe_jV#!_RB)%$GW<@H>3O~oD`qpTO0|NU?wuPeJ@(9$}4$fY!f073h}(W zgXiU6l1_Gz4G37r5Ij+QgSCp8^qwCaUsHPe^yvjGA~JQI!#o5wL14ch0w!&k!o6%Y zWrXKTee6BO#l_oudwats?cH^JP3cf5v;ilP!#t0Sx7<#Hh%1a%<$J2)U=3wGrK_u} ztHR;%33@qK(6X|!mNJ$1S|j^tz|>wcIy(BXH03r5=jP_lXlZF_vvFlT!c65gS}Z_~ zsS+!q6jr0eg@bUBxJ|GUEcY&p$1@s$UVc@_Q)S(l!bLdQU|R_u92|TO=lKEv@G;J~ zU)z8sqpn~Iw(^y)I2pUP5=@VZU?;FLC;geLeaEwIY&WNP?WEjTFb9Nc>d%x$^su~= zNy$Pbc-IJf6$dGrskmLE+JCc*1#=pbH;g(QWC2Oqp*7t{=rsXJ6AA3#Rpa zmxlwc_;^L#9NgSI`Pc}arakfOl~=3d8n}<0qoX1*&6JF>V4i#B+9^lCb{!ut%g!sZ z;c#<{$H!POi$vsk#|WQ`=Q!wcMD}Y8H#eOZ%Nh#?;Iwwa_O4jR$IBw}CXMaprt@N1 zW5ED)X(#NZ%XECateb4v;gI0C|8N&-bw$ z2bs^g_SJmP9{)Rg6YkzI2{qkjPyeJ zSB?otGV2JYU@M;^ijy&fm0(`fodjUJ_D=kbp#P$vvBV0NVCp>KBAj&E#99d!hk1E< zn=k_sr>Ae_&EXD-ry>ZJU@EKu#l;HR`ZX<}A-?9F16UsyZ|4aL>NOhqdKRO-%{CT1sey zH5sw$FAr}cyvUtTe1MVq68i#@#jV5}2p+sCzr z$ImXEmxxW8U?IV17V$arC8Db#?|kX=OAe7TlJ7F&ueY|gChdhoQUz;q>~+f+wHHV~ zf$+#ZsLQ=9LQhb>pV_d5Tg81O^=f8Z7KmUsI#XT#s3s?yyRan)D#4aY9pYWYyRkCG nf5jfK-8qS$bV&EWzdi5|lKs~q_-kTl00000NkvXXu0mjfF~p~b literal 0 HcmV?d00001 diff --git a/Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect_inactive@3x.png b/Paparazzo/Assets/Assets.xcassets/autocorrect_inactive.imageset/autocorrect_inactive@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ddba381a9b3024337951bdc9520d2f22517e78b GIT binary patch literal 2982 zcmV;X3t9AuP)Px=U`a$lRCodHoqudrR~g6YFDRC_8m;9=TiS*nizv(?I4J5E$>?+<>M(9m<77jp zVa{wZO0vmx@eke9Y>SzbsSC|Er^|3?*rE&k0mThv7_FHdDWxqGHbm$T`@8k?#M?LL zKD}@I-uIsS-uvo%lQ;J~&-w9upKs57&bjBFdpm1Z?#MBaV<5*sj)5ElIR-*D#4nbsuvu~6s70!0!Kix)3m0Lc;>l94PmE2NB+P0~y()UhSH zzP`SEWMt$3EY+G!5t|3(XX>Pkl$AP49~&{2L@!ygWX{0Az&-}MRf?%GNdA=k(2tpU zQda6nT~i;IbU|Ezm^kLlne#K`kC{42L-4I<&z^mzx3{;otgNidqm1{m-mONWs+P3oNUD*gyV%8X6kzOvaFt z_kzmLleVNyrH@S1PDKKwuCA_fXlUpdeP8tA4NKHqHAkwi-(7Y4?*(#O#&e4LQzrC+S8{`pEC8y(#)gl7zkgX z9SJ3ggLYn?J$v>w?d|P@31tL{$U_GtKkh;DFuG5qO0w`KWu*?p$^e--q;0X0P(+l7 zbTDM_yBi{=%rLm6mTBDcemP-q?2u_6ZEjU{--EuN!LeECGbTnxG$c1bhzx$NtBM}^ zJxG2aT?QcY1f;%&`VFd$Ytg?%ey7rBM07cs5sMgsWQn6s^D8uMLh}9{J9d22)TNV_ zHl!_8S8V;#v8}Sg7c)-u?%lh^W)r=+O1am@p!GTO$Cci5R`hnA_gNYTLI>&**(a$mZaXv9W4=6} zo01b0tW<@YAo;UOXI11e*j^gc<~hGjk>L>vL9(C21^UF&dI(E~`myA#8jx2{vo$ zii(Oa(O8RWu#)-p9;FLS%t((WWPF~G@pI)xA0)3KE@x!nhj@JjAw!-m&NFckuWun< z|H9OTkq$jZvn^l|4$4%k*mMArZ-?aHo4PR5#N!LZ>o>5mMcLU%yx!Z>)6=W;R>jam z$Jp4|9*A~5VuIw~L9z^r89lK1xY#z03O2!L>cU8eN;H@2c0f0pz;W~g-dXv<8*IC(QY>nT&0=``4G0^5_yZV%Yr4Yp3Dw<3nw zqaOyLi)7?yqb6Bcb1*zBY2y&Af;p5U6r!1BY^2BNhpTVUV`Q2Yi^IVpgJa?l%z{0k zu$Zt;w5%fl_y-yz)2O@YNf)#}Dp{}yCT(A^hp`r7Jx0r*0$A5VJH6*+=Bn#BPiV)Y zl);%t4?fCSKi%2RAHwRa$ZC&Xv0{bX4BW5Co1o`>HEtveM!_W5Na_x7zvGK?C4JHA z4GTLv?*Yn36r8>Ejz^R(HX=7FXV@Q74h zw~uwmIEdJBv=$EIM7XVbfaZ0O+j>s9c)gk2}diqF@B;1B%dj zj!#ORo@j;WZQHg<2->M2^b`ML8!{BuSzwhpbidN?bbM24|Du%`efjd`ZH(N~qZ0=` z?gWwQPF#eFb`FNHTA0S&c^_YjtS2H7(bWozJlo1)Y2bC`CM8}7A1zh{8 zvZogV5zXbKjdx6ME~|1~zIgcR%b7q#b9w#W5bfb>=7{F6Twz*6K4;3=oQ7D}6i@r= z*SA1D8e{)f24w^I|HQ$xb*^$m`>L|17XuN^U){C42f1{5bJ>%tebK)9^&}9{Tu%Qo z4I(czFX3^z~hp)bT3q&-QUQ^a;SFT)HqI6j&IzH4Y z;M!M}J-rx|=)bDw^Dkb!_yMKMI??e#bEAFr<(F>ESLK3&f9|vrg73|`J*{=8~zM49}=|Ds~#$5Z2(be%)uMheK z5|{E0_Bw?}`>3*K76TK_I_(qM3{NEP(7J4qy|8=v=+_@BmpBXM$GP0zYZ~WrYv2Ju zWba;^SW->n$9nuYVwT5`SMfvMR}6Fn?$Kf$FhBA@p*N&>6p5W`3g#rf`37!<=%q`S z%I^6#(>gt49gn(hG<9)K3s%9bFt$0qDYbvmO6U-?0QGvst!?hyxmUHdwO#fPEps*G zdwFbXJdVYB(<-sq+N*I^dbEIJN6!;nX8P%O{ovr>Gq$!>nIzas3pT+>doGg27xBrK z9cx4bO!gYfs}rLpI4;NQjvP7S6~oP9k);KjU{nA`#TW5O>C+ZnPq5Q&#i_{twe##> zlWnfotG3q4%kS)AJEruJip(;;49WLt5DIL%ur1X)9j48K6@7beP@rH)i^Kbansz zzTU6Fw7~;YE3bxJ-O%ERqFJXgzw;S$VgGR}`McLD@d$b;vh6c5$2Q`}1pFjcFYN-WF_s7ySyI zCo;BQWMA~B*)$`AcxZ?9!B8W;bm`JyV`Jk}EHlcJm-RXy`JMc3Vpeh;gJ^$cWo0J| z?|M6yN44ZC`GjV1aq(8#dkm7l?%0@80g@l&+orcPH#ZMV$+WBpy+@mPlf2 zi%q?m#`zKCL;R$NKTl8Z;5_3&&#j@*7Uy>jHj)mw*cfDc0;O*1xZsX#`F7R->n zgr!KCR_VERGMD5X@>Ymz<1Akw=#_%VrTof}>p9DSD_2G;#{uH;9gwUaET>X8?J^lB zdJ?I`PSW>5(v_3CFwgu!PSF0h!Ws)^!X6pXlfdZV*Aq_eg7A&#Y|nzmA^0F+;z4@w zfk~TDoFgZC(rKn&Rgir%^2a&LU8U=0VzOKc=TXR&yU*u1Kgq3hR|<7PFA^J~O;6>8 zcivsx0O70Y?aLr+IqCVF=TUBQ+gUF<`O?@q&i|m-wvzs{d^oVBrKQhoFPF|SkYgan cK$bG_A75}T;#Y!;)c^nh07*qoM6N<$f}vv4>Hq)$ literal 0 HcmV?d00001 From 149afd3c08ba4b03a15a97a7f047104224fd670e Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Thu, 24 Aug 2017 12:54:16 +0300 Subject: [PATCH 077/111] SEL-387: Add fexample filter --- .../project.pbxproj | 12 ++++ .../Example/Presenter/ExamplePresenter.swift | 1 + .../MediaPickerAutoAdjustmentFilter.swift | 56 +++++++++++++++++++ .../MediaPickerInteractorImpl.swift | 4 ++ .../Presenter/MediaPickerPresenter.swift | 1 + 5 files changed, 74 insertions(+) create mode 100644 Example/PaparazzoExample/Filters/MediaPickerAutoAdjustmentFilter.swift diff --git a/Example/PaparazzoExample.xcodeproj/project.pbxproj b/Example/PaparazzoExample.xcodeproj/project.pbxproj index ad42161c..1af08fe8 100644 --- a/Example/PaparazzoExample.xcodeproj/project.pbxproj +++ b/Example/PaparazzoExample.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 088535F351074F52B19B1688 /* Pods_PaparazzoExample_NoMarshroute.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9A4E214165A8FB30A7CE203 /* Pods_PaparazzoExample_NoMarshroute.framework */; }; 08BB7920D2F1F31E8D8C1760 /* Pods_PaparazzoExample_Storyboard.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B16F0AF7B8CAAE81DB10090 /* Pods_PaparazzoExample_Storyboard.framework */; }; + 136F1C791F4ED93A00C5CCA1 /* AutoAdjustmentFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136F1C781F4ED93A00C5CCA1 /* AutoAdjustmentFilter.swift */; }; 251E57C41E65651F0009A288 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E57C31E65651F0009A288 /* AppDelegate.swift */; }; 251E57F01E6565890009A288 /* AppSpecificUITheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E57D51E6565890009A288 /* AppSpecificUITheme.swift */; }; 251E57F21E6565890009A288 /* ExampleAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E57D91E6565890009A288 /* ExampleAssembly.swift */; }; @@ -59,6 +60,7 @@ /* Begin PBXFileReference section */ 0CD08CDB3C698AABC02FE1C4 /* Pods-PaparazzoExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PaparazzoExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-PaparazzoExample/Pods-PaparazzoExample.release.xcconfig"; sourceTree = ""; }; + 136F1C781F4ED93A00C5CCA1 /* AutoAdjustmentFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoAdjustmentFilter.swift; path = ../../../../../2/Avito/Avito/Utilities/MediaPickerFilters/AutoAdjustmentFilter.swift; sourceTree = ""; }; 251E57C01E65651F0009A288 /* PaparazzoExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PaparazzoExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 251E57C31E65651F0009A288 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 251E57CF1E65651F0009A288 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -131,6 +133,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 136F1C771F4ED91900C5CCA1 /* Filters */ = { + isa = PBXGroup; + children = ( + 136F1C781F4ED93A00C5CCA1 /* AutoAdjustmentFilter.swift */, + ); + path = Filters; + sourceTree = ""; + }; 251E57B71E65651F0009A288 = { isa = PBXGroup; children = ( @@ -161,6 +171,7 @@ 251E57C31E65651F0009A288 /* AppDelegate.swift */, 251E57D51E6565890009A288 /* AppSpecificUITheme.swift */, 251E57EF1E6565890009A288 /* NavigationController.swift */, + 136F1C771F4ED91900C5CCA1 /* Filters */, 251E57D71E6565890009A288 /* Example */, 251E57E71E6565890009A288 /* Fonts */, 251E57ED1E6565890009A288 /* MarshrouteHelpers */, @@ -606,6 +617,7 @@ 251E57F51E6565890009A288 /* ExampleInteractorImpl.swift in Sources */, 251E57F61E6565890009A288 /* ExamplePresenter.swift in Sources */, 251E57F31E6565890009A288 /* ExampleAssemblyImpl.swift in Sources */, + 136F1C791F4ED93A00C5CCA1 /* AutoAdjustmentFilter.swift in Sources */, 251E57F01E6565890009A288 /* AppSpecificUITheme.swift in Sources */, EFA7610E1E829434000EB296 /* ItemProvider.swift in Sources */, 251E57F91E6565890009A288 /* ExampleView.swift in Sources */, diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index 601646dd..e5f46198 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -117,6 +117,7 @@ final class ExamplePresenter { let data = MediaPickerData( items: items, + autocorrectionFilters: [AutoAdjustmentFilter()], selectedItem: items.last, maxItemsCount: 20, cropEnabled: true, diff --git a/Example/PaparazzoExample/Filters/MediaPickerAutoAdjustmentFilter.swift b/Example/PaparazzoExample/Filters/MediaPickerAutoAdjustmentFilter.swift new file mode 100644 index 00000000..54ce9836 --- /dev/null +++ b/Example/PaparazzoExample/Filters/MediaPickerAutoAdjustmentFilter.swift @@ -0,0 +1,56 @@ +import Paparazzo +import ImageSource +import ImageIO +import CoreGraphics +import MobileCoreServices + +final class MediaPickerAutoAdjustmentFilter: Filter { + func apply(_ sourceImage: ImageSource, completion: @escaping ((_ resultImage: ImageSource) -> ())) { + + let options = ImageRequestOptions(size: .fullResolution, deliveryMode: .best) + + sourceImage.requestImage(options: options) { [weak self] (result: ImageRequestResult) in + guard let image = result.image else { + completion(sourceImage) + return + } + + var ciImage = CIImage(image: image) + let adjustments = ciImage?.autoAdjustmentFilters() + + adjustments?.forEach { filter in + filter.setValue(ciImage, forKey: kCIInputImageKey) + ciImage = filter.outputImage + } + + let context = CIContext(options: nil) + if let output = ciImage, let cgImage = context.createCGImage(output, from: output.extent) { + + if let image = self?.imageSource(with: cgImage) { + completion(image) + return + } + } + + completion(sourceImage) + } + } + + private func imageSource(with cgImage: CGImage) -> ImageSource? { + + let path = (NSTemporaryDirectory() as NSString).appendingPathComponent("\(UUID().uuidString).jpg") + let url = URL(fileURLWithPath: path) + let destination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) + + if let destination = destination { + + CGImageDestinationAddImage(destination, cgImage, nil) + + if CGImageDestinationFinalize(destination) { + let imageSource = LocalImageSource(path: path, previewImage: cgImage) + return imageSource + } + } + return nil + } +} diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 9e7feb3c..5f08395f 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -154,6 +154,10 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { } DispatchQueue.main.async { + guard (image == originalItem.image) == false else { + completion(originalItem) + return + } let updatedItem = MediaPickerItem( identifier: originalItem.identifier, image: image, diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 3b9a2f57..dead7b26 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -252,6 +252,7 @@ final class MediaPickerPresenter: MediaPickerModule { if let originalItem = self?.interactor.selectedItem?.originalItem { self?.updateItem(originalItem) } else { + self?.view?.setAutocorrectionStatus(.corrected) self?.interactor.autocorrectItem { updatedItem in if let updatedItem = updatedItem { self?.updateItem(updatedItem) From fa9f9e1590ac0ba72406b9d515ea0fc57cd94eee Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Thu, 24 Aug 2017 13:02:47 +0300 Subject: [PATCH 078/111] SEL-387: Improve codestyle --- ImageSource/Core/ImageSource.swift | 4 ++++ .../MediaPicker/Interactor/MediaPickerInteractorImpl.swift | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ImageSource/Core/ImageSource.swift b/ImageSource/Core/ImageSource.swift index c66410e5..7952351b 100644 --- a/ImageSource/Core/ImageSource.swift +++ b/ImageSource/Core/ImageSource.swift @@ -29,3 +29,7 @@ public protocol ImageSource: class { public func ==(lhs: ImageSource, rhs: ImageSource) -> Bool { return lhs.isEqualTo(rhs) } + +public func !=(lhs: ImageSource, rhs: ImageSource) -> Bool { + return (lhs == rhs) == false +} diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 5f08395f..d760e005 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -154,8 +154,8 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { } DispatchQueue.main.async { - guard (image == originalItem.image) == false else { - completion(originalItem) + guard image != originalItem.image else { + completion(nil) return } let updatedItem = MediaPickerItem( From 82c17d17691ab31d2d3992b717e12325a8b85989 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Thu, 24 Aug 2017 13:14:03 +0300 Subject: [PATCH 079/111] SEL-387: Fix build --- Example/PaparazzoExample.xcodeproj/project.pbxproj | 8 ++++---- ...oAdjustmentFilter.swift => AutoAdjustmentFilter.swift} | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename Example/PaparazzoExample/Filters/{MediaPickerAutoAdjustmentFilter.swift => AutoAdjustmentFilter.swift} (97%) diff --git a/Example/PaparazzoExample.xcodeproj/project.pbxproj b/Example/PaparazzoExample.xcodeproj/project.pbxproj index 1af08fe8..1c6d883b 100644 --- a/Example/PaparazzoExample.xcodeproj/project.pbxproj +++ b/Example/PaparazzoExample.xcodeproj/project.pbxproj @@ -9,7 +9,7 @@ /* Begin PBXBuildFile section */ 088535F351074F52B19B1688 /* Pods_PaparazzoExample_NoMarshroute.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9A4E214165A8FB30A7CE203 /* Pods_PaparazzoExample_NoMarshroute.framework */; }; 08BB7920D2F1F31E8D8C1760 /* Pods_PaparazzoExample_Storyboard.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B16F0AF7B8CAAE81DB10090 /* Pods_PaparazzoExample_Storyboard.framework */; }; - 136F1C791F4ED93A00C5CCA1 /* AutoAdjustmentFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136F1C781F4ED93A00C5CCA1 /* AutoAdjustmentFilter.swift */; }; + 136F1C7C1F4EDE2B00C5CCA1 /* AutoAdjustmentFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136F1C7A1F4EDDF500C5CCA1 /* AutoAdjustmentFilter.swift */; }; 251E57C41E65651F0009A288 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E57C31E65651F0009A288 /* AppDelegate.swift */; }; 251E57F01E6565890009A288 /* AppSpecificUITheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E57D51E6565890009A288 /* AppSpecificUITheme.swift */; }; 251E57F21E6565890009A288 /* ExampleAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E57D91E6565890009A288 /* ExampleAssembly.swift */; }; @@ -60,7 +60,7 @@ /* Begin PBXFileReference section */ 0CD08CDB3C698AABC02FE1C4 /* Pods-PaparazzoExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PaparazzoExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-PaparazzoExample/Pods-PaparazzoExample.release.xcconfig"; sourceTree = ""; }; - 136F1C781F4ED93A00C5CCA1 /* AutoAdjustmentFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoAdjustmentFilter.swift; path = ../../../../../2/Avito/Avito/Utilities/MediaPickerFilters/AutoAdjustmentFilter.swift; sourceTree = ""; }; + 136F1C7A1F4EDDF500C5CCA1 /* AutoAdjustmentFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoAdjustmentFilter.swift; sourceTree = ""; }; 251E57C01E65651F0009A288 /* PaparazzoExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PaparazzoExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 251E57C31E65651F0009A288 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 251E57CF1E65651F0009A288 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -136,7 +136,7 @@ 136F1C771F4ED91900C5CCA1 /* Filters */ = { isa = PBXGroup; children = ( - 136F1C781F4ED93A00C5CCA1 /* AutoAdjustmentFilter.swift */, + 136F1C7A1F4EDDF500C5CCA1 /* AutoAdjustmentFilter.swift */, ); path = Filters; sourceTree = ""; @@ -608,6 +608,7 @@ 251E57FB1E6565890009A288 /* ExampleViewInput.swift in Sources */, 251E57FA1E6565890009A288 /* ExampleViewController.swift in Sources */, 251E57F21E6565890009A288 /* ExampleAssembly.swift in Sources */, + 136F1C7C1F4EDE2B00C5CCA1 /* AutoAdjustmentFilter.swift in Sources */, 251E57F41E6565890009A288 /* ExampleInteractor.swift in Sources */, 251E58011E6565890009A288 /* MarshrouteFacade.swift in Sources */, 251E58021E6565890009A288 /* NavigationController.swift in Sources */, @@ -617,7 +618,6 @@ 251E57F51E6565890009A288 /* ExampleInteractorImpl.swift in Sources */, 251E57F61E6565890009A288 /* ExamplePresenter.swift in Sources */, 251E57F31E6565890009A288 /* ExampleAssemblyImpl.swift in Sources */, - 136F1C791F4ED93A00C5CCA1 /* AutoAdjustmentFilter.swift in Sources */, 251E57F01E6565890009A288 /* AppSpecificUITheme.swift in Sources */, EFA7610E1E829434000EB296 /* ItemProvider.swift in Sources */, 251E57F91E6565890009A288 /* ExampleView.swift in Sources */, diff --git a/Example/PaparazzoExample/Filters/MediaPickerAutoAdjustmentFilter.swift b/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift similarity index 97% rename from Example/PaparazzoExample/Filters/MediaPickerAutoAdjustmentFilter.swift rename to Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift index 54ce9836..72f99905 100644 --- a/Example/PaparazzoExample/Filters/MediaPickerAutoAdjustmentFilter.swift +++ b/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift @@ -4,7 +4,7 @@ import ImageIO import CoreGraphics import MobileCoreServices -final class MediaPickerAutoAdjustmentFilter: Filter { +final class AutoAdjustmentFilter: Filter { func apply(_ sourceImage: ImageSource, completion: @escaping ((_ resultImage: ImageSource) -> ())) { let options = ImageRequestOptions(size: .fullResolution, deliveryMode: .best) From cf6bbda5ef449967006e0f85dfe0c97732776774 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Thu, 24 Aug 2017 16:19:12 +0300 Subject: [PATCH 080/111] SEL-368: Add info message stack --- Paparazzo/Core/Utilities/Debouncer.swift | 74 +++++++ .../InfoMessage/InfoMessageAnimator.swift | 184 ++++++++++++++++++ .../InfoMessage/InfoMessageDisplayer.swift | 14 ++ .../VIPER/InfoMessage/InfoMessageView.swift | 13 ++ .../InfoMessageViewAnimatorBehavior.swift | 25 +++ .../InfoMessage/InfoMessageViewFactory.swift | 29 +++ .../InfoMessage/InfoMessageViewInput.swift | 4 + 7 files changed, 343 insertions(+) create mode 100644 Paparazzo/Core/Utilities/Debouncer.swift create mode 100644 Paparazzo/Core/VIPER/InfoMessage/InfoMessageAnimator.swift create mode 100644 Paparazzo/Core/VIPER/InfoMessage/InfoMessageDisplayer.swift create mode 100644 Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift create mode 100644 Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewAnimatorBehavior.swift create mode 100644 Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift create mode 100644 Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewInput.swift diff --git a/Paparazzo/Core/Utilities/Debouncer.swift b/Paparazzo/Core/Utilities/Debouncer.swift new file mode 100644 index 00000000..18363e8c --- /dev/null +++ b/Paparazzo/Core/Utilities/Debouncer.swift @@ -0,0 +1,74 @@ +// 1--------2----3--4-5-6---------789------- +// ^delay. ^dela^de^d^d^delay. ^^^delay. +// ------1--------------------6-----------9- +// +// http://rxmarbles.com/#debounce +// +// Real life example: +// +// Л-е-с-на--я------------------------------ +// ^d^d^d^^de^delay. +// ----------------suggest("Лесная")-------- +// +// NOTE: Access to the resource should be syncronized! See cancel() below. +// + +public protocol Debouncable { + func debounce(_ closure: @escaping () -> ()) + func cancel() +} + +public final class Debouncer: Debouncable { + private var lastFireTime = DispatchTime(uptimeNanoseconds: 0) + private let queue: DispatchQueue + private let delay: TimeInterval + + public init(delay: TimeInterval, queue: DispatchQueue = DispatchQueue.main) { + self.delay = delay + self.queue = queue + } + + public func debounce(_ closure: @escaping () -> ()) { + lastFireTime = DispatchTime.now() + queue.asyncAfter(deadline: .now() + delay) { [weak self] in + if let strongSelf = self { + let now = DispatchTime.now() + let when = strongSelf.lastFireTime + strongSelf.delay + if now >= when { + closure() + } + } + } + } + + // Text input: --Л-е-с--ная------- ("Лесная") + // As events: --x-x-x--xxx------- + // Debounce time: ^-^-^--^^^----. + // Debounced: ----------------x-- + // User taps cancel: -------------y----- + // + // Expected result: -------------y----- + // Actual result: -------------y--x-- (without calling cancel()) + // + // Convenience function: cancel() + // + // Note: in ideal world with RX you don't have to use this. + // It should be a single stream: + // + // Text input: --Л-е-с--ная------- ("Лесная") + // As events: --x-x-x--xxx------- + // User taps cancel: -------------y----- + // + // A single stream: + // All this merged: --z-z-z--zzz-z----- + // Debounce time: ^-^-^--^^^-^----. + // Debounced: ------------------z ("") + // + // If data streams aint a mess you will be happy without cancel(). + // However, now in our app data streams are a mess. + + public func cancel() { + // Preempt current operation in debouncer to get expected result as in the example above. + debounce {} + } +} diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageAnimator.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageAnimator.swift new file mode 100644 index 00000000..e776aea5 --- /dev/null +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageAnimator.swift @@ -0,0 +1,184 @@ +import UIKit + +public enum InfoMessageViewDismissType { + case interactive + case timeout + case force +} + +struct InfoMessageAnimatorData { + + let animation: InfoMessageAnimatorBehavior + let timeout: TimeInterval + let onDismiss: ((InfoMessageViewDismissType) -> ())? + + init( + animation: InfoMessageAnimatorBehavior, + timeout: TimeInterval = 0.0, + onDismiss: ((InfoMessageViewDismissType) -> ())?) + { + self.animation = animation + self.timeout = timeout + self.onDismiss = onDismiss + } +} + +private enum AnimatorState { + case initial + case appearing + case appeared + case dismissingByTimer + case dismissingByDismissFunction + case dismissed +} + +final class InfoMessageAnimator: InfoMessageViewInput { + + private weak var container: UIView? + private weak var messageView: InfoMessageView? + + private var dismissingByTimerDebouncer: Debouncer + + // Constant settings + private let behavior: InfoMessageAnimatorBehavior + private let onDismiss: ((InfoMessageViewDismissType) -> ())? + + // Updatable settings + private var timeout: TimeInterval + + // Other state + private var state = AnimatorState.initial + + init(_ data: InfoMessageAnimatorData) + { + behavior = data.animation + onDismiss = data.onDismiss + timeout = data.timeout + dismissingByTimerDebouncer = Debouncer(delay: timeout) + } + + // MARK: - Interface + func appear(messageView: InfoMessageView, in container: UIView) { + self.container = container + self.messageView = messageView + + messageView.size = messageView.sizeThatFits(container.size) + behavior.configure(messageView: messageView, in: container) + container.addSubview(messageView) + container.bringSubview(toFront: messageView) + + changeState(to: .appearing) + } + + func dismiss() { + changeState(to: .dismissingByDismissFunction) + } + + func update(timeout: TimeInterval) { + self.timeout = timeout + dismissingByTimerDebouncer.cancel() + dismissingByTimerDebouncer = Debouncer(delay: timeout) + + switch state { + case .initial, .appearing: + // dismissing is not scheduled + break + case .appeared: + scheduleDismissing() + + case .dismissingByTimer, .dismissingByDismissFunction, .dismissed: + // scheduling is not needed + break + } + } + + // MARK: - States + private func changeState(to newState: AnimatorState) { + guard allowedTransitions().contains(newState) else { return } + let oldState = state + state = newState + + switch newState { + case .initial: + break + case .appearing: + break + case .appeared: + scheduleDismissing() + case .dismissingByTimer: + animateDismissing(dismissType: .timeout) + case .dismissingByDismissFunction: + animateDismissing(dismissType: .force) + case .dismissed: + messageView?.removeFromSuperview() + } + } + + private func allowedTransitions() -> [AnimatorState] { + switch state { + case .initial: + return [.appearing, .dismissingByDismissFunction] + case .appearing: + return [.appeared, .dismissingByDismissFunction] + case .appeared: + return [.dismissingByTimer, .dismissingByDismissFunction] + case .dismissingByTimer, .dismissingByDismissFunction: + return [.dismissed] + case .dismissed: + return [] + } + } + + private func scheduleDismissing() { + if timeout.isZero { + dismissingByTimerDebouncer.cancel() + } else { + dismissingByTimerDebouncer.debounce { [weak self] in + self?.changeState(to: .dismissingByTimer) + } + } + } + + // MARK: - Animations + private func animateAppearing() { + guard + let messageView = self.messageView, + let container = self.container + else { return } + + UIView.animate( + withDuration: 0.5, + delay: 0, + usingSpringWithDamping: 0.75, + initialSpringVelocity: 0.1, + options: .curveEaseIn, + animations: { + self.behavior.present(messageView: messageView, in: container) + }, + completion: {_ in + self.changeState(to: .appeared) + } + ) + } + + private func animateDismissing(dismissType: InfoMessageViewDismissType) { + guard + let messageView = self.messageView, + let container = self.container + else { return } + + UIView.animate( + withDuration: 0.3, + delay: 0, + options: .curveEaseOut, + animations: { + self.behavior.dismiss(messageView: messageView, in: container) + }, + completion: {_ in + self.changeState(to: .dismissed) + self.onDismiss?(dismissType) + } + ) + } +} + diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageDisplayer.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageDisplayer.swift new file mode 100644 index 00000000..54b0fd2a --- /dev/null +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageDisplayer.swift @@ -0,0 +1,14 @@ +final class InfoMessageDisplayer { + + init() {} + + private let infoMessageFactory = InfoMessageViewFactoryImpl() + + @discardableResult + func display(viewData: InfoMessageViewData, in container: UIView) -> InfoMessageViewInput { + let (messageView, animator) = infoMessageFactory.create(from: viewData) + animator.appear(messageView: messageView, in: container) + return animator + } +} + diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift new file mode 100644 index 00000000..fc0a7fa4 --- /dev/null +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift @@ -0,0 +1,13 @@ +import UIKit + +struct InfoMessageViewData { + let text: String +} + +final class InfoMessageView: UIView { + + func setViewData(_ viewData: InfoMessageViewData) { + + } + +} diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewAnimatorBehavior.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewAnimatorBehavior.swift new file mode 100644 index 00000000..4dfda088 --- /dev/null +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewAnimatorBehavior.swift @@ -0,0 +1,25 @@ +import UIKit + +protocol InfoMessageAnimatorBehavior { + func configure(messageView: UIView, in container: UIView) + func present(messageView: UIView, in container: UIView) + func dismiss(messageView: UIView, in container: UIView) +} + +final class DefaultInfoMessageAnimatorBehavior: InfoMessageAnimatorBehavior { + + func configure(messageView: UIView, in container: UIView) { + messageView.autoresizingMask = [.flexibleWidth, .flexibleTopMargin] + messageView.top = container.bottom + messageView.left = container.left + } + + func present(messageView: UIView, in container: UIView) { + messageView.bottom = container.height + } + + func dismiss(messageView: UIView, in container: UIView) { + messageView.top = container.bottom + } +} + diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift new file mode 100644 index 00000000..d6be65a8 --- /dev/null +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift @@ -0,0 +1,29 @@ +protocol InfoMessageViewFactory: class { + + func create( + from viewData: InfoMessageViewData + ) -> ( + view: InfoMessageView, + animator: InfoMessageAnimator + ) +} + +final class InfoMessageViewFactoryImpl: InfoMessageViewFactory { + + func create( + from viewData: InfoMessageViewData + ) -> ( + view: InfoMessageView, + animator: InfoMessageAnimator) + { + let animation = DefaultInfoMessageAnimatorBehavior() + let data = InfoMessageAnimatorData(animation: animation, onDismiss: nil) + let animator = InfoMessageAnimator(data) + + let messageView = InfoMessageView() + messageView.setViewData(viewData) + + return (view: messageView, animator: animator) + } +} + diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewInput.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewInput.swift new file mode 100644 index 00000000..4a6c092e --- /dev/null +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewInput.swift @@ -0,0 +1,4 @@ +public protocol InfoMessageViewInput: class { + func dismiss() +} + From 7cfdfc56255fe099a746ffb97404da44a76716a5 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Thu, 24 Aug 2017 16:29:35 +0300 Subject: [PATCH 081/111] SEL-388: Add info message displayable protocol --- .../Core/VIPER/InfoMessage/InfoMessageDisplayer.swift | 11 +++++++++++ .../VIPER/InfoMessage/InfoMessageViewFactory.swift | 1 - 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageDisplayer.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageDisplayer.swift index 54b0fd2a..b750aa40 100644 --- a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageDisplayer.swift +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageDisplayer.swift @@ -12,3 +12,14 @@ final class InfoMessageDisplayer { } } +protocol InfoMessageDisplayable: class { + @discardableResult + func showInfoMessage(_ viewData: InfoMessageViewData) -> InfoMessageViewInput +} + +extension InfoMessageDisplayable where Self: UIViewController { + func showInfoMessage(_ viewData: InfoMessageViewData) -> InfoMessageViewInput { + return InfoMessageDisplayer().display(viewData: viewData, in: self.view) + } +} + diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift index d6be65a8..61b60d4b 100644 --- a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift @@ -26,4 +26,3 @@ final class InfoMessageViewFactoryImpl: InfoMessageViewFactory { return (view: messageView, animator: animator) } } - From de1e11945cd3f8db3ead569cc363aa91623e4a47 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Fri, 25 Aug 2017 17:34:25 +0300 Subject: [PATCH 082/111] SEL-388: Add info message displaying --- .../project.pbxproj | 11 ++- .../InfoMessage/InfoMessageAnimator.swift | 2 +- .../InfoMessage/InfoMessageDisplayer.swift | 8 +- .../VIPER/InfoMessage/InfoMessageView.swift | 73 +++++++++++++++++++ .../InfoMessageViewAnimatorBehavior.swift | 9 ++- .../Presenter/MediaPickerPresenter.swift | 2 + .../MediaPicker/View/MediaPickerView.swift | 5 ++ .../View/MediaPickerViewController.swift | 4 + .../View/MediaPickerViewInput.swift | 2 + 9 files changed, 107 insertions(+), 9 deletions(-) diff --git a/Example/PaparazzoExample.xcodeproj/project.pbxproj b/Example/PaparazzoExample.xcodeproj/project.pbxproj index ad42161c..fa4f0067 100644 --- a/Example/PaparazzoExample.xcodeproj/project.pbxproj +++ b/Example/PaparazzoExample.xcodeproj/project.pbxproj @@ -375,7 +375,8 @@ TargetAttributes = { 251E57BF1E65651F0009A288 = { CreatedOnToolsVersion = 8.2.1; - ProvisioningStyle = Manual; + DevelopmentTeam = 5PHGGKL9UQ; + ProvisioningStyle = Automatic; }; 25A489B01E656A2B00CC431B = { CreatedOnToolsVersion = 8.2.1; @@ -758,12 +759,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5PHGGKL9UQ; INFOPLIST_FILE = PaparazzoExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ru.avito.PaparazzoExample; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 3.0; }; name = Debug; @@ -775,12 +778,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5PHGGKL9UQ; INFOPLIST_FILE = PaparazzoExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ru.avito.PaparazzoExample; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 3.0; }; name = Release; diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageAnimator.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageAnimator.swift index e776aea5..915ed0d1 100644 --- a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageAnimator.swift +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageAnimator.swift @@ -102,7 +102,7 @@ final class InfoMessageAnimator: InfoMessageViewInput { case .initial: break case .appearing: - break + animateAppearing() case .appeared: scheduleDismissing() case .dismissingByTimer: diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageDisplayer.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageDisplayer.swift index b750aa40..4214f18a 100644 --- a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageDisplayer.swift +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageDisplayer.swift @@ -1,13 +1,20 @@ final class InfoMessageDisplayer { + private var currentInfoMessage: InfoMessageViewInput? + init() {} private let infoMessageFactory = InfoMessageViewFactoryImpl() @discardableResult func display(viewData: InfoMessageViewData, in container: UIView) -> InfoMessageViewInput { + currentInfoMessage?.dismiss() + currentInfoMessage = nil + let (messageView, animator) = infoMessageFactory.create(from: viewData) animator.appear(messageView: messageView, in: container) + currentInfoMessage = animator + return animator } } @@ -22,4 +29,3 @@ extension InfoMessageDisplayable where Self: UIViewController { return InfoMessageDisplayer().display(viewData: viewData, in: self.view) } } - diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift index fc0a7fa4..ddb76763 100644 --- a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift @@ -6,8 +6,81 @@ struct InfoMessageViewData { final class InfoMessageView: UIView { + private struct Layout { + static let height: CGFloat = 22 + static let textInsets = UIEdgeInsets(top: 6, left: 6, bottom: 6, right: 6) + static let widthTextInsets = textInsets.left + textInsets.right + static let heightTextInsets = textInsets.top + textInsets.bottom + } + + private struct Spec { + static let font = UIFont.systemFont(ofSize: 14) + static let textColor = UIColor.black + static let cornerRadius: CGFloat = 2 + static let backgroundColor = UIColor.white + static let shadowOffset = CGSize(width: 0, height: 1) + static let shadowOpacity: Float = 0.14 + static let shadowRadius: CGFloat = 2 + } + + private let textLabel = UILabel() + private let contentView = UIView() + + // MARK: - Init + override init(frame: CGRect) { + super.init(frame: frame) + + addSubview(contentView) + + contentView.layer.cornerRadius = Spec.cornerRadius + contentView.layer.masksToBounds = true + + textLabel.font = Spec.font + textLabel.textColor = Spec.textColor + contentView.addSubview(textLabel) + + contentView.backgroundColor = Spec.backgroundColor + + layer.masksToBounds = false + layer.shadowOffset = Spec.shadowOffset + layer.shadowRadius = Spec.shadowRadius + layer.shadowOpacity = Spec.shadowOpacity + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View data func setViewData(_ viewData: InfoMessageViewData) { + textLabel.text = viewData.text + } + + // MARK: - Layout + override func layoutSubviews() { + super.layoutSubviews() + + textLabel.frame = CGRect( + x: Layout.textInsets.left, + y: Layout.textInsets.top, + width: bounds.width - Layout.widthTextInsets, + height: bounds.height - Layout.heightTextInsets + ) + contentView.frame = bounds } + override func sizeThatFits(_ size: CGSize) -> CGSize { + let shrinkedSize = CGSize( + width: size.width - Layout.widthTextInsets, + height: Layout.height - Layout.heightTextInsets + ) + + let textSize = textLabel.sizeThatFits(shrinkedSize) + + return CGSize( + width: textSize.width + Layout.widthTextInsets, + height: Layout.height + ) + } } diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewAnimatorBehavior.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewAnimatorBehavior.swift index 4dfda088..ac35f450 100644 --- a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewAnimatorBehavior.swift +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewAnimatorBehavior.swift @@ -10,16 +10,17 @@ final class DefaultInfoMessageAnimatorBehavior: InfoMessageAnimatorBehavior { func configure(messageView: UIView, in container: UIView) { messageView.autoresizingMask = [.flexibleWidth, .flexibleTopMargin] - messageView.top = container.bottom - messageView.left = container.left + messageView.alpha = 0 + messageView.bottom = container.height - 20 + messageView.centerX = ceil(container.width / 2) } func present(messageView: UIView, in container: UIView) { - messageView.bottom = container.height + messageView.alpha = 1 } func dismiss(messageView: UIView, in container: UIView) { - messageView.top = container.bottom + messageView.alpha = 0 } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 3b9a2f57..a1d19436 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -250,8 +250,10 @@ final class MediaPickerPresenter: MediaPickerModule { view?.onAutocorrectButtonTap = { [weak self] in if let originalItem = self?.interactor.selectedItem?.originalItem { + self?.view?.showInfoMessage("РАЗМЫТИЕ ВЫКЛ") self?.updateItem(originalItem) } else { + self?.view?.showInfoMessage("РАЗМЫТИЕ ВКЛ") self?.interactor.autocorrectItem { updatedItem in if let updatedItem = updatedItem { self?.updateItem(updatedItem) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index 918d6551..d95a5167 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -41,6 +41,7 @@ final class MediaPickerView: UIView, ThemeConfigurable { private var mode = MediaPickerViewMode.camera private var deviceOrientation = DeviceOrientation.portrait + private let infoMessageDisplayer = InfoMessageDisplayer() private var showsPreview: Bool = true { didSet { @@ -538,6 +539,10 @@ final class MediaPickerView: UIView, ThemeConfigurable { thumbnailRibbonView.reloadCamera() } + func showInfoMessage(_ message: String) { + infoMessageDisplayer.display(viewData: InfoMessageViewData(text: message), in: photoPreviewView) + } + // MARK: - Private private func layoutCloseAndContinueButtons() { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index 9a4bd5cc..8abefd63 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -339,6 +339,10 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI mediaPickerView.setPhotoLibraryButtonEnabled(enabled) } + func showInfoMessage(_ message: String) { + mediaPickerView.showInfoMessage(message) + } + // MARK: - ThemeConfigurable func setTheme(_ theme: ThemeType) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift index c85b95e0..83b80450 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift @@ -63,6 +63,8 @@ protocol MediaPickerViewInput: class { func setShowPreview(_ showPreview: Bool) + func showInfoMessage(_ message: String) + // MARK: - Actions in photo ribbon var onItemSelect: ((MediaPickerItem) -> ())? { get set } var onItemMove: ((_ sourceIndex: Int, _ destinationIndex: Int) -> ())? { get set } From 3b32b633274613395618a4a6561daed9410daee7 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Fri, 25 Aug 2017 17:47:20 +0300 Subject: [PATCH 083/111] SEL-388: Add dismissing by timer --- Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift | 1 + Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift | 2 +- .../VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift | 4 ++-- Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift | 4 ++-- .../VIPER/MediaPicker/View/MediaPickerViewController.swift | 4 ++-- .../Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift index ddb76763..c860035b 100644 --- a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageView.swift @@ -2,6 +2,7 @@ import UIKit struct InfoMessageViewData { let text: String + let timeout: TimeInterval } final class InfoMessageView: UIView { diff --git a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift index 61b60d4b..f4e6ced8 100644 --- a/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift +++ b/Paparazzo/Core/VIPER/InfoMessage/InfoMessageViewFactory.swift @@ -17,7 +17,7 @@ final class InfoMessageViewFactoryImpl: InfoMessageViewFactory { animator: InfoMessageAnimator) { let animation = DefaultInfoMessageAnimatorBehavior() - let data = InfoMessageAnimatorData(animation: animation, onDismiss: nil) + let data = InfoMessageAnimatorData(animation: animation, timeout: viewData.timeout, onDismiss: nil) let animator = InfoMessageAnimator(data) let messageView = InfoMessageView() diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index a1d19436..d12a0148 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -250,10 +250,10 @@ final class MediaPickerPresenter: MediaPickerModule { view?.onAutocorrectButtonTap = { [weak self] in if let originalItem = self?.interactor.selectedItem?.originalItem { - self?.view?.showInfoMessage("РАЗМЫТИЕ ВЫКЛ") + self?.view?.showInfoMessage("РАЗМЫТИЕ ВЫКЛ", timeout: 2.0) self?.updateItem(originalItem) } else { - self?.view?.showInfoMessage("РАЗМЫТИЕ ВКЛ") + self?.view?.showInfoMessage("РАЗМЫТИЕ ВКЛ", timeout: 2.0) self?.interactor.autocorrectItem { updatedItem in if let updatedItem = updatedItem { self?.updateItem(updatedItem) diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift index d95a5167..deabf5ab 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerView.swift @@ -539,8 +539,8 @@ final class MediaPickerView: UIView, ThemeConfigurable { thumbnailRibbonView.reloadCamera() } - func showInfoMessage(_ message: String) { - infoMessageDisplayer.display(viewData: InfoMessageViewData(text: message), in: photoPreviewView) + func showInfoMessage(_ message: String, timeout: TimeInterval) { + infoMessageDisplayer.display(viewData: InfoMessageViewData(text: message, timeout: timeout), in: photoPreviewView) } // MARK: - Private diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift index 8abefd63..23b14018 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewController.swift @@ -339,8 +339,8 @@ final class MediaPickerViewController: PaparazzoViewController, MediaPickerViewI mediaPickerView.setPhotoLibraryButtonEnabled(enabled) } - func showInfoMessage(_ message: String) { - mediaPickerView.showInfoMessage(message) + func showInfoMessage(_ message: String, timeout: TimeInterval) { + mediaPickerView.showInfoMessage(message, timeout: timeout) } // MARK: - ThemeConfigurable diff --git a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift index 83b80450..c4d1ad1e 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/View/MediaPickerViewInput.swift @@ -63,7 +63,7 @@ protocol MediaPickerViewInput: class { func setShowPreview(_ showPreview: Bool) - func showInfoMessage(_ message: String) + func showInfoMessage(_ message: String, timeout: TimeInterval) // MARK: - Actions in photo ribbon var onItemSelect: ((MediaPickerItem) -> ())? { get set } From e4772e1b8496edcd36acc6fa0e123ebc651f8104 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Fri, 25 Aug 2017 17:49:26 +0300 Subject: [PATCH 084/111] SEL-388: Improve focus indicator customization --- Paparazzo/Core/VIPER/Camera/View/CameraView.swift | 4 ++-- Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Paparazzo/Core/VIPER/Camera/View/CameraView.swift b/Paparazzo/Core/VIPER/Camera/View/CameraView.swift index 643b67a5..4430e172 100644 --- a/Paparazzo/Core/VIPER/Camera/View/CameraView.swift +++ b/Paparazzo/Core/VIPER/Camera/View/CameraView.swift @@ -62,7 +62,7 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { focusIndicator?.hide() focusIndicator = FocusIndicator() if let theme = theme { - focusIndicator?.setTheme(theme) + focusIndicator?.setColor(theme.focusIndicatorColor) } focusIndicator?.animate(in: layer, focusPoint: focusPoint) } @@ -135,7 +135,7 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { func setTheme(_ theme: ThemeType) { self.theme = theme accessDeniedView.setTheme(theme) - focusIndicator?.setTheme(theme) + focusIndicator?.setColor(theme.focusIndicatorColor) } // MARK: - Dispose bag diff --git a/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift b/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift index 5b92a604..40d312a5 100644 --- a/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift +++ b/Paparazzo/Core/VIPER/Camera/View/FocusIndicator.swift @@ -1,6 +1,6 @@ import UIKit -final class FocusIndicator: CALayer, ThemeConfigurable { +final class FocusIndicator: CALayer { typealias ThemeType = MediaPickerRootModuleUITheme @@ -36,10 +36,8 @@ final class FocusIndicator: CALayer, ThemeConfigurable { fatalError("init(coder:) has not been implemented") } - // MARK: - ThemeConfigurable - - func setTheme(_ theme: ThemeType) { - shapeLayer.strokeColor = theme.focusIndicatorColor.cgColor + func setColor(_ color: UIColor) { + shapeLayer.strokeColor = color.cgColor } func animate(in superlayer: CALayer, focusPoint: CGPoint) { From 238fe28ce7f79e18a759b7c5f9c7286dff017900 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Fri, 25 Aug 2017 17:51:37 +0300 Subject: [PATCH 085/111] SEL-388: Revert proj file --- Example/PaparazzoExample.xcodeproj/project.pbxproj | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Example/PaparazzoExample.xcodeproj/project.pbxproj b/Example/PaparazzoExample.xcodeproj/project.pbxproj index fa4f0067..ad42161c 100644 --- a/Example/PaparazzoExample.xcodeproj/project.pbxproj +++ b/Example/PaparazzoExample.xcodeproj/project.pbxproj @@ -375,8 +375,7 @@ TargetAttributes = { 251E57BF1E65651F0009A288 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = 5PHGGKL9UQ; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; 25A489B01E656A2B00CC431B = { CreatedOnToolsVersion = 8.2.1; @@ -759,14 +758,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 5PHGGKL9UQ; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = PaparazzoExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ru.avito.PaparazzoExample; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 3.0; }; name = Debug; @@ -778,14 +775,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 5PHGGKL9UQ; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = PaparazzoExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ru.avito.PaparazzoExample; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 3.0; }; name = Release; From 0692c0339d871c87a2c43eb7c9e67658205cc473 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Fri, 25 Aug 2017 17:55:37 +0300 Subject: [PATCH 086/111] SEL-388: Improve debouncer codestyle --- Paparazzo/Core/Utilities/Debouncer.swift | 42 ------------------------ 1 file changed, 42 deletions(-) diff --git a/Paparazzo/Core/Utilities/Debouncer.swift b/Paparazzo/Core/Utilities/Debouncer.swift index 18363e8c..e8e461ff 100644 --- a/Paparazzo/Core/Utilities/Debouncer.swift +++ b/Paparazzo/Core/Utilities/Debouncer.swift @@ -1,18 +1,3 @@ -// 1--------2----3--4-5-6---------789------- -// ^delay. ^dela^de^d^d^delay. ^^^delay. -// ------1--------------------6-----------9- -// -// http://rxmarbles.com/#debounce -// -// Real life example: -// -// Л-е-с-на--я------------------------------ -// ^d^d^d^^de^delay. -// ----------------suggest("Лесная")-------- -// -// NOTE: Access to the resource should be syncronized! See cancel() below. -// - public protocol Debouncable { func debounce(_ closure: @escaping () -> ()) func cancel() @@ -41,34 +26,7 @@ public final class Debouncer: Debouncable { } } - // Text input: --Л-е-с--ная------- ("Лесная") - // As events: --x-x-x--xxx------- - // Debounce time: ^-^-^--^^^----. - // Debounced: ----------------x-- - // User taps cancel: -------------y----- - // - // Expected result: -------------y----- - // Actual result: -------------y--x-- (without calling cancel()) - // - // Convenience function: cancel() - // - // Note: in ideal world with RX you don't have to use this. - // It should be a single stream: - // - // Text input: --Л-е-с--ная------- ("Лесная") - // As events: --x-x-x--xxx------- - // User taps cancel: -------------y----- - // - // A single stream: - // All this merged: --z-z-z--zzz-z----- - // Debounce time: ^-^-^--^^^-^----. - // Debounced: ------------------z ("") - // - // If data streams aint a mess you will be happy without cancel(). - // However, now in our app data streams are a mess. - public func cancel() { - // Preempt current operation in debouncer to get expected result as in the example above. debounce {} } } From 613d738bce45719be2046ec2a9e65abab52799ca Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Fri, 25 Aug 2017 18:25:11 +0300 Subject: [PATCH 087/111] SEL-388: Update info message texts --- .../VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index 0645728e..d7cbc47c 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -250,10 +250,10 @@ final class MediaPickerPresenter: MediaPickerModule { view?.onAutocorrectButtonTap = { [weak self] in if let originalItem = self?.interactor.selectedItem?.originalItem { - self?.view?.showInfoMessage("РАЗМЫТИЕ ВЫКЛ", timeout: 2.0) + self?.view?.showInfoMessage("РАЗМЫТИЕ ВЫКЛ.", timeout: 1.0) self?.updateItem(originalItem) } else { - self?.view?.showInfoMessage("РАЗМЫТИЕ ВКЛ", timeout: 2.0) + self?.view?.showInfoMessage("РАЗМЫТИЕ ВКЛ.", timeout: 1.0) self?.view?.setAutocorrectionStatus(.corrected) self?.interactor.autocorrectItem { updatedItem in if let updatedItem = updatedItem { From 6c988c55d6b7a0542d5f10a09598d0c07d356511 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Mon, 28 Aug 2017 17:01:06 +0300 Subject: [PATCH 088/111] SEL-388: Add filter fallbackMessage --- .../Filters/AutoAdjustmentFilter.swift | 2 ++ .../Interactor/Filters/Filter.swift | 2 ++ .../Interactor/MediaPickerInteractor.swift | 5 ++++- .../MediaPickerInteractorImpl.swift | 19 +++++++++++++++---- .../Presenter/MediaPickerPresenter.swift | 13 ++++++++++--- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift b/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift index 72f99905..b3e32b9f 100644 --- a/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift +++ b/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift @@ -5,6 +5,8 @@ import CoreGraphics import MobileCoreServices final class AutoAdjustmentFilter: Filter { + let errorMessage: String? = "Cannot do anything aaaaaaa".uppercased() + func apply(_ sourceImage: ImageSource, completion: @escaping ((_ resultImage: ImageSource) -> ())) { let options = ImageRequestOptions(size: .fullResolution, deliveryMode: .best) diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift index 0af54572..8123eb2c 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift @@ -2,4 +2,6 @@ import ImageSource public protocol Filter { func apply(_ sourceImage: ImageSource, completion: @escaping ((_ sourceImage: ImageSource) -> Void)) + + var errorMessage: String? { get } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift index 7dd073fb..b19aa1c8 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractor.swift @@ -35,5 +35,8 @@ protocol MediaPickerInteractor: class { func canAddItems() -> Bool - func autocorrectItem(completion: @escaping (_ updatedItem: MediaPickerItem?) -> ()) + func autocorrectItem( + onResult: @escaping (_ updatedItem: MediaPickerItem?) -> (), + onError: @escaping (_ errorMessage: String?) -> () + ) } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index d760e005..7b10cd90 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -134,9 +134,12 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { return maxItemsCount.flatMap { self.items.count < $0 } ?? true } - func autocorrectItem(completion: @escaping (_ updatedItem: MediaPickerItem?) -> ()) { + func autocorrectItem( + onResult: @escaping (_ updatedItem: MediaPickerItem?) -> (), + onError: @escaping (_ errorMessage: String?) -> ()) + { guard let originalItem = selectedItem else { - completion(nil) + onError(nil) return } @@ -144,9 +147,16 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { DispatchQueue.global(qos: .userInitiated).async { let filtersGroup = DispatchGroup() + var errorMessages = [String]() + self.autocorrectionFilters.forEach { filter in filtersGroup.enter() filter.apply(image) { resultItem in + let isFilterFailed = resultItem == image + if isFilterFailed, let errorMessage = filter.errorMessage { + errorMessages.append(errorMessage) + } + image = resultItem filtersGroup.leave() } @@ -155,16 +165,17 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { DispatchQueue.main.async { guard image != originalItem.image else { - completion(nil) + onError(errorMessages.first) return } + let updatedItem = MediaPickerItem( identifier: originalItem.identifier, image: image, source: originalItem.source, originalItem: originalItem ) - completion(updatedItem) + onResult(updatedItem) } } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index d7cbc47c..bcb27db7 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -255,11 +255,18 @@ final class MediaPickerPresenter: MediaPickerModule { } else { self?.view?.showInfoMessage("РАЗМЫТИЕ ВКЛ.", timeout: 1.0) self?.view?.setAutocorrectionStatus(.corrected) - self?.interactor.autocorrectItem { updatedItem in - if let updatedItem = updatedItem { - self?.updateItem(updatedItem) + self?.interactor.autocorrectItem( + onResult: { [weak self] updatedItem in + if let updatedItem = updatedItem { + self?.updateItem(updatedItem) + } + }, onError: { [weak self] errorMessage in + if let errorMessage = errorMessage { + self?.view?.showInfoMessage(errorMessage, timeout: 1.0) } + self?.view?.setAutocorrectionStatus(.original) } + ) } } From 53fd166e1f95e3a151ecc083104e2a6207402d31 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Mon, 28 Aug 2017 17:02:11 +0300 Subject: [PATCH 089/111] SEL-388: Rename filter error message --- Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift | 2 +- .../Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift | 2 +- .../MediaPicker/Interactor/MediaPickerInteractorImpl.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift b/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift index b3e32b9f..8788bc9e 100644 --- a/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift +++ b/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift @@ -5,7 +5,7 @@ import CoreGraphics import MobileCoreServices final class AutoAdjustmentFilter: Filter { - let errorMessage: String? = "Cannot do anything aaaaaaa".uppercased() + let fallbackMessage: String? = "Cannot do anything aaaaaaa".uppercased() func apply(_ sourceImage: ImageSource, completion: @escaping ((_ resultImage: ImageSource) -> ())) { diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift index 8123eb2c..6560fee5 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/Filters/Filter.swift @@ -3,5 +3,5 @@ import ImageSource public protocol Filter { func apply(_ sourceImage: ImageSource, completion: @escaping ((_ sourceImage: ImageSource) -> Void)) - var errorMessage: String? { get } + var fallbackMessage: String? { get } } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift index 7b10cd90..ed84aaec 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Interactor/MediaPickerInteractorImpl.swift @@ -153,7 +153,7 @@ final class MediaPickerInteractorImpl: MediaPickerInteractor { filtersGroup.enter() filter.apply(image) { resultItem in let isFilterFailed = resultItem == image - if isFilterFailed, let errorMessage = filter.errorMessage { + if isFilterFailed, let errorMessage = filter.fallbackMessage { errorMessages.append(errorMessage) } From a70513a0e6c0c97218bc6e03cfd0fae66e5b169c Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Mon, 28 Aug 2017 17:10:17 +0300 Subject: [PATCH 090/111] SEL-388: Add error message text --- Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift b/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift index 8788bc9e..cda670df 100644 --- a/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift +++ b/Example/PaparazzoExample/Filters/AutoAdjustmentFilter.swift @@ -5,7 +5,7 @@ import CoreGraphics import MobileCoreServices final class AutoAdjustmentFilter: Filter { - let fallbackMessage: String? = "Cannot do anything aaaaaaa".uppercased() + let fallbackMessage: String? = "Failed to apply autocorrection".uppercased() func apply(_ sourceImage: ImageSource, completion: @escaping ((_ resultImage: ImageSource) -> ())) { From 3f3d2a1551f53713feace2663c453e54ea47c913 Mon Sep 17 00:00:00 2001 From: Vadim Smal Date: Mon, 28 Aug 2017 18:06:46 +0300 Subject: [PATCH 091/111] AI-6098: Add the ability to clear the cache --- .../Example/Router/ExampleRouterImpl.swift | 15 ++- .../AppDelegate.swift | 7 +- Paparazzo/Core/DI/AssemblyFactory.swift | 8 +- Paparazzo/Core/DI/ServiceFactory.swift | 11 ++- Paparazzo/Core/Helpers/PhotoStorage.swift | 98 +++++++++++++++++++ .../Core/Services/Camera/CameraService.swift | 2 +- .../Services/Camera/CameraServiceImpl.swift | 35 ++----- .../Camera/Assembly/CameraAssemblyImpl.swift | 6 +- .../MarshrouteAssemblyFactory.swift | 14 ++- 9 files changed, 157 insertions(+), 39 deletions(-) create mode 100644 Paparazzo/Core/Helpers/PhotoStorage.swift diff --git a/Example/PaparazzoExample/Example/Router/ExampleRouterImpl.swift b/Example/PaparazzoExample/Example/Router/ExampleRouterImpl.swift index 000067d7..a4e0bb9c 100644 --- a/Example/PaparazzoExample/Example/Router/ExampleRouterImpl.swift +++ b/Example/PaparazzoExample/Example/Router/ExampleRouterImpl.swift @@ -4,9 +4,18 @@ import Paparazzo final class ExampleRouterImpl: BaseRouter, ExampleRouter { - private let mediaPickerAssemblyFactory = Paparazzo.MarshrouteAssemblyFactory( - theme: PaparazzoUITheme.appSpecificTheme() - ) + private let mediaPickerAssemblyFactory: MarshrouteAssemblyFactory + private let photoStorage: PhotoStorage + + override init(routerSeed seed: RouterSeed) { + self.photoStorage = PhotoStorageImpl() + self.photoStorage.removeAll() + self.mediaPickerAssemblyFactory = Paparazzo.MarshrouteAssemblyFactory( + theme: PaparazzoUITheme.appSpecificTheme(), + photoStorage: photoStorage + ) + super.init(routerSeed: seed) + } // MARK: - ExampleRouter diff --git a/Example/PaparazzoExample_NoMarshroute/AppDelegate.swift b/Example/PaparazzoExample_NoMarshroute/AppDelegate.swift index 19ad5aa9..fe2e08a9 100644 --- a/Example/PaparazzoExample_NoMarshroute/AppDelegate.swift +++ b/Example/PaparazzoExample_NoMarshroute/AppDelegate.swift @@ -17,7 +17,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private func rootViewController() -> UIViewController { - let assemblyFactory = Paparazzo.AssemblyFactory(theme: PaparazzoUITheme.appSpecificTheme()) + let photoStorage = PhotoStorageImpl() + photoStorage.removeAll() + let assemblyFactory = Paparazzo.AssemblyFactory( + theme: PaparazzoUITheme.appSpecificTheme(), + photoStorage: photoStorage + ) let exampleController = ExampleViewController() diff --git a/Paparazzo/Core/DI/AssemblyFactory.swift b/Paparazzo/Core/DI/AssemblyFactory.swift index 0cf6246f..109e38ad 100644 --- a/Paparazzo/Core/DI/AssemblyFactory.swift +++ b/Paparazzo/Core/DI/AssemblyFactory.swift @@ -7,10 +7,14 @@ public final class AssemblyFactory: { private let theme: PaparazzoUITheme - private let serviceFactory = ServiceFactoryImpl() + private let serviceFactory: ServiceFactory - public init(theme: PaparazzoUITheme = PaparazzoUITheme()) { + public init( + theme: PaparazzoUITheme = PaparazzoUITheme(), + photoStorage: PhotoStorage = PhotoStorageImpl()) + { self.theme = theme + self.serviceFactory = ServiceFactoryImpl(photoStorage: photoStorage) } func cameraAssembly() -> CameraAssembly { diff --git a/Paparazzo/Core/DI/ServiceFactory.swift b/Paparazzo/Core/DI/ServiceFactory.swift index 90f22d9d..3e7b34e3 100644 --- a/Paparazzo/Core/DI/ServiceFactory.swift +++ b/Paparazzo/Core/DI/ServiceFactory.swift @@ -9,12 +9,21 @@ protocol ServiceFactory: class { final class ServiceFactoryImpl: ServiceFactory { + private let photoStorage: PhotoStorage + + init (photoStorage: PhotoStorage) { + self.photoStorage = photoStorage + } + func deviceOrientationService() -> DeviceOrientationService { return DeviceOrientationServiceImpl() } func cameraService(initialActiveCameraType: CameraType) -> CameraService { - return CameraServiceImpl(initialActiveCameraType: initialActiveCameraType) + return CameraServiceImpl( + initialActiveCameraType: initialActiveCameraType, + photoStorage: photoStorage + ) } func photoLibraryLatestPhotoProvider() -> PhotoLibraryLatestPhotoProvider { diff --git a/Paparazzo/Core/Helpers/PhotoStorage.swift b/Paparazzo/Core/Helpers/PhotoStorage.swift new file mode 100644 index 00000000..6671c94c --- /dev/null +++ b/Paparazzo/Core/Helpers/PhotoStorage.swift @@ -0,0 +1,98 @@ +import AVFoundation + +public protocol PhotoStorage { + func savePhoto( + sampleBuffer: CMSampleBuffer?, + completion: @escaping (PhotoFromCamera?) -> () + ) + func removePhoto(_ photo: PhotoFromCamera) + func removeAll() +} + + +public final class PhotoStorageImpl: PhotoStorage { + + private static let folderName = "Paparazzo" + + private let createFolder = { + PhotoStorageImpl.createPhotoDirectoryIfNotExist() + }() + + // MARK: - Init + public init() {} + + // MARK: - PhotoStorage + public func savePhoto( + sampleBuffer: CMSampleBuffer?, + completion: @escaping (PhotoFromCamera?) -> ()) + { + let path = randomTemporaryPhotoFilePath() + + DispatchQueue.global(qos: .userInitiated).async { + if let data = sampleBuffer.flatMap({ AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation($0) }) { + do { + try data.write( + to: URL(fileURLWithPath: path), + options: [.atomicWrite] + ) + completion(PhotoFromCamera(path: path)) + } catch { + completion(nil) + } + } else { + completion(nil) + } + } + } + + public func removePhoto(_ photo: PhotoFromCamera) { + do { + try FileManager.default.removeItem(atPath: photo.path) + } catch { + assert(false, "Couldn't remove photo at path \(photo.path)") + } + } + + public func removeAll() { + do { + try FileManager.default.removeItem(atPath: PhotoStorageImpl.photoDirectoryPath()) + PhotoStorageImpl.createPhotoDirectoryIfNotExist() + } catch { + assert(false, "Couldn't remove photo folder") + } + } + + // MARK: - Private + + private static func createPhotoDirectoryIfNotExist() { + var isDirectory : ObjCBool = false + let path = PhotoStorageImpl.photoDirectoryPath() + let exist = FileManager.default.fileExists( + atPath: path, + isDirectory: &isDirectory + ) + if !exist || !isDirectory.boolValue { + do { + try FileManager.default.createDirectory( + atPath: PhotoStorageImpl.photoDirectoryPath(), + withIntermediateDirectories: false, + attributes: nil + ) + } catch { + assert(false, "Couldn't create folder for images") + } + } + } + + private static func photoDirectoryPath() -> String { + let tempDirPath = NSTemporaryDirectory() as NSString + return tempDirPath.appendingPathComponent(PhotoStorageImpl.folderName) + } + + private func randomTemporaryPhotoFilePath() -> String { + let tempName = "\(NSUUID().uuidString).jpg" + let directoryPath = PhotoStorageImpl.photoDirectoryPath() as NSString + return directoryPath.appendingPathComponent(tempName) + } + +} diff --git a/Paparazzo/Core/Services/Camera/CameraService.swift b/Paparazzo/Core/Services/Camera/CameraService.swift index 20dafce4..fb48ed6a 100644 --- a/Paparazzo/Core/Services/Camera/CameraService.swift +++ b/Paparazzo/Core/Services/Camera/CameraService.swift @@ -21,6 +21,6 @@ protocol CameraService: class { func toggleCamera(completion: @escaping (_ newOutputOrientation: ExifOrientation) -> ()) } -struct PhotoFromCamera { +public struct PhotoFromCamera { let path: String } diff --git a/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift b/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift index 7d1ec550..5929e3a1 100644 --- a/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift +++ b/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift @@ -13,6 +13,7 @@ final class CameraServiceImpl: CameraService { private struct Error: Swift.Error {} + private var photoStorage: PhotoStorage private var captureSession: AVCaptureSession? private var output: AVCaptureStillImageOutput? private var backCamera: AVCaptureDevice? @@ -26,7 +27,13 @@ final class CameraServiceImpl: CameraService { // MARK: - Init - init(initialActiveCameraType: CameraType) { + init( + initialActiveCameraType: CameraType, + photoStorage: PhotoStorage) + { + + self.photoStorage = photoStorage + let videoDevices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as? [AVCaptureDevice] backCamera = videoDevices?.filter({ $0.position == .back }).first @@ -230,7 +237,7 @@ final class CameraServiceImpl: CameraService { } output.captureStillImageAsynchronously(from: connection) { [weak self] sampleBuffer, error in - self?.savePhoto(sampleBuffer: sampleBuffer) { photo in + self?.photoStorage.savePhoto(sampleBuffer: sampleBuffer) { photo in DispatchQueue.main.async { completion(photo) } @@ -257,24 +264,6 @@ final class CameraServiceImpl: CameraService { private let captureSessionSetupQueue = DispatchQueue(label: "ru.avito.AvitoMediaPicker.CameraServiceImpl.captureSessionSetupQueue") - private func savePhoto(sampleBuffer: CMSampleBuffer?, completion: @escaping (PhotoFromCamera?) -> ()) { - - let path = randomTemporaryPhotoFilePath() - - DispatchQueue.global(qos: .userInitiated).async { - if let data = sampleBuffer.flatMap({ AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation($0) }) { - do { - try data.write(to: URL(fileURLWithPath: path), options: [.atomicWrite]) - completion(PhotoFromCamera(path: path)) - } catch { - completion(nil) - } - } else { - completion(nil) - } - } - } - private func videoOutputConnection() -> AVCaptureConnection? { guard let output = output else { return nil } @@ -301,12 +290,6 @@ final class CameraServiceImpl: CameraService { camera?.unlockForConfiguration() } - private func randomTemporaryPhotoFilePath() -> String { - let tempDirPath = NSTemporaryDirectory() as NSString - let tempName = "\(NSUUID().uuidString).jpg" - return tempDirPath.appendingPathComponent(tempName) - } - private func outputOrientationForCamera(_ camera: AVCaptureDevice?) -> ExifOrientation { if camera == frontCamera { return .leftMirrored diff --git a/Paparazzo/Core/VIPER/Camera/Assembly/CameraAssemblyImpl.swift b/Paparazzo/Core/VIPER/Camera/Assembly/CameraAssemblyImpl.swift index 83ead72c..ded4fb6e 100644 --- a/Paparazzo/Core/VIPER/Camera/Assembly/CameraAssemblyImpl.swift +++ b/Paparazzo/Core/VIPER/Camera/Assembly/CameraAssemblyImpl.swift @@ -5,10 +5,12 @@ final class CameraAssemblyImpl: BasePaparazzoAssembly, CameraAssembly { // MARK: - CameraAssembly func module(initialActiveCameraType: CameraType) -> (UIView, CameraModuleInput) { - - let cameraService = CameraServiceImpl(initialActiveCameraType: initialActiveCameraType) let deviceOrientationService = DeviceOrientationServiceImpl() + let cameraService = serviceFactory.cameraService( + initialActiveCameraType: initialActiveCameraType + ) + let interactor = CameraInteractorImpl( cameraService: cameraService, deviceOrientationService: deviceOrientationService diff --git a/Paparazzo/Marshroute/MarshrouteAssemblyFactory.swift b/Paparazzo/Marshroute/MarshrouteAssemblyFactory.swift index 030ea524..71b5cc98 100644 --- a/Paparazzo/Marshroute/MarshrouteAssemblyFactory.swift +++ b/Paparazzo/Marshroute/MarshrouteAssemblyFactory.swift @@ -8,14 +8,22 @@ public final class MarshrouteAssemblyFactory: MaskCropperMarshrouteAssemblyFactory { private let theme: PaparazzoUITheme - private let serviceFactory = ServiceFactoryImpl() + private let serviceFactory: ServiceFactory + private let photoStorage: PhotoStorage - public init(theme: PaparazzoUITheme = PaparazzoUITheme()) { + public init(theme: PaparazzoUITheme = PaparazzoUITheme(), + photoStorage: PhotoStorage = PhotoStorageImpl()) + { self.theme = theme + self.photoStorage = photoStorage + self.serviceFactory = ServiceFactoryImpl(photoStorage: photoStorage) } func cameraAssembly() -> CameraAssembly { - return CameraAssemblyImpl(theme: theme, serviceFactory: serviceFactory) + return CameraAssemblyImpl( + theme: theme, + serviceFactory: serviceFactory + ) } public func mediaPickerAssembly() -> MediaPickerMarshrouteAssembly { From d5d366f4b70176af419f70964dba23dce57b1f4d Mon Sep 17 00:00:00 2001 From: Vadim Smal Date: Mon, 28 Aug 2017 18:15:20 +0300 Subject: [PATCH 092/111] AI-6098: Remove unnecessary space --- Paparazzo/Core/Helpers/PhotoStorage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Paparazzo/Core/Helpers/PhotoStorage.swift b/Paparazzo/Core/Helpers/PhotoStorage.swift index 6671c94c..aeecd0f3 100644 --- a/Paparazzo/Core/Helpers/PhotoStorage.swift +++ b/Paparazzo/Core/Helpers/PhotoStorage.swift @@ -65,7 +65,7 @@ public final class PhotoStorageImpl: PhotoStorage { // MARK: - Private private static func createPhotoDirectoryIfNotExist() { - var isDirectory : ObjCBool = false + var isDirectory: ObjCBool = false let path = PhotoStorageImpl.photoDirectoryPath() let exist = FileManager.default.fileExists( atPath: path, From 7b4d0b301f9e9cb71492cb861e0b4297f425a158 Mon Sep 17 00:00:00 2001 From: Vadim Smal Date: Mon, 28 Aug 2017 18:42:26 +0300 Subject: [PATCH 093/111] AI-6098: Remove unnecessary space --- Paparazzo/Core/DI/ServiceFactory.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Paparazzo/Core/DI/ServiceFactory.swift b/Paparazzo/Core/DI/ServiceFactory.swift index 3e7b34e3..dbf5af54 100644 --- a/Paparazzo/Core/DI/ServiceFactory.swift +++ b/Paparazzo/Core/DI/ServiceFactory.swift @@ -11,7 +11,7 @@ final class ServiceFactoryImpl: ServiceFactory { private let photoStorage: PhotoStorage - init (photoStorage: PhotoStorage) { + init(photoStorage: PhotoStorage) { self.photoStorage = photoStorage } From 28b2c90c8de7515abdcd275b1a45f9da94d55893 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 29 Aug 2017 12:55:38 +0300 Subject: [PATCH 094/111] SEL-361: `add support of expose point select --- .../Services/Camera/CameraServiceImpl.swift | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift b/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift index 5929e3a1..7d82b5b5 100644 --- a/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift +++ b/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift @@ -137,17 +137,26 @@ final class CameraServiceImpl: CameraService { } func focusOnPoint(_ focusPoint: CGPoint) -> Bool { - guard let activeCamera = self.activeCamera, activeCamera.isFocusPointOfInterestSupported else { + guard let activeCamera = self.activeCamera, + activeCamera.isFocusPointOfInterestSupported || activeCamera.isExposurePointOfInterestSupported else { return false } do { try activeCamera.lockForConfiguration() - activeCamera.focusPointOfInterest = focusPoint - activeCamera.focusMode = .continuousAutoFocus - activeCamera.exposurePointOfInterest = focusPoint - activeCamera.exposureMode = .continuousAutoExposure + + if activeCamera.isFocusPointOfInterestSupported { + activeCamera.focusPointOfInterest = focusPoint + activeCamera.focusMode = .continuousAutoFocus + } + + if activeCamera.isExposurePointOfInterestSupported { + activeCamera.exposurePointOfInterest = focusPoint + activeCamera.exposureMode = .continuousAutoExposure + } + activeCamera.unlockForConfiguration() + return true } catch { From c6c6e64ae57ef2a179260940a46125d570f11dfa Mon Sep 17 00:00:00 2001 From: Vadim Smal Date: Tue, 29 Aug 2017 16:41:12 +0300 Subject: [PATCH 095/111] AI-6098: Add error to save file assert --- Paparazzo/Core/Helpers/PhotoStorage.swift | 34 +++++++++++-------- .../Services/Camera/CameraServiceImpl.swift | 6 ++-- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Paparazzo/Core/Helpers/PhotoStorage.swift b/Paparazzo/Core/Helpers/PhotoStorage.swift index aeecd0f3..8e853045 100644 --- a/Paparazzo/Core/Helpers/PhotoStorage.swift +++ b/Paparazzo/Core/Helpers/PhotoStorage.swift @@ -3,6 +3,7 @@ import AVFoundation public protocol PhotoStorage { func savePhoto( sampleBuffer: CMSampleBuffer?, + callbackQueue: DispatchQueue, completion: @escaping (PhotoFromCamera?) -> () ) func removePhoto(_ photo: PhotoFromCamera) @@ -24,23 +25,26 @@ public final class PhotoStorageImpl: PhotoStorage { // MARK: - PhotoStorage public func savePhoto( sampleBuffer: CMSampleBuffer?, + callbackQueue: DispatchQueue, completion: @escaping (PhotoFromCamera?) -> ()) { - let path = randomTemporaryPhotoFilePath() - DispatchQueue.global(qos: .userInitiated).async { - if let data = sampleBuffer.flatMap({ AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation($0) }) { + let imageData = sampleBuffer.flatMap({ AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation($0) }) + var photo: PhotoFromCamera? = nil + if let imageData = imageData { + let path = self.randomTemporaryPhotoFilePath() do { - try data.write( + try imageData.write( to: URL(fileURLWithPath: path), options: [.atomicWrite] ) - completion(PhotoFromCamera(path: path)) - } catch { - completion(nil) + photo = PhotoFromCamera(path: path) + } catch let error { + assert(false, "Couldn't save photo at path \(path) with error: \(error)") } - } else { - completion(nil) + } + callbackQueue.async { + completion(photo) } } } @@ -48,8 +52,8 @@ public final class PhotoStorageImpl: PhotoStorage { public func removePhoto(_ photo: PhotoFromCamera) { do { try FileManager.default.removeItem(atPath: photo.path) - } catch { - assert(false, "Couldn't remove photo at path \(photo.path)") + } catch let error { + assert(false, "Couldn't remove photo at path \(photo.path) with error: \(error)") } } @@ -57,8 +61,8 @@ public final class PhotoStorageImpl: PhotoStorage { do { try FileManager.default.removeItem(atPath: PhotoStorageImpl.photoDirectoryPath()) PhotoStorageImpl.createPhotoDirectoryIfNotExist() - } catch { - assert(false, "Couldn't remove photo folder") + } catch let error { + assert(false, "Couldn't remove photo folder with error: \(error)") } } @@ -78,8 +82,8 @@ public final class PhotoStorageImpl: PhotoStorage { withIntermediateDirectories: false, attributes: nil ) - } catch { - assert(false, "Couldn't create folder for images") + } catch let error { + assert(false, "Couldn't create folder for images with error: \(error)") } } } diff --git a/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift b/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift index 5929e3a1..7424fde4 100644 --- a/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift +++ b/Paparazzo/Core/Services/Camera/CameraServiceImpl.swift @@ -237,10 +237,8 @@ final class CameraServiceImpl: CameraService { } output.captureStillImageAsynchronously(from: connection) { [weak self] sampleBuffer, error in - self?.photoStorage.savePhoto(sampleBuffer: sampleBuffer) { photo in - DispatchQueue.main.async { - completion(photo) - } + self?.photoStorage.savePhoto(sampleBuffer: sampleBuffer, callbackQueue: .main) { photo in + completion(photo) } } } From 5fb2da7781ec8cf87dfe9c186fc9b0bf63784c8b Mon Sep 17 00:00:00 2001 From: Vadim Smal Date: Tue, 29 Aug 2017 16:41:49 +0300 Subject: [PATCH 096/111] AI-6098: Move removeAll image cache to AppDelegate --- Example/PaparazzoExample/AppDelegate.swift | 11 ++++++++++- .../Example/Assembly/ExampleAssemblyImpl.swift | 8 ++++++++ .../Example/Router/ExampleRouterImpl.swift | 15 ++++++--------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Example/PaparazzoExample/AppDelegate.swift b/Example/PaparazzoExample/AppDelegate.swift index 009528d3..0b61a0a4 100644 --- a/Example/PaparazzoExample/AppDelegate.swift +++ b/Example/PaparazzoExample/AppDelegate.swift @@ -14,8 +14,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { cleanTemporaryDirectory() window = UIWindow(frame: UIScreen.main.bounds) + + let photoStorage = PhotoStorageImpl() + photoStorage.removeAll() + let mediaPickerAssemblyFactory = Paparazzo.MarshrouteAssemblyFactory( + theme: PaparazzoUITheme.appSpecificTheme(), + photoStorage: photoStorage + ) + let exampleAssembly = ExampleAssemblyImpl(mediaPickerAssemblyFactory: mediaPickerAssemblyFactory) + window?.rootViewController = MarshrouteFacade().navigationController(NavigationController()) { routerSeed in - ExampleAssemblyImpl().viewController(routerSeed: routerSeed) + exampleAssembly.viewController(routerSeed: routerSeed) } window?.makeKeyAndVisible() diff --git a/Example/PaparazzoExample/Example/Assembly/ExampleAssemblyImpl.swift b/Example/PaparazzoExample/Example/Assembly/ExampleAssemblyImpl.swift index 5b40da8b..9b6e2980 100644 --- a/Example/PaparazzoExample/Example/Assembly/ExampleAssemblyImpl.swift +++ b/Example/PaparazzoExample/Example/Assembly/ExampleAssemblyImpl.swift @@ -1,8 +1,15 @@ import UIKit import Marshroute +import Paparazzo final class ExampleAssemblyImpl: ExampleAssembly { + private let mediaPickerAssemblyFactory: MarshrouteAssemblyFactory + + init(mediaPickerAssemblyFactory: MarshrouteAssemblyFactory) { + self.mediaPickerAssemblyFactory = mediaPickerAssemblyFactory + } + // MARK: - ExampleAssembly func viewController(routerSeed: RouterSeed) -> UIViewController { @@ -10,6 +17,7 @@ final class ExampleAssemblyImpl: ExampleAssembly { let interactor = ExampleInteractorImpl() let router = ExampleRouterImpl( + mediaPickerAssemblyFactory: mediaPickerAssemblyFactory, routerSeed: routerSeed ) diff --git a/Example/PaparazzoExample/Example/Router/ExampleRouterImpl.swift b/Example/PaparazzoExample/Example/Router/ExampleRouterImpl.swift index a4e0bb9c..afd424e6 100644 --- a/Example/PaparazzoExample/Example/Router/ExampleRouterImpl.swift +++ b/Example/PaparazzoExample/Example/Router/ExampleRouterImpl.swift @@ -5,16 +5,13 @@ import Paparazzo final class ExampleRouterImpl: BaseRouter, ExampleRouter { private let mediaPickerAssemblyFactory: MarshrouteAssemblyFactory - private let photoStorage: PhotoStorage - override init(routerSeed seed: RouterSeed) { - self.photoStorage = PhotoStorageImpl() - self.photoStorage.removeAll() - self.mediaPickerAssemblyFactory = Paparazzo.MarshrouteAssemblyFactory( - theme: PaparazzoUITheme.appSpecificTheme(), - photoStorage: photoStorage - ) - super.init(routerSeed: seed) + init( + mediaPickerAssemblyFactory: MarshrouteAssemblyFactory, + routerSeed: RouterSeed) + { + self.mediaPickerAssemblyFactory = mediaPickerAssemblyFactory + super.init(routerSeed: routerSeed) } // MARK: - ExampleRouter From 31f21346dfd2aa111716c76ae6056f7af8bccdcf Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 29 Aug 2017 18:07:58 +0300 Subject: [PATCH 097/111] SEL-419: Add support of autocorrection event --- .../Example/Presenter/ExamplePresenter.swift | 1 + .../MediaPicker/Module/MediaPickerModule.swift | 1 + .../Presenter/MediaPickerPresenter.swift | 14 ++++++++++---- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index e5f46198..5271418a 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -137,6 +137,7 @@ final class ExamplePresenter { module.onItemsAdd = { _ in debugPrint("mediaPickerDidAddItems") } module.onItemUpdate = { _ in debugPrint("mediaPickerDidUpdateItem") } module.onItemRemove = { _ in debugPrint("mediaPickerDidRemoveItem") } + module.onItemAutocorrect = { isAutocorrected, _ in debugPrint("mediaPickerDidAutocorrectItem: \(isAutocorrected)") } module.setContinueButtonTitle("Готово") diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift index a6793bf4..5f72d583 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift @@ -29,6 +29,7 @@ public protocol MediaPickerModule: class { // startIndex - index of element in previous array of MediaPickerItem, new elements were added after that index var onItemsAdd: (([MediaPickerItem], _ startIndex: Int) -> ())? { get set } var onItemUpdate: ((MediaPickerItem, _ index: Int?) -> ())? { get set } + var onItemAutocorrect: ((_ isAutocorrected: Bool, _ index: Int?) -> ())? { get set } var onItemMove: ((_ sourceIndex: Int, _ destinationIndex: Int) -> ())? { get set } var onItemRemove: ((MediaPickerItem, _ index: Int?) -> ())? { get set } var onCropFinish: (() -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index bcb27db7..fdbdc075 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -26,6 +26,7 @@ final class MediaPickerPresenter: MediaPickerModule { var onItemsAdd: (([MediaPickerItem], _ startIndex: Int) -> ())? var onItemUpdate: ((MediaPickerItem, _ index: Int?) -> ())? + var onItemAutocorrect: ((_ isAutocorrected: Bool, _ index: Int?) -> ())? var onItemMove: ((_ sourceIndex: Int, _ destinationIndex: Int) -> ())? var onItemRemove: ((MediaPickerItem, _ index: Int?) -> ())? var onCropFinish: (() -> ())? @@ -251,14 +252,14 @@ final class MediaPickerPresenter: MediaPickerModule { view?.onAutocorrectButtonTap = { [weak self] in if let originalItem = self?.interactor.selectedItem?.originalItem { self?.view?.showInfoMessage("РАЗМЫТИЕ ВЫКЛ.", timeout: 1.0) - self?.updateItem(originalItem) + self?.updateItem(originalItem, afterAutocorrect: true) } else { self?.view?.showInfoMessage("РАЗМЫТИЕ ВКЛ.", timeout: 1.0) self?.view?.setAutocorrectionStatus(.corrected) self?.interactor.autocorrectItem( onResult: { [weak self] updatedItem in if let updatedItem = updatedItem { - self?.updateItem(updatedItem) + self?.updateItem(updatedItem, afterAutocorrect: true) } }, onError: { [weak self] errorMessage in if let errorMessage = errorMessage { @@ -290,13 +291,18 @@ final class MediaPickerPresenter: MediaPickerModule { } } - private func updateItem(_ updatedItem: MediaPickerItem) { + private func updateItem(_ updatedItem: MediaPickerItem, afterAutocorrect: Bool = false) { interactor.updateItem(updatedItem) view?.updateItem(updatedItem) adjustPhotoTitleForItem(updatedItem) let index = interactor.indexOfItem(updatedItem) updateAutocorrectionStatusForItem(updatedItem) - onItemUpdate?(updatedItem, index) + + if afterAutocorrect { + onItemAutocorrect?(updatedItem.originalItem != nil, index) + } else { + onItemUpdate?(updatedItem, index) + } } private func adjustViewForSelectedItem(_ item: MediaPickerItem, animated: Bool, scrollToSelected: Bool) { From e23e6ee79c3d52a01077774f1ac9a4f7e37247bc Mon Sep 17 00:00:00 2001 From: Ivan Bondar Date: Tue, 5 Sep 2017 14:07:49 +0300 Subject: [PATCH 098/111] SEL-384: Fix bug with full resolution image fetching (appeared on iOS 11) --- .../ImageCropping/View/CroppingPreviewView.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift b/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift index 695af1fe..0d7269b0 100644 --- a/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift +++ b/Paparazzo/Core/VIPER/ImageCropping/View/CroppingPreviewView.swift @@ -2,9 +2,13 @@ import ImageSource import UIKit final class CroppingPreviewView: UIView { + private static let greatestFiniteMagnitudeSize = CGSize( + width: CGFloat.greatestFiniteMagnitude, + height: CGFloat.greatestFiniteMagnitude + ) /// Максимальный размер оригинальной картинки. Если меньше размера самой картинки, она будет даунскейлиться. - private var sourceImageMaxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) + private var sourceImageMaxSize = CroppingPreviewView.greatestFiniteMagnitudeSize private let previewView = PhotoTweakView() @@ -71,7 +75,11 @@ final class CroppingPreviewView: UIView { } } - let options = ImageRequestOptions(size: .fitSize(sourceImageMaxSize), deliveryMode: .best) + let imageSizeOption: ImageSizeOption = (sourceImageMaxSize == CroppingPreviewView.greatestFiniteMagnitudeSize) + ? .fullResolution + : .fitSize(sourceImageMaxSize) + + let options = ImageRequestOptions(size: imageSizeOption, deliveryMode: .best) image.requestImage(options: options) { [weak self] (result: ImageRequestResult) in if let image = result.image { From cedb2dfa8749ad3cf1cd80fbc5289e4191a20986 Mon Sep 17 00:00:00 2001 From: Ivan Bondar Date: Tue, 5 Sep 2017 16:50:47 +0300 Subject: [PATCH 099/111] SEL-473: Add new item to onAutocorrect callback --- .../PaparazzoExample/Example/Presenter/ExamplePresenter.swift | 2 +- .../Core/VIPER/MediaPicker/Module/MediaPickerModule.swift | 2 +- .../VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift index 5271418a..e9287ae0 100644 --- a/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift +++ b/Example/PaparazzoExample/Example/Presenter/ExamplePresenter.swift @@ -137,7 +137,7 @@ final class ExamplePresenter { module.onItemsAdd = { _ in debugPrint("mediaPickerDidAddItems") } module.onItemUpdate = { _ in debugPrint("mediaPickerDidUpdateItem") } module.onItemRemove = { _ in debugPrint("mediaPickerDidRemoveItem") } - module.onItemAutocorrect = { isAutocorrected, _ in debugPrint("mediaPickerDidAutocorrectItem: \(isAutocorrected)") } + module.onItemAutocorrect = { _, isAutocorrected, _ in debugPrint("mediaPickerDidAutocorrectItem: \(isAutocorrected)") } module.setContinueButtonTitle("Готово") diff --git a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift index 5f72d583..3d1e260d 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Module/MediaPickerModule.swift @@ -29,7 +29,7 @@ public protocol MediaPickerModule: class { // startIndex - index of element in previous array of MediaPickerItem, new elements were added after that index var onItemsAdd: (([MediaPickerItem], _ startIndex: Int) -> ())? { get set } var onItemUpdate: ((MediaPickerItem, _ index: Int?) -> ())? { get set } - var onItemAutocorrect: ((_ isAutocorrected: Bool, _ index: Int?) -> ())? { get set } + var onItemAutocorrect: ((MediaPickerItem, _ isAutocorrected: Bool, _ index: Int?) -> ())? { get set } var onItemMove: ((_ sourceIndex: Int, _ destinationIndex: Int) -> ())? { get set } var onItemRemove: ((MediaPickerItem, _ index: Int?) -> ())? { get set } var onCropFinish: (() -> ())? { get set } diff --git a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift index fdbdc075..31204842 100644 --- a/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift +++ b/Paparazzo/Core/VIPER/MediaPicker/Presenter/MediaPickerPresenter.swift @@ -26,7 +26,7 @@ final class MediaPickerPresenter: MediaPickerModule { var onItemsAdd: (([MediaPickerItem], _ startIndex: Int) -> ())? var onItemUpdate: ((MediaPickerItem, _ index: Int?) -> ())? - var onItemAutocorrect: ((_ isAutocorrected: Bool, _ index: Int?) -> ())? + var onItemAutocorrect: ((MediaPickerItem, _ isAutocorrected: Bool, _ index: Int?) -> ())? var onItemMove: ((_ sourceIndex: Int, _ destinationIndex: Int) -> ())? var onItemRemove: ((MediaPickerItem, _ index: Int?) -> ())? var onCropFinish: (() -> ())? @@ -299,7 +299,7 @@ final class MediaPickerPresenter: MediaPickerModule { updateAutocorrectionStatusForItem(updatedItem) if afterAutocorrect { - onItemAutocorrect?(updatedItem.originalItem != nil, index) + onItemAutocorrect?(updatedItem, updatedItem.originalItem != nil, index) } else { onItemUpdate?(updatedItem, index) } From b6274a2676204a7e053f7fd988f3bbb2fe607087 Mon Sep 17 00:00:00 2001 From: Artem Peskishev Date: Fri, 8 Sep 2017 17:27:36 +0300 Subject: [PATCH 100/111] SEL-496: Prevent focus animation if user denied access to camera --- Paparazzo/Core/VIPER/Camera/View/CameraView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Paparazzo/Core/VIPER/Camera/View/CameraView.swift b/Paparazzo/Core/VIPER/Camera/View/CameraView.swift index 4430e172..c66a63bd 100644 --- a/Paparazzo/Core/VIPER/Camera/View/CameraView.swift +++ b/Paparazzo/Core/VIPER/Camera/View/CameraView.swift @@ -41,7 +41,7 @@ final class CameraView: UIView, CameraViewInput, ThemeConfigurable { super.touchesBegan(touches, with: event) let screenSize = bounds.size - guard screenSize.width != 0 && screenSize.height != 0 else { + guard screenSize.width != 0 && screenSize.height != 0 && accessDeniedView.isHidden == true else { return } From d2a27826be1cacfdb2f2c35d54ac57f6a5993b8d Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Mon, 11 Sep 2017 14:55:00 +0300 Subject: [PATCH 101/111] SEL-512: Update readme --- README.md | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 38b4d515..87fd87e1 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ | :camera: | Taking photos using camera | | :iphone: | Picking photos from user's photo library | | :scissors: | Photo cropping and rotation | +| :droplet: | Applying filters to photos | ![Demo](PaparazzoDemo.gif) @@ -54,31 +55,43 @@ let assembly = factory.mediaPickerAssembly() ``` Create view controller using assembly's `module` method: ```swift -let viewController = assembly.module( +let data = MediaPickerData( items: items, - selectedItem: selectedItem, - maxItemsCount: maxItemsCount, + autocorrectionFilters: filters, + selectedItem: items.last, + maxItemsCount: 20, cropEnabled: true, - cropCanvasSize: cropCanvasSize, + autocorrectEnabled: true, + cropCanvasSize: cropCanvasSize +) + +let viewController = assembly.module( + data: data, routerSeed: routerSeed, // omit this parameter if you're using AssemblyFactory - configuration: configuration + configure: configure ) ``` Method parameters: * _items_ — array of photos that should be initially selected when module is presenter. +* _filters_ — array of filters that can be applyed to photos. * _selectedItem_ — selected photo. If set to `nil` or if _items_ doesn't contain any photo with matching _identifier_, then the first photo in array will be selected. * _maxItemsCount_ — maximum number of photos that user is allowed to pick. * _cropEnabled_ — boolean flag indicating whether user can perform photo cropping. +* _autocorrectEnabled_ — boolean flag indicating whether user can apply filters to photo . * _cropCanvasSize_ — maximum size of canvas when cropping photos. (see [Memory constraints when cropping](#memory-constraints)). * _routerSeed_ — routerSeed provided by Marshroute. -* _configuration_ — closure that allows you to provide [module's additional parameters](#MediaPickerModule). +* _configure_ — closure that allows you to provide [module's additional parameters](#MediaPickerModule). ### Additional parameters of MediaPicker module Additional parameters is described in protocol `MediaPickerModule`: -* `setContinueButtonTitle(_:)` and `setContinueButtonEnabled(_:)` allow to customize "Continue" button text and availability. +* `setContinueButtonTitle(_:)`, `setContinueButtonEnabled(_:)` , `setContinueButtonVisible(_:)` and `setContinueButtonStyle(_:)` allow to customize "Continue" button text and availability. +* `setAccessDeniedTitle(_:)`, `setAccessDeniedMessage(_:)` and `setAccessDeniedButtonTitle(_:)` allow to customize "Access Deined" view texts. +* `setCropMode(_:)` allow to customize photo crop behaivour. * `onItemsAdd` is called when user picks items from photo library or takes a new photo using camera. * `onItemUpdate` is called after user performed cropping. +* `onItemAutocorrect` is called after user applied filter. +* `onItemMove` is called after user moves photo. * `onItemRemove` is called when user deletes photo. * `onFinish` and `onCancel` is called when user taps Continue and Close respectively. @@ -164,8 +177,11 @@ imageSource.imageSize { size in } ``` -# Author +# Authors Andrey Yutkin (ayutkin@avito.ru) +Artem Peskishev (aopeskishev@avito.ru) +Timofey Khomutnikov (tnkhomutnikov@avito.ru) +Vladimir Kaltyrin (vkaltyrin@avito.ru) # License MIT From c908f0d6f94fffedc43209815313a30af355747a Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Mon, 11 Sep 2017 15:16:52 +0300 Subject: [PATCH 102/111] SEL-512: Add docs for crop --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 87fd87e1..e43700d9 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ * [Additional parameters of MediaPicker module](#MediaPickerModule) * [Memory constraints when cropping](#memory-constraints) * [Presenting photo library](#present-gallery) + * [Presenting mask cropper](#present-maskCropper) * [UI Customization](#ui-customization) * [ImageSource](#ImageSource) * [Typical use cases](#use-cases) @@ -118,6 +119,30 @@ let viewController = assembly.module( * _routerSeed_ — routerSeed provided by Marshroute. * _configuration_ — closure used to provide additional module setup. +## Presenting mask cropper +Initialize module assembly using `Paparazzo.AssemblyFactory` (or `Paparazzo.MarshrouteAssemblyFactory` if you use Marshroute): +```swift +let factory = Paparazzo.AssemblyFactory() +let assembly = factory.maskCropperAssembly() +``` +Create view controller using assembly's `module` method: +```swift +let data = MaskCropperData( + imageSource: photo.image, + cropCanvasSize: cropCanvasSize +) +let viewController = assembly.module( + data: data, + croppingOverlayProvider: croppingOverlayProvider, + routerSeed: routerSeed, // omit this parameter if you're using AssemblyFactory + configure: configure +) +``` +* _imageSource_ — photo that should be cropped. +* _croppingOverlayProvider_ — provider from CroppingOverlayProvidersFactory. +* _routerSeed_ — routerSeed provided by Marshroute. +* _configure_ — closure used to provide additional module setup. + ## UI Customization You can customize colors, fonts and icons used in photo picker. Just pass an instance of `PaparazzoUITheme` to the initializer of assembly factory. From 49673ff2cd5c37500952ad8ac522110dc320582a Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Mon, 11 Sep 2017 15:20:07 +0300 Subject: [PATCH 103/111] SEL-512: Improve library docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e43700d9..cb35720f 100644 --- a/README.md +++ b/README.md @@ -111,13 +111,13 @@ let viewController = assembly.module( selectedItems: selectedItems, maxSelectedItemsCount: maxSelectedItemsCount, routerSeed: routerSeed, // omit this parameter if you're using AssemblyFactory - configuration: configuration + configure: configure ) ``` * _selectedItems_ — preselected photos (or `nil`). * _maxItemsCount_ — maximum number of photos that user is allowed to pick. * _routerSeed_ — routerSeed provided by Marshroute. -* _configuration_ — closure used to provide additional module setup. +* _configure_ — closure used to provide additional module setup. ## Presenting mask cropper Initialize module assembly using `Paparazzo.AssemblyFactory` (or `Paparazzo.MarshrouteAssemblyFactory` if you use Marshroute): From e56c464ae9936a7ea25ce24b737025e5d62551b3 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Mon, 11 Sep 2017 15:22:03 +0300 Subject: [PATCH 104/111] SEL-512: Bump version --- ImageSource.podspec | 2 +- Paparazzo.podspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ImageSource.podspec b/ImageSource.podspec index 2b3f80a1..c1b26143 100644 --- a/ImageSource.podspec +++ b/ImageSource.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'ImageSource' s.module_name = 'ImageSource' - s.version = '1.1.0' + s.version = '2.0.0' s.summary = 'ImageSource by Avito' s.homepage = 'https://github.com/avito-tech/Paparazzo' s.license = 'Avito' diff --git a/Paparazzo.podspec b/Paparazzo.podspec index 5d930e41..dd350f0a 100644 --- a/Paparazzo.podspec +++ b/Paparazzo.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'Paparazzo' s.module_name = 'Paparazzo' - s.version = '1.1.0' + s.version = '2.0.0' s.summary = "iOS component for picking and editing photos from camera and user's photo library" s.homepage = 'https://github.com/avito-tech/Paparazzo' s.license = 'MIT' From c894ac7bfd44ecb53531573e3c67e982ddafce40 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Mon, 11 Sep 2017 15:23:33 +0300 Subject: [PATCH 105/111] SEL-512: Improve docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb35720f..eb88b978 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ let data = MediaPickerData( items: items, autocorrectionFilters: filters, selectedItem: items.last, - maxItemsCount: 20, + maxItemsCount: maxItemsCount, cropEnabled: true, autocorrectEnabled: true, cropCanvasSize: cropCanvasSize From 6388f5a569d7ca7287069942ffa4926d6f71228e Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Mon, 11 Sep 2017 15:56:10 +0300 Subject: [PATCH 106/111] SEL-512: Improve readme --- README.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index eb88b978..af1d8dfb 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ | :camera: | Taking photos using camera | | :iphone: | Picking photos from user's photo library | | :scissors: | Photo cropping and rotation | -| :droplet: | Applying filters to photos | +| :droplet: | Applying filters to photos | ![Demo](PaparazzoDemo.gif) @@ -49,7 +49,7 @@ pod "Paparazzo/Core" You can use either the entire module or photo library exclusively. ## Presenting entire module -Initialize module assembly using `Paparazzo.AssemblyFactory` (or `Paparazzo.MarshrouteAssemblyFactory` if you use Marshroute): +Initialize module assembly using `Paparazzo.AssemblyFactory` (or `Paparazzo.MarshrouteAssemblyFactory` if you use [Marshroute](https://github.com/avito-tech/Marshroute)): ```swift let factory = Paparazzo.AssemblyFactory() let assembly = factory.mediaPickerAssembly() @@ -68,13 +68,13 @@ let data = MediaPickerData( let viewController = assembly.module( data: data, - routerSeed: routerSeed, // omit this parameter if you're using AssemblyFactory + routerSeed: routerSeed, // omit this parameter if you're using Paparazzo.AssemblyFactory configure: configure ) ``` Method parameters: * _items_ — array of photos that should be initially selected when module is presenter. -* _filters_ — array of filters that can be applyed to photos. +* _filters_ — array of filters that can be applied to photos. * _selectedItem_ — selected photo. If set to `nil` or if _items_ doesn't contain any photo with matching _identifier_, then the first photo in array will be selected. * _maxItemsCount_ — maximum number of photos that user is allowed to pick. * _cropEnabled_ — boolean flag indicating whether user can perform photo cropping. @@ -88,11 +88,11 @@ Additional parameters is described in protocol `MediaPickerModule`: * `setContinueButtonTitle(_:)`, `setContinueButtonEnabled(_:)` , `setContinueButtonVisible(_:)` and `setContinueButtonStyle(_:)` allow to customize "Continue" button text and availability. * `setAccessDeniedTitle(_:)`, `setAccessDeniedMessage(_:)` and `setAccessDeniedButtonTitle(_:)` allow to customize "Access Deined" view texts. -* `setCropMode(_:)` allow to customize photo crop behaivour. +* `setCropMode(_:)` allow to customize photo crop behavior. * `onItemsAdd` is called when user picks items from photo library or takes a new photo using camera. * `onItemUpdate` is called after user performed cropping. -* `onItemAutocorrect` is called after user applied filter. -* `onItemMove` is called after user moves photo. +* `onItemAutocorrect` is called after applying filter. +* `onItemMove` is called after moving photo. * `onItemRemove` is called when user deletes photo. * `onFinish` and `onCancel` is called when user taps Continue and Close respectively. @@ -100,7 +100,7 @@ Additional parameters is described in protocol `MediaPickerModule`: When cropping photo on devices with low RAM capacity your application can crash due to memory warning. It happens because in order to perform actual cropping we need to put a bitmap of the original photo in memory. To descrease a chance of crashing on older devices (such as iPhone 4 or 4s) we can scale the source photo beforehand so that it takes up less space in memory. _cropCanvasSize_ is used for that. It specifies the size of the photo we should be targeting when scaling. ## Presenting photo library -Initialize module assembly using `Paparazzo.AssemblyFactory` (or `Paparazzo.MarshrouteAssemblyFactory` if you use Marshroute): +Initialize module assembly using `Paparazzo.AssemblyFactory` (or `Paparazzo.MarshrouteAssemblyFactory` if you use [Marshroute](https://github.com/avito-tech/Marshroute)): ```swift let factory = Paparazzo.AssemblyFactory() let assembly = factory.photoLibraryAssembly() @@ -110,7 +110,7 @@ Create view controller using assembly's `module` method: let viewController = assembly.module( selectedItems: selectedItems, maxSelectedItemsCount: maxSelectedItemsCount, - routerSeed: routerSeed, // omit this parameter if you're using AssemblyFactory + routerSeed: routerSeed, // omit this parameter if you're using Paparazzo.AssemblyFactory configure: configure ) ``` @@ -120,7 +120,9 @@ let viewController = assembly.module( * _configure_ — closure used to provide additional module setup. ## Presenting mask cropper -Initialize module assembly using `Paparazzo.AssemblyFactory` (or `Paparazzo.MarshrouteAssemblyFactory` if you use Marshroute): +MaskCropper is a module which provides easy way to customize cropping experience. See CroppingOverlayProvider protocol to get more details. + +Initialize module assembly using `Paparazzo.AssemblyFactory` (or `Paparazzo.MarshrouteAssemblyFactory` if you use [Marshroute](https://github.com/avito-tech/Marshroute)): ```swift let factory = Paparazzo.AssemblyFactory() let assembly = factory.maskCropperAssembly() @@ -134,7 +136,7 @@ let data = MaskCropperData( let viewController = assembly.module( data: data, croppingOverlayProvider: croppingOverlayProvider, - routerSeed: routerSeed, // omit this parameter if you're using AssemblyFactory + routerSeed: routerSeed, // omit this parameter if you're using Paparazzo.AssemblyFactory configure: configure ) ``` From df369ab5928f09d3789bbc2154cd34c58af80158 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 12 Sep 2017 11:55:53 +0300 Subject: [PATCH 107/111] SEL-512: Add changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..9e93bdeb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +## 2.0.0 (2017-09-12) +[Compare]:https://github.com/avito-tech/Paparazzo/compare/1.1.0...2.0.0 + +##### Enhancements + +* Add support for photo filters + [Timofey Khomutnikov](https://github.com/khomTima) + +* Add supprot for mask cropping + [Vladimir Kaltyrin](https://github.com/vkaltyrin) + +* Add support for photo reordering + [Artem Peskishev](https://github.com/paulb777) + +* Add cache cleanup support + Vadim Smal + +##### Bug Fixes + +* Minor fixes and improvements From 4ddb19e621c7cee5120bc7538689e065abf007e7 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 12 Sep 2017 11:57:27 +0300 Subject: [PATCH 108/111] SEL-512: Add github link --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e93bdeb..95d6530c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ [Vladimir Kaltyrin](https://github.com/vkaltyrin) * Add support for photo reordering - [Artem Peskishev](https://github.com/paulb777) + [Artem Peskishev](https://github.com/peskish) * Add cache cleanup support Vadim Smal From cbd8646edca4f434cfc46849593fb11d20b8bd4f Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 12 Sep 2017 11:59:55 +0300 Subject: [PATCH 109/111] SEL-512: Add link to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index af1d8dfb..105a8d9d 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ ![Version](https://cocoapod-badges.herokuapp.com/v/Paparazzo/badge.png) ![License](https://img.shields.io/badge/license-MIT-blue.svg) +[Changelog](https://github.com/avito-tech/Paparazzo/blob/master/CHANGELOG.md) | See the changes introduced in each Paparazzo version. + **Paparazzo** is a component for picking and editing photos. | | Key Features | From 65fc7602ea67dd96bd6f6d79788ac333414536d2 Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 12 Sep 2017 12:19:24 +0300 Subject: [PATCH 110/111] SEL-512: Move changelog --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 105a8d9d..317acc4b 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ ![Version](https://cocoapod-badges.herokuapp.com/v/Paparazzo/badge.png) ![License](https://img.shields.io/badge/license-MIT-blue.svg) -[Changelog](https://github.com/avito-tech/Paparazzo/blob/master/CHANGELOG.md) | See the changes introduced in each Paparazzo version. - **Paparazzo** is a component for picking and editing photos. | | Key Features | @@ -16,6 +14,8 @@ ![Demo](PaparazzoDemo.gif) +[Changelog](https://github.com/avito-tech/Paparazzo/blob/master/CHANGELOG.md) | See the changes introduced in each Paparazzo version. + # Contents * [Installation](#installation) From a54c957db0f7042f238c40630d2b9c847450d20f Mon Sep 17 00:00:00 2001 From: Timofey Khomutnikov Date: Tue, 12 Sep 2017 13:02:43 +0300 Subject: [PATCH 111/111] SEL-512: Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95d6530c..1d5bcf70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,8 @@ * Add support for photo reordering [Artem Peskishev](https://github.com/peskish) -* Add cache cleanup support - Vadim Smal +* Add support for cache cleaning + [Vadim Smal](https://github.com/CognitiveDisson) ##### Bug Fixes