diff --git a/Nynja/BuildNumberItemsFactory.swift b/Nynja/BuildNumberItemsFactory.swift new file mode 100644 index 0000000000000000000000000000000000000000..4b13ac4317581de298cece71822dbde91a094f63 --- /dev/null +++ b/Nynja/BuildNumberItemsFactory.swift @@ -0,0 +1,18 @@ +// +// BuildNumberItemsFactory.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +class BuildNumberItemsFactory: OptionsItemsFactory { + + // MARK: - Second lvl + + override var buildNumber: ImageActionItemModel { + let item = super.buildNumber + item.state = .highlighted + return item + } +} diff --git a/Nynja/ChatState.swift b/Nynja/ChatState.swift index 896c9fbfbed4ca47b33dea58f734708a7873053f..3b2ec87fc7ae3c20018a5353e2ac1610e6fe4c28 100644 --- a/Nynja/ChatState.swift +++ b/Nynja/ChatState.swift @@ -19,18 +19,8 @@ final class ChatState { case room(ChatIdentifier) } - private final class ChatStateObserver { - let object: WeakRef - let handler: Handler - - init(object: AnyObject, handler: Handler) { - self.object = WeakRef(value: object) - self.handler = handler - } - } - - private var observers: [ChatStateObserver] = [] - private var totalCountObservers: [ChatStateObserver] = [] + private var observers: [AnyWeakSubscriber] = [] + private var totalCountObservers: [AnyWeakSubscriber] = [] private var contacts: [ChatIdentifier: Int64] = [:] private var contactsCount: Int64 { @@ -88,12 +78,12 @@ final class ChatState { } func observeUnreadCount(_ object: AnyObject, handler: @escaping ChatHandler) { - let subscriber = ChatStateObserver(object: object, handler: handler) + let subscriber = AnyWeakSubscriber(object: object, handler: handler) observers.append(subscriber) } func observeUnreadCount(_ object: AnyObject, in chat: Chat, handler: @escaping ChatHandler) { - let subscriber = ChatStateObserver(object: object, handler: handler) + let subscriber = AnyWeakSubscriber(object: object, handler: handler) observers.append(subscriber) // Always return the current value on registering observer. @@ -109,7 +99,7 @@ final class ChatState { } func observeTotalUnreadCount(_ object: AnyObject, handler: @escaping TotalHandler) { - let subscriber = ChatStateObserver(object: object, handler: handler) + let subscriber = AnyWeakSubscriber(object: object, handler: handler) totalCountObservers.append(subscriber) handler(self.totalUnreadCount) diff --git a/Nynja/Extensions/Bundle+Keys.swift b/Nynja/Extensions/Bundle+Keys.swift new file mode 100644 index 0000000000000000000000000000000000000000..255e4c48f0cfe32df102a8c3bc8c5e2990860006 --- /dev/null +++ b/Nynja/Extensions/Bundle+Keys.swift @@ -0,0 +1,31 @@ +// +// Bundle+Keys.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +extension Bundle { + + public var bundleIdentifier: String { + return object(forInfoDictionaryKey: kCFBundleIdentifierKey as String) as! String + } + + /// Project bundle name + public var bundleName: String { + return object(forInfoDictionaryKey: kCFBundleNameKey as String) as! String + } + + /// App name which displaying in Springboard + public var displayName: String { + let displayName = object(forInfoDictionaryKey: "CFBundleDisplayName") as? String + return displayName ?? self.bundleName + } + + public var buildVersion: String? { + return object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String + } +} diff --git a/Nynja/Library/UI/CollectionView/Layout/CarouselFlowLayout.swift b/Nynja/Library/UI/CollectionView/Layout/CarouselFlowLayout.swift new file mode 100644 index 0000000000000000000000000000000000000000..a4a746e941c2e1ebcb822a4314b1a822c774b63a --- /dev/null +++ b/Nynja/Library/UI/CollectionView/Layout/CarouselFlowLayout.swift @@ -0,0 +1,146 @@ +// +// CarouselFlowLayout.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +public enum CarouselFlowLayoutSpacingMode { + case fixed(spacing: CGFloat) + case overlap(visibleOffset: CGFloat) +} + +/* + Source: https://github.com/ink-spot/UPCarouselFlowLayout/blob/master/UPCarouselFlowLayout/UPCarouselFlowLayout.swift + */ +open class CarouselFlowLayout: UICollectionViewFlowLayout { + + fileprivate struct LayoutState { + var size: CGSize + var direction: UICollectionViewScrollDirection + func isEqual(_ otherState: LayoutState) -> Bool { + return self.size.equalTo(otherState.size) && self.direction == otherState.direction + } + } + + @IBInspectable open var sideItemScale: CGFloat = 0.6 + @IBInspectable open var sideItemAlpha: CGFloat = 0.6 + @IBInspectable open var sideItemShift: CGFloat = 0.0 + + open var spacingMode = CarouselFlowLayoutSpacingMode.fixed(spacing: 40) + + fileprivate var state = LayoutState(size: CGSize.zero, direction: .horizontal) + + + override open func prepare() { + super.prepare() + + let currentState = LayoutState(size: self.collectionView!.bounds.size, direction: self.scrollDirection) + + if !self.state.isEqual(currentState) { + self.setupCollectionView() + self.updateLayout() + self.state = currentState + } + } + + fileprivate func setupCollectionView() { + guard let collectionView = self.collectionView else { return } + if collectionView.decelerationRate != UIScrollViewDecelerationRateFast { + collectionView.decelerationRate = UIScrollViewDecelerationRateFast + } + } + + fileprivate func updateLayout() { + guard let collectionView = self.collectionView else { return } + + let collectionSize = collectionView.bounds.size + let isHorizontal = (self.scrollDirection == .horizontal) + + let yInset = (collectionSize.height - self.itemSize.height) / 2 + let xInset = (collectionSize.width - self.itemSize.width) / 2 + self.sectionInset = UIEdgeInsetsMake(yInset, xInset, yInset, xInset) + + let side = isHorizontal ? self.itemSize.width : self.itemSize.height + let scaledItemOffset = (side - side*self.sideItemScale) / 2 + switch self.spacingMode { + case .fixed(let spacing): + self.minimumLineSpacing = spacing - scaledItemOffset + case .overlap(let visibleOffset): + let fullSizeSideItemOverlap = visibleOffset + scaledItemOffset + let inset = isHorizontal ? xInset : yInset + self.minimumLineSpacing = inset - fullSizeSideItemOverlap + } + } + + override open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + return true + } + + override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + return super.layoutAttributesForElements(in: rect)?.map { self.transformLayoutAttributes($0) } + } + + fileprivate func transformLayoutAttributes(_ attributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + guard let collectionView = self.collectionView else { return attributes } + let isHorizontal = (self.scrollDirection == .horizontal) + + let collectionCenter = isHorizontal ? collectionView.frame.size.width / 2 : collectionView.frame.size.height / 2 + let offset = isHorizontal ? collectionView.contentOffset.x : collectionView.contentOffset.y + let normalizedCenter = (isHorizontal ? attributes.center.x : attributes.center.y) - offset + + let maxDistance = (isHorizontal ? self.itemSize.width : self.itemSize.height) + self.minimumLineSpacing + let distance = min(abs(collectionCenter - normalizedCenter), maxDistance) + let ratio = (maxDistance - distance)/maxDistance + + let alpha = ratio * (1 - self.sideItemAlpha) + self.sideItemAlpha + let scale = ratio * (1 - self.sideItemScale) + self.sideItemScale + let shift = (1 - ratio) * self.sideItemShift + attributes.alpha = alpha + attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1) + attributes.zIndex = Int(alpha * 10) + + if isHorizontal { + attributes.center.y = attributes.center.y + shift + } else { + attributes.center.x = attributes.center.x + shift + } + + return attributes + } + + override open func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { + guard + let collectionView = collectionView , !collectionView.isPagingEnabled, + let layoutAttributes = self.layoutAttributesForElements(in: collectionView.bounds) + else { + return super.targetContentOffset(forProposedContentOffset: proposedContentOffset) + } + let isHorizontal = (self.scrollDirection == .horizontal) + + let midSide = (isHorizontal ? collectionView.bounds.size.width : collectionView.bounds.size.height) / 2 + + let proposedContentOffsetCenterOrigin = isHorizontal + ? proposedContentOffset.x + velocity.x * itemSize.width + midSide + : proposedContentOffset.y + velocity.y * itemSize.height + midSide + + let targetContentOffset: CGPoint + if isHorizontal { + let closest = layoutAttributes.sorted { + abs($0.center.x - proposedContentOffsetCenterOrigin) < abs($1.center.x - proposedContentOffsetCenterOrigin) + }.first ?? UICollectionViewLayoutAttributes() + + targetContentOffset = CGPoint(x: floor(closest.center.x - midSide), y: proposedContentOffset.y) + } else { + let closest = layoutAttributes.sorted { + abs($0.center.y - proposedContentOffsetCenterOrigin) < abs($1.center.y - proposedContentOffsetCenterOrigin) + }.first ?? UICollectionViewLayoutAttributes() + + targetContentOffset = CGPoint(x: proposedContentOffset.x, y: floor(closest.center.y - midSide)) + } + return targetContentOffset + } +} diff --git a/Nynja/Library/UI/Layout.swift b/Nynja/Library/UI/Layout.swift index f8fa6ccff4bb6db263ca71b2eb3b7a796b394584..f6412798f468bc4bcacb50e52faf7807cec4dde5 100644 --- a/Nynja/Library/UI/Layout.swift +++ b/Nynja/Library/UI/Layout.swift @@ -53,7 +53,7 @@ extension CGFloat { return restored(from: .height) } - private func adjusted(by side: AdjustedSide) -> CGFloat { + fileprivate func adjusted(by side: AdjustedSide) -> CGFloat { guard iPhone6plusSize != UIScreen.main.bounds.size else { return self } @@ -61,7 +61,7 @@ extension CGFloat { return ceil(self * side.current / side.mockup) } - private func restored(from side: AdjustedSide) -> CGFloat { + fileprivate func restored(from side: AdjustedSide) -> CGFloat { guard iPhone6plusSize != UIScreen.main.bounds.size else { return self } @@ -190,3 +190,40 @@ extension Int { } } + +extension CGSize { + + /// Adjusted value by width of iPhone 6+ screen size to current screen size. + var adjustedByWidth: CGSize { + return adjusted(by: .width) + } + + /// Adjusted value by height of iPhone 6+ screen size to current screen size. + var adjustedByHeight: CGSize { + return adjusted(by: .height) + } + + /// Adjusted value by width of iPhone 6+ screen size to current screen size. + var restoredFromWidth: CGSize { + return restored(from: .width) + } + + /// Adjusted value by height of iPhone 6+ screen size to current screen size. + var restoredFromHeight: CGSize { + return restored(from: .height) + } + + private func adjusted(by side: AdjustedSide) -> CGSize { + guard iPhone6plusSize != UIScreen.main.bounds.size else { + return self + } + return CGSize(width: width.adjusted(by: side), height: height.adjusted(by: side)) + } + + private func restored(from side: AdjustedSide) -> CGSize { + guard iPhone6plusSize != UIScreen.main.bounds.size else { + return self + } + return CGSize(width: width.restored(from: side), height: height.restored(from: side)) + } +} diff --git a/Nynja/Library/UI/WheelContainer/Wheel/ItemModels/WheelItemModel.swift b/Nynja/Library/UI/WheelContainer/Wheel/ItemModels/WheelItemModel.swift index 09da84369e6e3b5b0742cad55062e8c40f819292..16a245299061cd1d3f56767ff35f25364579a7e5 100644 --- a/Nynja/Library/UI/WheelContainer/Wheel/ItemModels/WheelItemModel.swift +++ b/Nynja/Library/UI/WheelContainer/Wheel/ItemModels/WheelItemModel.swift @@ -129,6 +129,10 @@ class WheelItemModel: WheelItemModelDelegate { /// Default: empty array var subitems: ItemModels = [] + /// true - if left wheel position is currently selected, otherwise - false. + var isReversed: Bool = false + + // MARK: - Init init() {} @@ -137,6 +141,7 @@ class WheelItemModel: WheelItemModelDelegate { self.subitems = model.subitems self.isSeparatorVisible = model.isSeparatorVisible self.isSelectable = model.isSelectable + self.isReversed = model.isReversed self.delegate = model.delegate } diff --git a/Nynja/Library/UI/WheelContainer/Wheel/ItemViews/WheelItemView.swift b/Nynja/Library/UI/WheelContainer/Wheel/ItemViews/WheelItemView.swift index 90665210f5586dd0b8f02d09a97f17a0d138716c..5414d2618b45dbe45f64fbff47dd43ea3f95ecef 100644 --- a/Nynja/Library/UI/WheelContainer/Wheel/ItemViews/WheelItemView.swift +++ b/Nynja/Library/UI/WheelContainer/Wheel/ItemViews/WheelItemView.swift @@ -108,8 +108,16 @@ class WheelItemView: UIView { adjustContentViewMask(contentView.bounds) // Draw separator line + if model?.isSeparatorVisible ?? true { - self.drawSeparator(from: point1, to: point4) + if model?.isReversed ?? false { + // Draw separator on right side of the view. + let from = CGPoint(x: rect.maxX, y: rect.minY) + let to = CGPoint(x: rect.maxX - (rect.width - CGFloat(widthBottom)) / 2, y: rect.maxY) + self.drawSeparator(from: from, to: to, in: rect) + } else { + self.drawSeparator(from: point1, to: point4, in: rect) + } } // Draw marker @@ -190,9 +198,19 @@ fileprivate extension WheelItemView { markerLayer?.removeFromSuperlayer() // Define points of pin - let point1 = CGPoint(x: rect.minX, y: rect.minY) - let point2 = CGPoint(x: rect.minX + 20, y: rect.minY) - let point3 = CGPoint(x: rect.minX, y: rect.minY + 30) + let point1: CGPoint + let point2: CGPoint + let point3: CGPoint + + if model?.isReversed ?? false { + point1 = CGPoint(x: rect.maxX, y: rect.minY) + point2 = CGPoint(x: rect.maxX - 20, y: rect.minY) + point3 = CGPoint(x: rect.maxX, y: rect.minY + 30) + } else { + point1 = CGPoint(x: rect.minX, y: rect.minY) + point2 = CGPoint(x: rect.minX + 20, y: rect.minY) + point3 = CGPoint(x: rect.minX, y: rect.minY + 30) + } let markerPath = UIBezierPath() markerPath.move(to: point1) @@ -207,7 +225,7 @@ fileprivate extension WheelItemView { layer.addSublayer(markerLayer!) } - func drawSeparator(from: CGPoint, to: CGPoint) { + func drawSeparator(from: CGPoint, to: CGPoint, in rect: CGRect) { separatorLayer?.removeFromSuperlayer() // Calculate distance between two points @@ -225,11 +243,23 @@ fileprivate extension WheelItemView { var point2 = interpolate(point1: from, point2: to, alpha: (delta + separatorOffset.top.adjustedByWidth) / distance) var point3 = interpolate(point1: from, point2: to, alpha: (distance - separatorOffset.bottom.adjustedByWidth) / distance) - let point1 = CGPoint(x: 0, y: point2.y) - let point4 = CGPoint(x: 0, y: point3.y) - point2.x += 1 - point3.x += 1 + let point1: CGPoint + let point4: CGPoint + let offset: CGFloat + + if model?.isReversed ?? false { + point1 = CGPoint(x: rect.maxX, y: point2.y) + point4 = CGPoint(x: rect.maxX, y: point3.y) + offset = 0 + } else { + point1 = CGPoint(x: 0, y: point2.y) + point4 = CGPoint(x: 0, y: point3.y) + offset = 1 + } + + point2.x += offset + point3.x += offset let path = UIBezierPath() path.move(to: point1) diff --git a/Nynja/Library/UI/WheelContainer/WheelContainer.swift b/Nynja/Library/UI/WheelContainer/WheelContainer.swift index 02b81b1ef8cb769f16fb9121984cab1ac1801c62..fec51e0bd6326eb8dede1f358b5d0db5f453a649 100644 --- a/Nynja/Library/UI/WheelContainer/WheelContainer.swift +++ b/Nynja/Library/UI/WheelContainer/WheelContainer.swift @@ -15,7 +15,7 @@ private typealias AnimationCompletion = () -> Void /// It uses two type of index path /// 1. Full index path: , where size of index path is equal to number of wheels in container /// 2. Simple index path: , where level - is an index of wheel and item is and index of item view -class WheelContainer: UIView { +class WheelContainer: UIView, UISettingsRespondable { weak var delegate: WheelContainerDelegate? weak var dataSource: WheelContainerDataSource? @@ -70,17 +70,23 @@ class WheelContainer: UIView { convenience init(with configurations: Configurations) { self.init() configure(with: configurations) + registerForUserInterfaceSettingsNotifications() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + deinit { + unregisterForUserInterfaceSettingsNotifications() + } + func configure(with configurations: Configurations) { backgroundColor = UIColor.clear clipsToBounds = true wheels = createWheels(with: configurations) + } override func draw(_ rect: CGRect) { @@ -207,7 +213,7 @@ class WheelContainer: UIView { wheel.configuration = configuration // Rotate wheel, so the 1st item is in the center of visible part - wheel.transform = CGAffineTransform(rotationAngle: CGFloat(-Double.pi / 4)) + setupTransform(forWheel: wheel) // Set selection handler wheel.selectHandler = { [unowned self] index in @@ -505,6 +511,21 @@ class WheelContainer: UIView { borderView.removeFromSuperview() } + // MARK: UISettingsRespondable + + func userInterfaceSettingsDidChange(_ newSettings: UISettings) { + wheels.forEach { setupTransform(forWheel: $0, with: newSettings) } + } + + private func setupTransform(forWheel wheel: Wheel, with settings: UISettings = .default) { + switch settings.wheelPosition { + case .right: + wheel.transform = CGAffineTransform(rotationAngle: CGFloat(-Double.pi / 4)) + case .left: + wheel.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 4)) + } + } + // MARK: Utils private func wheel(for level: Int) -> Wheel? { return level >= 0 && level < wheels.count ? wheels[level] : nil diff --git a/Nynja/Library/UI/WheelContainer/iCarousel/iCarousel.m b/Nynja/Library/UI/WheelContainer/iCarousel/iCarousel.m index a10cd87ae2920259b4933894a4efc6e1a4c7bd3c..998c64b096c0dbc9b6ed997d1682137c336aa3bd 100644 --- a/Nynja/Library/UI/WheelContainer/iCarousel/iCarousel.m +++ b/Nynja/Library/UI/WheelContainer/iCarousel/iCarousel.m @@ -843,6 +843,7 @@ NSComparisonResult compareViewDepth(UIView *view1, UIView *view2, iCarousel *sel view.frame = frame; [containerView addSubview:view]; containerView.layer.opacity = 0; + containerView.layer.zPosition = view.layer.zPosition; return containerView; } diff --git a/Nynja/Modules/Main/MainProtocols.swift b/Nynja/Modules/Main/MainProtocols.swift index cc31dcaa96d6438180bd4dbffee81c48d7bbe1bc..7e732ec29519cd1b59565b9a5ce9fcba15e27c98 100644 --- a/Nynja/Modules/Main/MainProtocols.swift +++ b/Nynja/Modules/Main/MainProtocols.swift @@ -48,6 +48,9 @@ protocol MainWireFrameProtocol: class { func getContact() -> String? func viewShowed() func showMessages(contact: Contact, callVC: CallViewProtocol, isVideo: Bool) + func showWheelPositionPicker() + func showBuildNumber() + func showThemePicker() func logout() func hideReturnToCallView() func returnToCall() @@ -135,6 +138,9 @@ protocol MainPresenterProtocol: class { func openScheduleMessageScreen(text : String?, audioUrl : URL?) func returnToCall() func about() + func showWheelPositionPicker() + func showBuildNumber() + func showThemePicker() func openMapView() func deleteAccount() diff --git a/Nynja/Modules/Main/Presenter/MainPresenter.swift b/Nynja/Modules/Main/Presenter/MainPresenter.swift index 1e8b845d4da352da4323068cd97497cd4b159d66..ef20a298208520487b5c399b4dceb2526a56587b 100644 --- a/Nynja/Modules/Main/Presenter/MainPresenter.swift +++ b/Nynja/Modules/Main/Presenter/MainPresenter.swift @@ -163,6 +163,18 @@ class MainPresenter: MainPresenterProtocol, MainInteractorOutputProtocol, Schedu self.wireFrame.showSplash() } + func showWheelPositionPicker() { + self.wireFrame.showWheelPositionPicker() + } + + func showBuildNumber() { + self.wireFrame.showBuildNumber() + } + + func showThemePicker() { + self.wireFrame.showThemePicker() + } + func showContactsToShare() { self.wireFrame.showContactsToShare() } diff --git a/Nynja/Modules/Main/View/MainNavigationItem.swift b/Nynja/Modules/Main/View/MainNavigationItem.swift index 9bd0e98c80936ba330ff8400db81374ea2f15038..1f517d3680b5b2dae54d75e199c6e3619d10d320 100644 --- a/Nynja/Modules/Main/View/MainNavigationItem.swift +++ b/Nynja/Modules/Main/View/MainNavigationItem.swift @@ -81,6 +81,9 @@ enum MainNavigationItem: String { // Options section case options = "Setting" + case wheelPosition = "Wheel position" + case buildNumber = "Build number" + case theme = "Theme" case logOut = "LOG OUT" case about = "About" case deleteAccount = "Delete Account" diff --git a/Nynja/Modules/Main/View/MainViewController.swift b/Nynja/Modules/Main/View/MainViewController.swift index 1a5908e6549cfafb21f6055a908de988e79a0bb7..63327e3e7f8668ec39ef612b19124a051fdf784b 100644 --- a/Nynja/Modules/Main/View/MainViewController.swift +++ b/Nynja/Modules/Main/View/MainViewController.swift @@ -13,7 +13,7 @@ import MobileCoreServices import AssetsLibrary -class MainViewController: BaseVC, MainViewProtocol, BaseInputViewProtocol, DefaultInputViewProtocol, RecordViewProtocol, ImageSelectorDelegate, HitTestDelegate, ALTextInputBarDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, CustomImagePickerDelegate { +class MainViewController: BaseVC, MainViewProtocol, BaseInputViewProtocol, DefaultInputViewProtocol, RecordViewProtocol, ImageSelectorDelegate, HitTestDelegate, ALTextInputBarDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, CustomImagePickerDelegate, UISettingsRespondable { var presenter: MainPresenterProtocol! @@ -100,17 +100,7 @@ class MainViewController: BaseVC, MainViewProtocol, BaseInputViewProtocol, Defau self.view.addSubview(btn) - let centerX = Constraints.nextButton.centerX.adjustedByWidth - - btn.snp.makeConstraints({ (make) in - make.width.height.equalTo(self.smallButtonWidth) - make.centerX.equalTo(self.view.snp.trailing).offset(-centerX) - if #available(iOS 11, *) { - make.centerY.equalTo(self.view.safeAreaLayoutGuide.snp.bottomMargin).offset(-centerX) - } else { - make.centerY.equalTo(self.view.snp.bottom).offset(-centerX) - } - }) + self.setupWheelButton(btn) return btn }() @@ -281,6 +271,10 @@ class MainViewController: BaseVC, MainViewProtocol, BaseInputViewProtocol, Defau } + deinit { + unregisterForUserInterfaceSettingsNotifications() + } + func updateWheelGestures(enabled: Bool) { if let oldGestures = nextButton.gestureRecognizers { for i in oldGestures { @@ -304,6 +298,7 @@ class MainViewController: BaseVC, MainViewProtocol, BaseInputViewProtocol, Defau // MARK: BaseVC + override func initialize() { super.initialize() @@ -318,8 +313,35 @@ class MainViewController: BaseVC, MainViewProtocol, BaseInputViewProtocol, Defau inputBarDelegate = self defaultInputView.isHidden = true recordInputView.isHidden = true + + registerForUserInterfaceSettingsNotifications() + } + + + // MARK - UI Setup + + private func setupWheelButton(_ button: UIButton, with settings: UISettings = .default) { + let centerX = Constraints.nextButton.centerX.adjustedByWidth + + button.snp.remakeConstraints { make in + make.width.height.equalTo(self.smallButtonWidth) + + switch settings.wheelPosition { + case .right: + make.centerX.equalTo(self.view.snp.trailing).offset(-centerX) + case .left: + make.centerX.equalTo(self.view.snp.leading).offset(centerX) + } + if #available(iOS 11, *) { + make.centerY.equalTo(self.view.safeAreaLayoutGuide.snp.bottomMargin).offset(-centerX) + } else { + make.centerY.equalTo(self.view.snp.bottom).offset(-centerX) + } + } } + // MARK: - Keyboard + override func keyboardNotified(endFrame: CGRect) { let centerX = Constraints.nextButton.centerX.adjustedByWidth @@ -861,6 +883,12 @@ class MainViewController: BaseVC, MainViewProtocol, BaseInputViewProtocol, Defau nextButton.isUserInteractionEnabled = true } + // MARK: - UISettingsRespondable + + func userInterfaceSettingsDidChange(_ newSettings: UISettings) { + setupWheelButton(self.nextButton, with: newSettings) + } + //MARK: - Clean Input func cleanTextInput() { self.textInputBar.text = "" diff --git a/Nynja/Modules/Main/View/MainViewControllerNav.swift b/Nynja/Modules/Main/View/MainViewControllerNav.swift index 2bdfffcacbeb0c8732c426b482176fe82b9bdd15..3e771d0c12b6fccb9b522e00bd697da47e74ed58 100644 --- a/Nynja/Modules/Main/View/MainViewControllerNav.swift +++ b/Nynja/Modules/Main/View/MainViewControllerNav.swift @@ -26,28 +26,47 @@ extension MainViewController: NavigateProtocol { return } - var subitems : ItemModels! - var newPosition : Int! + var subitems: ItemModels + let newPosition: Int var items = wheelContainerDS.items[level - 1] - if items[position].isSelected { - subitems = items[position].subitems + let selectedItem = items[position] + + var indexOfItem = indexOfItem + if let index = indexOfItem, selectedItem.isReversed { + indexOfItem = selectedItem.subitems.count - index - 1 + } + + if selectedItem.isSelected { + items.remove(at: position) + subitems = selectedItem.subitems subitems.forEach { $0.state = .normal } items.insert(contentsOf: subitems, at: position) - newPosition = position+subitems.count + if selectedItem.isReversed { + newPosition = position + } else { + newPosition = position + subitems.count + } + items.insert(selectedItem, at: newPosition) + } else { - subitems = items[position].subitems - items.removeSubrange((position-subitems.count).. WheelItemView {// Get level let level = indexPath.level + let levelItems = items[level] // Get model - let itemModel = items[level][indexPath.item] - // Crete Frame + let itemIndex = indexPath.item + let itemModel = levelItems[itemIndex] + + // Create Frame let frame = CGRect(origin: CGPoint.zero, size: configurations[level].sectorSize) // Get item view and update it with model let itemView = itemViewFactory.itemViewForModel(itemModel, frame: frame) itemView.update(model: itemModel) + // Update zPosition order when wheel is transformed for left position. + itemView.layer.zPosition = itemModel.isReversed ? CGFloat(levelItems.count - itemIndex) : CGFloat(itemIndex) + return itemView } diff --git a/Nynja/Modules/Main/View/NavigateProtocol.swift b/Nynja/Modules/Main/View/NavigateProtocol.swift index b3e9db39988e2a28ffac6751c9010db3a3dded38..dcc9e6e14cb952c44f9d642de2faada92eee7189 100644 --- a/Nynja/Modules/Main/View/NavigateProtocol.swift +++ b/Nynja/Modules/Main/View/NavigateProtocol.swift @@ -74,6 +74,9 @@ protocol SecondLevelNavigateProtocol: class { // MARK: - Options + func showWheelPositionPicker(indexPath: IndexPath?) + func showBuildNumber(indexPath: IndexPath?) + func showThemePicker(indexPath: IndexPath?) func logout(indexPath: IndexPath?) func showAbout(indexPath: IndexPath?) func deleteAccount(indexPath: IndexPath?) diff --git a/Nynja/Modules/Main/WireFrame/MainWireframe.swift b/Nynja/Modules/Main/WireFrame/MainWireframe.swift index 496772a804f661586c3feb323bc5166f810376bd..031f166af01ca48121db950bd3eadd276f3cfcbb 100644 --- a/Nynja/Modules/Main/WireFrame/MainWireframe.swift +++ b/Nynja/Modules/Main/WireFrame/MainWireframe.swift @@ -431,6 +431,21 @@ class MainWireFrame: MainWireFrameProtocol, VoxServiceDelegate { } } + func showWheelPositionPicker() { + guard let navigation = contentNavigation else { return } + WheelPositionPickerWireFrame().presentWheelPositionPicker(navigation: navigation) + } + + func showBuildNumber() { + guard let navigation = contentNavigation else { return } + BuildNumberWireFrame().presentBuildNumber(navigation: navigation) + } + + func showThemePicker() { + guard let navigation = contentNavigation else { return } + ThemePickerWireFrame().presentThemePicker(navigation: navigation) + } + // MARK: Edit Profile diff --git a/Nynja/Modules/Settings/BuildNumber/BuildNumberProtocols.swift b/Nynja/Modules/Settings/BuildNumber/BuildNumberProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..5eb9c19a7fee8ca286c56dd1542e8762415ae48b --- /dev/null +++ b/Nynja/Modules/Settings/BuildNumber/BuildNumberProtocols.swift @@ -0,0 +1,56 @@ +// +// BuildNumberProtocols.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol BuildNumberWireFrameProtocol: class { + + func presentBuildNumber(navigation: UINavigationController) + + /** + * Add here your methods for communication PRESENTER -> WIREFRAME + */ +} + +protocol BuildNumberViewProtocol: class { + + var presenter: BuildNumberPresenterProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> VIEW + */ + func setupBuildNumber(_ buildNumber: String) +} + +protocol BuildNumberPresenterProtocol: AnyObject, BasePresenterProtocol { + + var view: BuildNumberViewProtocol! { get set } + var interactor: BuildNumberInteractorInputProtocol! { get set } + var wireFrame: BuildNumberWireFrameProtocol! { get set } + + /** + * Add here your methods for communication VIEW -> PRESENTER + */ + func showed() +} + +protocol BuildNumberInteractorOutputProtocol: class { + + /** + * Add here your methods for communication INTERACTOR -> PRESENTER + */ +} + +protocol BuildNumberInteractorInputProtocol: class { + + var presenter: BuildNumberInteractorOutputProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> INTERACTOR + */ +} diff --git a/Nynja/Modules/Settings/BuildNumber/Interactor/BuildNumberInteractor.swift b/Nynja/Modules/Settings/BuildNumber/Interactor/BuildNumberInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..b5a18e39b1787e9ecd709d7e19e2cfc089bdbe8d --- /dev/null +++ b/Nynja/Modules/Settings/BuildNumber/Interactor/BuildNumberInteractor.swift @@ -0,0 +1,13 @@ +// +// BuildNumberInteractor.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +class BuildNumberInteractor: BuildNumberInteractorInputProtocol { + weak var presenter: BuildNumberInteractorOutputProtocol! +} diff --git a/Nynja/Modules/Settings/BuildNumber/Presenter/BuildNumberPresenter.swift b/Nynja/Modules/Settings/BuildNumber/Presenter/BuildNumberPresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..9177cdd72bac5bab7ed067685513512b018a13b6 --- /dev/null +++ b/Nynja/Modules/Settings/BuildNumber/Presenter/BuildNumberPresenter.swift @@ -0,0 +1,27 @@ +// +// BuildNumberPresenter.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +class BuildNumberPresenter: BasePresenter, BuildNumberPresenterProtocol, BuildNumberInteractorOutputProtocol { + + override var itemsFactory: WCItemsFactory? { + return BuildNumberItemsFactory() + } + + weak var view: BuildNumberViewProtocol! + var interactor: BuildNumberInteractorInputProtocol! + var wireFrame: BuildNumberWireFrameProtocol! + + func showed() { + if let buildNumber = Bundle.main.buildVersion { + let displayString = String(format: "build number version".localized, buildNumber) + view.setupBuildNumber(displayString) + } + } +} diff --git a/Nynja/Modules/Settings/BuildNumber/View/BuildNumberViewController.swift b/Nynja/Modules/Settings/BuildNumber/View/BuildNumberViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..d0d45476a26d395abc61441a66e52d981ecc2edb --- /dev/null +++ b/Nynja/Modules/Settings/BuildNumber/View/BuildNumberViewController.swift @@ -0,0 +1,85 @@ +// +// BuildNumberViewController.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +class BuildNumberViewController: BaseVC, BuildNumberViewProtocol { + + var presenter: BuildNumberPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + // MARK: - Views + + private lazy var logoImageView: UIImageView = { + let imageView = UIImageView() + imageView.image = #imageLiteral(resourceName: "icon") + self.view.addSubview(imageView) + + imageView.snp.makeConstraints { maker in + maker.centerX.equalToSuperview() + maker.centerY.equalToSuperview().offset(-Constraints.logoImageView.centerOffset.adjustedByWidth) + maker.width.equalTo(Constraints.logoImageView.width.adjustedByWidth) + maker.height.equalTo(Constraints.logoImageView.height.adjustedByWidth) + } + return imageView + }() + + private lazy var textLogoImageView: UIImageView = { + let imageView = UIImageView() + imageView.image = #imageLiteral(resourceName: "ic_logo") + self.view.addSubview(imageView) + + imageView.snp.makeConstraints { maker in + maker.centerX.equalToSuperview() + maker.top.equalTo(logoImageView.snp.bottom).offset(Constraints.textLogoImageView.topOffset.adjustedByWidth) + maker.width.equalTo(Constraints.textLogoImageView.width.adjustedByWidth) + maker.height.equalTo(Constraints.textLogoImageView.height.adjustedByWidth) + } + return imageView + }() + + private lazy var buildLabel: UILabel = { + let width = Constraints.buildLabel.width.adjustedByWidth + let height = Constraints.buildLabel.height.adjustedByWidth + + let label = UILabel(size: CGSize(width: width, height: height), + color: Constants.colors.white.getColor(), + fontName: Constants.fonts.medium) + label.numberOfLines = 0 + label.textAlignment = .center + + self.view.addSubview(label) + + label.snp.makeConstraints { maker in + maker.height.equalTo(height) + + let inset = Constraints.buildLabel.horizontalInset.adjustedByWidth + maker.left.equalToSuperview().offset(inset) + maker.right.equalToSuperview().inset(inset) + maker.top.equalTo(textLogoImageView.snp.bottom).offset(Constraints.buildLabel.topOffset.adjustedByWidth) + } + return label + }() + + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + self.screenTitle = "build number title".localized + presenter.showed() + } + + func setupBuildNumber(_ buildNumber: String) { + self.buildLabel.text = buildNumber + } +} diff --git a/Nynja/Modules/Settings/BuildNumber/View/BuildNumberViewControllerLayout.swift b/Nynja/Modules/Settings/BuildNumber/View/BuildNumberViewControllerLayout.swift new file mode 100644 index 0000000000000000000000000000000000000000..04c9fa5ac17c251fe2ce7af85cc7b08d3ef0894c --- /dev/null +++ b/Nynja/Modules/Settings/BuildNumber/View/BuildNumberViewControllerLayout.swift @@ -0,0 +1,32 @@ +// +// BuildNumberViewControllerLayout.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +extension BuildNumberViewController { + + enum Constraints { + enum logoImageView { + static let centerOffset: CGFloat = 97 + static let width: CGFloat = 214 + static let height: CGFloat = 202 + } + enum textLogoImageView { + static let topOffset: CGFloat = 16 + static let width: CGFloat = 273 + static let height: CGFloat = 38 + } + enum buildLabel { + static let topOffset: CGFloat = 64 + static let width: CGFloat = 222 + static let height: CGFloat = 22 + static let horizontalInset: CGFloat = 16 + } + } + +} diff --git a/Nynja/Modules/Settings/BuildNumber/WireFrame/BuildNumberWireFrame.swift b/Nynja/Modules/Settings/BuildNumber/WireFrame/BuildNumberWireFrame.swift new file mode 100644 index 0000000000000000000000000000000000000000..251a1ebf8a010f239bcc209217c335704971bedf --- /dev/null +++ b/Nynja/Modules/Settings/BuildNumber/WireFrame/BuildNumberWireFrame.swift @@ -0,0 +1,31 @@ +// +// BuildNumberWireFrame.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +class BuildNumberWireFrame: BuildNumberWireFrameProtocol { + + weak var navigation : UINavigationController? + + func presentBuildNumber(navigation: UINavigationController) { + let view = BuildNumberViewController() + let presenter = BuildNumberPresenter() + let interactor = BuildNumberInteractor() + + self.navigation = navigation + + // Connecting + view.presenter = presenter + presenter.view = view + presenter.wireFrame = self + presenter.interactor = interactor + interactor.presenter = presenter + navigation.pushViewController(view as UIViewController, animated: false) + + } +} diff --git a/Nynja/Modules/Settings/CarouselPicker/CarouselPickerProtocols.swift b/Nynja/Modules/Settings/CarouselPicker/CarouselPickerProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..dee7f69d0db1b152ba4988451601e7d096b31010 --- /dev/null +++ b/Nynja/Modules/Settings/CarouselPicker/CarouselPickerProtocols.swift @@ -0,0 +1,17 @@ +// +// CarouselPickerProtocols.swift +// Nynja +// +// Created by Anton Poltoratskyi on 01.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol CarouselPickerViewProtocol: class { + func setupItems(_ items: [CarouselPickerCellModel]) +} + +protocol CarouselPickerPresenterProtocol: BasePresenterProtocol { + func didSelectItem(_ item: CarouselPickerCellModel) +} diff --git a/Nynja/Modules/Settings/CarouselPicker/CollectionView/CarouselPickerCellModel.swift b/Nynja/Modules/Settings/CarouselPicker/CollectionView/CarouselPickerCellModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..5409a4818454856843cce9e42b52723a9925aaf1 --- /dev/null +++ b/Nynja/Modules/Settings/CarouselPicker/CollectionView/CarouselPickerCellModel.swift @@ -0,0 +1,19 @@ +// +// CarouselPickerCellModel.swift +// Nynja +// +// Created by Anton Poltoratskyi on 01.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +typealias CarouselPickerCellModel = AnyCellViewModel & SelectableCellModel & TextRepresentable + +protocol SelectableCellModel { + var isSelected: Bool { get } +} + +protocol TextRepresentable { + func title() -> String? +} diff --git a/Nynja/Modules/Settings/CarouselPicker/CollectionView/CarouselPickerCollectionViewCell.swift b/Nynja/Modules/Settings/CarouselPicker/CollectionView/CarouselPickerCollectionViewCell.swift new file mode 100644 index 0000000000000000000000000000000000000000..d50059fb76a4328113887d304b12823a553934a4 --- /dev/null +++ b/Nynja/Modules/Settings/CarouselPicker/CollectionView/CarouselPickerCollectionViewCell.swift @@ -0,0 +1,85 @@ +// +// CarouselPickerCollectionViewCell.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +class CarouselPickerCollectionViewCell: UICollectionViewCell { + + // MARK: - Views + + private(set) lazy var containerView: UIView = { + let containerView = UIView() + self.contentView.addSubview(containerView) + + containerView.layer.shadowOpacity = Constraints.shadow.alpha + containerView.layer.shadowRadius = Constraints.shadow.radius.adjustedByWidth + containerView.layer.shadowOffset = Constraints.shadow.offset.adjustedByWidth + + containerView.clipsToBounds = false + containerView.layer.masksToBounds = false + + containerView.snp.makeConstraints { maker in + maker.top.bottom.centerX.equalToSuperview() + maker.width.equalTo(containerView.snp.height).multipliedBy(Constraints.imageView.aspectRatio) + } + return containerView + }() + + + // MARK: - Init + + override init(frame: CGRect) { + super.init(frame: frame) + baseSetup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + baseSetup() + } + + + // MARK: - UI Setup + + func baseSetup() { + clipsToBounds = false + layer.masksToBounds = false + contentView.clipsToBounds = false + contentView.layer.masksToBounds = false + containerView.isHidden = false + } +} + +// MARK: - Layout +extension CarouselPickerCollectionViewCell { + enum Constraints { + enum imageView { + static let minScale: CGFloat = scaledState.height / normalState.height + static let aspectRatio: CGFloat = normalState.width / normalState.height + + enum normalState { + static let width: CGFloat = 270 + static let height: CGFloat = 480 + } + enum scaledState { + static let height: CGFloat = 355 + } + } + enum itemLabel { + static let topOffset: CGFloat = 21 + static let height: CGFloat = 22 + static let width: CGFloat = 270 + } + enum shadow { + static let offset = CGSize(width: 0, height: 12) + static let radius: CGFloat = 12 + static let alpha: Float = 0.5 + } + } +} diff --git a/Nynja/Modules/Settings/CarouselPicker/ViewController/CarouselPickerViewController.swift b/Nynja/Modules/Settings/CarouselPicker/ViewController/CarouselPickerViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..ff4a934a34f4976d48ab00b1991d9d3bcc9247f2 --- /dev/null +++ b/Nynja/Modules/Settings/CarouselPicker/ViewController/CarouselPickerViewController.swift @@ -0,0 +1,205 @@ +// +// CarouselPickerViewController.swift +// Nynja +// +// Created by Anton Poltoratskyi on 01.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +class CarouselPickerViewController: BaseVC, CarouselPickerViewProtocol, UICollectionViewDataSource, UICollectionViewDelegate { + + var carouselPresenter: CarouselPickerPresenterProtocol! { + didSet { + _presenter = carouselPresenter + } + } + + private(set) var items: [CarouselPickerCellModel] = [] { + didSet { + collectionView.reloadData() + } + } + + var collectionTitle: String? { + didSet { + collectionTitleLabel.text = collectionTitle + } + } + + var selectedItemText: String? { + didSet { + selectedItemLabel.text = selectedItemText + } + } + + private var currentItemIndexPath: IndexPath? { + var point = collectionView.contentOffset + point.x += collectionView.bounds.width / 2 + return collectionView.indexPathForItem(at: point) + } + + private var isAppeared = false + + + // MARK: - Views + + private lazy var collectionTitleLabel: UILabel = { + let width = Constraints.collectionTitleLabel.width.adjustedByWidth + let height = Constraints.collectionTitleLabel.height.adjustedByWidth + + let label = UILabel(size: CGSize(width: width, height: height), + color: Constants.colors.darkGray.getColor(), fontName: Constants.fonts.regular) + + label.textAlignment = .center + + self.view.addSubview(label) + + label.snp.makeConstraints { maker in + let horizontalInset = Constraints.collectionTitleLabel.horizontalInset.adjustedByWidth + maker.left.equalToSuperview().offset(horizontalInset) + maker.right.equalToSuperview().inset(horizontalInset) + + let topOffset = Constraints.collectionTitleLabel.topOffset.adjustedByWidth + maker.top.equalTo(self.navigationView.snp.bottom).offset(topOffset) + + maker.height.equalTo(Constraints.collectionTitleLabel.height.adjustedByWidth) + } + + return label + }() + + private(set) lazy var carouselLayout: CarouselFlowLayout = { + let layout = CarouselFlowLayout() + layout.spacingMode = .fixed(spacing: Constraints.collectionView.items.padding.adjustedByWidth) + layout.sideItemScale = CarouselPickerCollectionViewCell.Constraints.imageView.minScale + layout.sideItemAlpha = 1.0 + layout.scrollDirection = .horizontal + + return layout + }() + + private(set) lazy var collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: self.carouselLayout) + collectionView.backgroundColor = .clear + collectionView.showsHorizontalScrollIndicator = false + collectionView.clipsToBounds = false + + self.view.addSubview(collectionView) + + collectionView.snp.makeConstraints { maker in + maker.left.right.equalToSuperview() + maker.top.equalTo(collectionTitleLabel.snp.bottom).offset(Constraints.collectionView.topOffset.adjustedByWidth) + maker.bottom.equalToSuperview().inset(Constraints.collectionView.bottomOffset.adjustedByWidth) + } + + return collectionView + }() + + private(set) lazy var selectedItemLabel: UILabel = { + let width = Constraints.selectedItemLabel.width.adjustedByWidth + let height = Constraints.selectedItemLabel.height.adjustedByWidth + + let label = UILabel(size: CGSize(width: width, height: height), color: Constants.colors.white.getColor(), fontName: Constants.fonts.medium) + label.textAlignment = .center + + self.view.addSubview(label) + + label.snp.makeConstraints { maker in + maker.left.right.equalToSuperview() + maker.top.equalTo(collectionView.snp.bottom).offset(Constraints.selectedItemLabel.topOffset.adjustedByWidth) + maker.height.equalTo(Constraints.selectedItemLabel.topOffset.adjustedByWidth) + } + return label + }() + + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + guard !isAppeared else { return } + + carouselLayout.itemSize = CGSize( + width: collectionView.bounds.width - Constraints.collectionView.items.horizontalInset.adjustedByWidth * 2, + height: collectionView.bounds.height + ) + showSelectedPosition() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + isAppeared = true + } + + + // MARK: - UI Setup + + func setupUI() { + collectionView.delegate = self + collectionView.dataSource = self + // register cell in subclasses + } + + + // MARK: - CarouselPickerViewProtocol + + func setupItems(_ items: [CarouselPickerCellModel]) { + self.items = items + showSelectedPosition() + } + + private func showSelectedPosition() { + guard let index = items.index(where: { $0.isSelected }) else { return } + scrollToModel(at: index, animated: false) + } + + private func scrollToModel(at index: Int, animated: Bool) { + let item = items[index] + selectedItemText = item.title() + + let indexPath = IndexPath(item: index, section: 0) + collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: animated) + } + + + // MARK: - UICollectionViewDataSource + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return items.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + return collectionView.dequeueReusableCell(withModel: itemModel(at: indexPath), for: indexPath) + } + + private func itemModel(at indexPath: IndexPath) -> CarouselPickerCellModel { + return items[indexPath.row] + } + + + // MARK: - UICollectionViewDelegate + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard indexPath == self.currentItemIndexPath else { return } + + let selectedItem = itemModel(at: indexPath) + carouselPresenter.didSelectItem(selectedItem) + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + guard let indexPath = currentItemIndexPath else { + return + } + let itemModel = self.itemModel(at: indexPath) + selectedItemText = itemModel.title() + } +} diff --git a/Nynja/Modules/Settings/CarouselPicker/ViewController/CarouselPickerViewControllerLayout.swift b/Nynja/Modules/Settings/CarouselPicker/ViewController/CarouselPickerViewControllerLayout.swift new file mode 100644 index 0000000000000000000000000000000000000000..a5590cd3e931b5a45252806b98cbe6a48d10fb3c --- /dev/null +++ b/Nynja/Modules/Settings/CarouselPicker/ViewController/CarouselPickerViewControllerLayout.swift @@ -0,0 +1,39 @@ +// +// CarouselPickerViewControllerLayout.swift +// Nynja +// +// Created by Anton Poltoratskyi on 01.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +extension CarouselPickerViewController { + + enum Constraints { + + enum collectionTitleLabel { + static let topOffset: CGFloat = 20 + static let height: CGFloat = 20 + static let width: CGFloat = 270 + static let horizontalInset: CGFloat = 16 + } + + enum collectionView { + static let topOffset: CGFloat = 20 + static let bottomOffset: CGFloat = 128 + + enum items { + static let padding: CGFloat = 24 + static let horizontalInset: CGFloat = 72 + } + } + + enum selectedItemLabel { + static let horizontalInset: CGFloat = 16 + static let topOffset: CGFloat = 21 + static let height: CGFloat = 22 + static let width: CGFloat = 270 + } + } +} diff --git a/Nynja/Modules/Settings/ThemePicker/Interactor/ThemePickerInteractor.swift b/Nynja/Modules/Settings/ThemePicker/Interactor/ThemePickerInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..992c98139c05f41ba0ec4d8d09e07c2089339fb2 --- /dev/null +++ b/Nynja/Modules/Settings/ThemePicker/Interactor/ThemePickerInteractor.swift @@ -0,0 +1,24 @@ +// +// ThemePickerInteractor.swift +// Nynja +// +// Created by Anton Poltoratskyi on 01.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class ThemePickerInteractor: ThemePickerInteractorInputProtocol { + + weak var presenter: ThemePickerInteractorOutputProtocol! + + func getCurrentTheme() -> Theme { + return UISettingsService.shared.theme + } + + func setTheme(_ theme: Theme) { + UISettingsService.shared.theme = theme + presenter.didUpdateTheme(theme) + } +} + diff --git a/Nynja/Modules/Settings/ThemePicker/Presenter/ThemePickerPresenter.swift b/Nynja/Modules/Settings/ThemePicker/Presenter/ThemePickerPresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..ddab646c02854b52a73c199e062032fe6d3d78de --- /dev/null +++ b/Nynja/Modules/Settings/ThemePicker/Presenter/ThemePickerPresenter.swift @@ -0,0 +1,68 @@ +// +// ThemePickerPresenter.swift +// Nynja +// +// Created by Anton Poltoratskyi on 01.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class ThemePickerPresenter: BasePresenter, ThemePickerPresenterProtocol, ThemePickerInteractorOutputProtocol { + + override var itemsFactory: WCItemsFactory? { + return nil + } + + weak var view: ThemePickerViewProtocol! + var interactor: ThemePickerInteractorInputProtocol! + var wireFrame: ThemePickerWireFrameProtocol! + + + // MARK: - ThemePickerPresenterProtocol + + func showed() { + let currentTheme = interactor.getCurrentTheme() + setupView(for: currentTheme) + } + + func didSelectItem(_ itemModel: CarouselPickerCellModel) { + guard !itemModel.isSelected else { return } + + guard let itemModel = itemModel as? ThemeCellModel else { + return + } + + AlertManager.sharedInstance.showAlertWithTwoActions( + title: "theme picker alert title".localized, + message: "", + firstActionTitle: "yes".localized, + secondActionTitle: "no".localized, + firstAction: { + self.applyTheme(itemModel.theme) + }, + secondAction: nil + ) + } + + + // MARK: - ThemePickerInteractorOutputProtocol + + func didUpdateTheme(_ theme: Theme) { + setupView(for: theme) + } + + + // MARK: - Private + + private func setupView(for currentTheme: Theme) { + let items = Theme.all.map { + ThemeCellModel(theme: $0, isSelected: $0 == currentTheme) + } + view.setupItems(items) + } + + private func applyTheme(_ theme: Theme) { + interactor.setTheme(theme) + } +} diff --git a/Nynja/Modules/Settings/ThemePicker/ThemePickerProtocols.swift b/Nynja/Modules/Settings/ThemePicker/ThemePickerProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..b3f847a6e817d6e9dd1f6c59320270011b4c6198 --- /dev/null +++ b/Nynja/Modules/Settings/ThemePicker/ThemePickerProtocols.swift @@ -0,0 +1,60 @@ +// +// ThemePickerProtocols.swift +// Nynja +// +// Created by Anton Poltoratskyi on 01.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol ThemePickerWireFrameProtocol: class { + + func presentThemePicker(navigation: UINavigationController) + + /** + * Add here your methods for communication PRESENTER -> WIREFRAME + */ +} + +protocol ThemePickerViewProtocol: CarouselPickerViewProtocol { + + var presenter: ThemePickerPresenterProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> VIEW + */ + +} + +protocol ThemePickerPresenterProtocol: CarouselPickerPresenterProtocol { + + var view: ThemePickerViewProtocol! { get set } + var interactor: ThemePickerInteractorInputProtocol! { get set } + var wireFrame: ThemePickerWireFrameProtocol! { get set } + + /** + * Add here your methods for communication VIEW -> PRESENTER + */ + + func showed() +} + +protocol ThemePickerInteractorOutputProtocol: class { + + /** + * Add here your methods for communication INTERACTOR -> PRESENTER + */ + func didUpdateTheme(_ theme: Theme) +} + +protocol ThemePickerInteractorInputProtocol: class { + + var presenter: ThemePickerInteractorOutputProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> INTERACTOR + */ + func getCurrentTheme() -> Theme + func setTheme(_ theme: Theme) +} diff --git a/Nynja/Modules/Settings/ThemePicker/View/CollectionView/ThemeCellModel.swift b/Nynja/Modules/Settings/ThemePicker/View/CollectionView/ThemeCellModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..f30e4bfb91695d703cec2aa9ab5ac2cb70eebda1 --- /dev/null +++ b/Nynja/Modules/Settings/ThemePicker/View/CollectionView/ThemeCellModel.swift @@ -0,0 +1,28 @@ +// +// ThemeCellModel.swift +// Nynja +// +// Created by Anton Poltoratskyi on 01.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +struct ThemeCellModel: CellViewModel, SelectableCellModel, TextRepresentable { + + let theme: Theme + var isSelected: Bool + + func setup(cell: ThemeCollectionViewCell) { + cell.backgroundImageView.image = UIImage(named: theme.backgroundName) + } + + func title() -> String? { + if isSelected { + return "\(theme.name) (\("theme current name".localized))" + } else { + return theme.name + } + } + +} diff --git a/Nynja/Modules/Settings/ThemePicker/View/CollectionView/ThemeCollectionViewCell.swift b/Nynja/Modules/Settings/ThemePicker/View/CollectionView/ThemeCollectionViewCell.swift new file mode 100644 index 0000000000000000000000000000000000000000..56335f060e1926ed4615c5554b9e76c69eeb258d --- /dev/null +++ b/Nynja/Modules/Settings/ThemePicker/View/CollectionView/ThemeCollectionViewCell.swift @@ -0,0 +1,117 @@ +// +// ThemeCollectionViewCell.swift +// Nynja +// +// Created by Anton Poltoratskyi on 02.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class ThemeCollectionViewCell: CarouselPickerCollectionViewCell { + + // MARK: - Views + + private(set) lazy var backgroundImageView: UIImageView = { + let imageView = UIImageView() + self.containerView.addSubview(imageView) + + imageView.snp.makeConstraints { maker in + maker.edges.equalToSuperview() + } + + return imageView + }() + + private lazy var logoImageView: UIImageView = { + let imageView = UIImageView() + self.containerView.addSubview(imageView) + + imageView.image = #imageLiteral(resourceName: "icon") + + imageView.snp.makeConstraints { maker in + maker.width.equalTo(ThemeConstraints.imageLogo.width.adjustedByWidth) + maker.height.equalTo(ThemeConstraints.imageLogo.height.adjustedByWidth) + maker.centerX.equalToSuperview() + maker.centerY.equalToSuperview().offset(-ThemeConstraints.imageLogo.centerOffset.adjustedByWidth) + } + + return imageView + }() + + private lazy var textLogoImageView: UIImageView = { + let imageView = UIImageView() + self.containerView.addSubview(imageView) + + imageView.image = #imageLiteral(resourceName: "ic_logo") + + imageView.snp.makeConstraints { maker in + maker.width.equalTo(ThemeConstraints.textLogo.width.adjustedByWidth) + maker.height.equalTo(ThemeConstraints.textLogo.height.adjustedByWidth) + maker.centerX.equalToSuperview() + maker.centerY.equalToSuperview().offset(ThemeConstraints.textLogo.centerOffset.adjustedByWidth) + } + + return imageView + }() + + private lazy var subtitleLabel: UILabel = { + let width = ThemeConstraints.subtitleLabel.width.adjustedByWidth + let height = ThemeConstraints.subtitleLabel.height.adjustedByWidth + + let label = UILabel(size: CGSize(width: width, height: height), + color: Constants.colors.white.getColor(), + fontName: Constants.fonts.medium) + + label.text = "theme cell subtitle text".localized + label.textColor = Constants.colors.subtitleColor.getColor() + label.textAlignment = .center + + self.containerView.addSubview(label) + + label.snp.makeConstraints { maker in + maker.left.right.equalTo(textLogoImageView) + maker.top.equalTo(textLogoImageView.snp.bottom).offset(ThemeConstraints.subtitleLabel.topOffset.adjustedByWidth) + maker.height.equalTo(height) + } + + return label + }() + + + // MARK: - UI Setup + + override func baseSetup() { + super.baseSetup() + backgroundImageView.isHidden = false + logoImageView.isHidden = false + textLogoImageView.isHidden = false + subtitleLabel.isHidden = false + } +} + +// MARK: - Layout + +extension ThemeCollectionViewCell { + + enum ThemeConstraints { + enum imageLogo { + static let width: CGFloat = 140 + static let height: CGFloat = 132 + static let centerOffset: CGFloat = 86 + } + + enum textLogo { + static let width: CGFloat = 176 + static let height: CGFloat = 24 + static let centerOffset: CGFloat = 76 + } + + enum subtitleLabel { + static let width: CGFloat = 192 + static let height: CGFloat = 14 + static let topOffset: CGFloat = 4 + } + } +} diff --git a/Nynja/Modules/Settings/ThemePicker/View/ViewController/ThemePickerViewController.swift b/Nynja/Modules/Settings/ThemePicker/View/ViewController/ThemePickerViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..b284a8b3dda8e0b3b9583b55719d662d5e98c801 --- /dev/null +++ b/Nynja/Modules/Settings/ThemePicker/View/ViewController/ThemePickerViewController.swift @@ -0,0 +1,39 @@ +// +// ThemePickerViewController.swift +// Nynja +// +// Created by Anton Poltoratskyi on 01.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class ThemePickerViewController: CarouselPickerViewController, ThemePickerViewProtocol { + + var presenter: ThemePickerPresenterProtocol! { + didSet { + carouselPresenter = presenter + } + } + + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + presenter.showed() + } + + + // MARK: - UI Setup + + override func setupUI() { + super.setupUI() + screenTitle = "theme picker title".localized + collectionTitle = "choose theme title".localized + + // Important to register custom cell model + collectionView.register(viewModel: ThemeCellModel.self) + } +} diff --git a/Nynja/Modules/Settings/ThemePicker/WireFrame/ThemePickerWireFrame.swift b/Nynja/Modules/Settings/ThemePicker/WireFrame/ThemePickerWireFrame.swift new file mode 100644 index 0000000000000000000000000000000000000000..a6c3eb765c954b525c80d68f26026c9ff71400b4 --- /dev/null +++ b/Nynja/Modules/Settings/ThemePicker/WireFrame/ThemePickerWireFrame.swift @@ -0,0 +1,32 @@ +// +// ThemePickerWireFrame.swift +// Nynja +// +// Created by Anton Poltoratskyi on 01.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class ThemePickerWireFrame: ThemePickerWireFrameProtocol { + + weak var navigation: UINavigationController? + + func presentThemePicker(navigation: UINavigationController) { + self.navigation = navigation + + let view = ThemePickerViewController() + let presenter = ThemePickerPresenter() + let interactor = ThemePickerInteractor() + + // Connecting + view.presenter = presenter + presenter.view = view + presenter.wireFrame = self + presenter.interactor = interactor + interactor.presenter = presenter + + navigation.pushViewController(view, animated: true) + } + +} diff --git a/Nynja/Modules/Settings/WheelPositionPicker/Interactor/WheelPositionPickerInteractor.swift b/Nynja/Modules/Settings/WheelPositionPicker/Interactor/WheelPositionPickerInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..416a34247b4affd5391ae96222580b9926a10c48 --- /dev/null +++ b/Nynja/Modules/Settings/WheelPositionPicker/Interactor/WheelPositionPickerInteractor.swift @@ -0,0 +1,22 @@ +// +// WheelPositionPickerInteractor.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class WheelPositionPickerInteractor: WheelPositionPickerInteractorInputProtocol { + weak var presenter: WheelPositionPickerInteractorOutputProtocol! + + func getCurrentWheelPosition() -> WheelPosition { + return UISettingsService.shared.wheelPosition + } + + func setWheelPosition(_ wheelPosition: WheelPosition) { + UISettingsService.shared.wheelPosition = wheelPosition + presenter.didUpdateWheelPosition(wheelPosition) + } +} diff --git a/Nynja/Modules/Settings/WheelPositionPicker/Presenter/WheelPositionPickerPresenter.swift b/Nynja/Modules/Settings/WheelPositionPicker/Presenter/WheelPositionPickerPresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..09d1b32bc347f34efcbe6e478350ceb149b6cd37 --- /dev/null +++ b/Nynja/Modules/Settings/WheelPositionPicker/Presenter/WheelPositionPickerPresenter.swift @@ -0,0 +1,69 @@ +// +// WheelPositionPickerPresenter.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class WheelPositionPickerPresenter: BasePresenter, WheelPositionPickerPresenterProtocol, WheelPositionPickerInteractorOutputProtocol { + + + override var itemsFactory: WCItemsFactory? { + return WheelPositionItemsFactory() + } + + weak var view: WheelPositionPickerViewProtocol! + var interactor: WheelPositionPickerInteractorInputProtocol! + var wireFrame: WheelPositionPickerWireFrameProtocol! + + + // MARK: - WheelPositionPickerPresenterProtocol + + func showed() { + let currentPosition = interactor.getCurrentWheelPosition() + setupView(for: currentPosition) + } + + func didSelectItem(_ itemPositionModel: CarouselPickerCellModel) { + guard !itemPositionModel.isSelected else { return } + + guard let itemPositionModel = itemPositionModel as? WheelPositionCellModel else { + return + } + + AlertManager.sharedInstance.showAlertWithTwoActions( + title: "wheel position picker alert title".localized, + message: "", + firstActionTitle: "yes".localized, + secondActionTitle: "no".localized, + firstAction: { + self.apply(wheelPosition: itemPositionModel.position) + }, + secondAction: nil + ) + } + + + // MARK: - WheelPositionPickerInteractorOutputProtocol + + func didUpdateWheelPosition(_ wheelPosition: WheelPosition) { + setupView(for: wheelPosition) + } + + + // MARK: - Private + + private func setupView(for currentPosition: WheelPosition) { + let items = WheelPosition.all.map { + WheelPositionCellModel(position: $0, isSelected: $0 == currentPosition) + } + view.setupItems(items) + } + + private func apply(wheelPosition: WheelPosition) { + interactor.setWheelPosition(wheelPosition) + } +} diff --git a/Nynja/Modules/Settings/WheelPositionPicker/View/CollectionView/WheelPositionCellModel.swift b/Nynja/Modules/Settings/WheelPositionPicker/View/CollectionView/WheelPositionCellModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..b01bf11930f4348ae8a6160cba14b2b2ad930530 --- /dev/null +++ b/Nynja/Modules/Settings/WheelPositionPicker/View/CollectionView/WheelPositionCellModel.swift @@ -0,0 +1,41 @@ +// +// WheelPositionCellModel.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +struct WheelPositionCellModel: CellViewModel, SelectableCellModel, TextRepresentable { + + let position: WheelPosition + var isSelected: Bool + + func setup(cell: WheelPositionCollectionViewCell) { + switch position { + case .left: + cell.imageView.image = UIImage(named: "wheel_left_image") + case .right: + cell.imageView.image = UIImage(named: "wheel_right_image") + } + } + + func title() -> String? { + switch position { + case .left: + if isSelected { + return "\("wheel left hand".localized) (\("wheel current position".localized))" + } else { + return "wheel left hand".localized + } + case .right: + if isSelected { + return "\("wheel right hand".localized) (\("wheel current position".localized))" + } else { + return "wheel right hand".localized + } + } + } +} diff --git a/Nynja/Modules/Settings/WheelPositionPicker/View/CollectionView/WheelPositionCollectionViewCell.swift b/Nynja/Modules/Settings/WheelPositionPicker/View/CollectionView/WheelPositionCollectionViewCell.swift new file mode 100644 index 0000000000000000000000000000000000000000..c4dd9f8137837d7ad6595e3cfa8ca40ada4ed9e8 --- /dev/null +++ b/Nynja/Modules/Settings/WheelPositionPicker/View/CollectionView/WheelPositionCollectionViewCell.swift @@ -0,0 +1,33 @@ +// +// WheelPositionCollectionViewCell.swift +// Nynja +// +// Created by Anton Poltoratskyi on 02.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class WheelPositionCollectionViewCell: CarouselPickerCollectionViewCell { + + // MARK: - Views + + private(set) lazy var imageView: UIImageView = { + let imageView = UIImageView() + self.containerView.addSubview(imageView) + + imageView.snp.makeConstraints { maker in + maker.edges.equalToSuperview() + } + + return imageView + }() + + // MARK: - UI Setup + + override func baseSetup() { + super.baseSetup() + imageView.isHidden = false + } +} diff --git a/Nynja/Modules/Settings/WheelPositionPicker/View/ViewController/WheelPositionPickerViewController.swift b/Nynja/Modules/Settings/WheelPositionPicker/View/ViewController/WheelPositionPickerViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..d963083c2a625f9c44f5bb2d5d6622c4e1434ad6 --- /dev/null +++ b/Nynja/Modules/Settings/WheelPositionPicker/View/ViewController/WheelPositionPickerViewController.swift @@ -0,0 +1,36 @@ +// +// WheelPositionPickerViewController.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class WheelPositionPickerViewController: CarouselPickerViewController, WheelPositionPickerViewProtocol { + + var presenter: WheelPositionPickerPresenterProtocol! { + didSet { + carouselPresenter = presenter + } + } + + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + presenter.showed() + } + + override func setupUI() { + super.setupUI() + screenTitle = "wheel position title".localized + collectionTitle = "choose wheel position".localized + + // Important to register custom cell model + collectionView.register(viewModel: WheelPositionCellModel.self) + } +} diff --git a/Nynja/Modules/Settings/WheelPositionPicker/WheelPositionPickerProtocols.swift b/Nynja/Modules/Settings/WheelPositionPicker/WheelPositionPickerProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..25f3566172191947d03e0e6fce0e62257d02b467 --- /dev/null +++ b/Nynja/Modules/Settings/WheelPositionPicker/WheelPositionPickerProtocols.swift @@ -0,0 +1,60 @@ +// +// WheelPositionPickerProtocols.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol WheelPositionPickerWireFrameProtocol: class { + + func presentWheelPositionPicker(navigation: UINavigationController) + + /** + * Add here your methods for communication PRESENTER -> WIREFRAME + */ +} + +protocol WheelPositionPickerViewProtocol: CarouselPickerViewProtocol { + + var presenter: WheelPositionPickerPresenterProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> VIEW + */ + +} + +protocol WheelPositionPickerPresenterProtocol: class, CarouselPickerPresenterProtocol { + + var view: WheelPositionPickerViewProtocol! { get set } + var interactor: WheelPositionPickerInteractorInputProtocol! { get set } + var wireFrame: WheelPositionPickerWireFrameProtocol! { get set } + + /** + * Add here your methods for communication VIEW -> PRESENTER + */ + + func showed() +} + +protocol WheelPositionPickerInteractorOutputProtocol: class { + + /** + * Add here your methods for communication INTERACTOR -> PRESENTER + */ + func didUpdateWheelPosition(_ wheelPosition: WheelPosition) +} + +protocol WheelPositionPickerInteractorInputProtocol: class { + + var presenter: WheelPositionPickerInteractorOutputProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> INTERACTOR + */ + func getCurrentWheelPosition() -> WheelPosition + func setWheelPosition(_ wheelPosition: WheelPosition) +} diff --git a/Nynja/Modules/Settings/WheelPositionPicker/WireFrame/WheelPositionPickerWireFrame.swift b/Nynja/Modules/Settings/WheelPositionPicker/WireFrame/WheelPositionPickerWireFrame.swift new file mode 100644 index 0000000000000000000000000000000000000000..815d305014545025c7f09c4664a0969ffe9e8ef9 --- /dev/null +++ b/Nynja/Modules/Settings/WheelPositionPicker/WireFrame/WheelPositionPickerWireFrame.swift @@ -0,0 +1,32 @@ +// +// WheelPositionPickerWireFrame.swift +// Nynja +// +// Created by Anton Poltoratskyi on 26.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class WheelPositionPickerWireFrame: WheelPositionPickerWireFrameProtocol { + + weak var navigation: UINavigationController? + + func presentWheelPositionPicker(navigation: UINavigationController) { + self.navigation = navigation + + let view = WheelPositionPickerViewController() + let presenter = WheelPositionPickerPresenter() + let interactor = WheelPositionPickerInteractor() + + // Connecting + view.presenter = presenter + presenter.view = view + presenter.wireFrame = self + presenter.interactor = interactor + interactor.presenter = presenter + + navigation.pushViewController(view, animated: true) + } + +} diff --git a/Nynja/Modules/tutorial/View/TutorialViewController.swift b/Nynja/Modules/tutorial/View/TutorialViewController.swift index b92530a1d7a9a70ce1b49303a30bae3a04ccaf50..68076a5053e17d6630d4c29e65143dc85223457b 100644 --- a/Nynja/Modules/tutorial/View/TutorialViewController.swift +++ b/Nynja/Modules/tutorial/View/TutorialViewController.swift @@ -86,7 +86,7 @@ class TutorialViewController: BaseVC, TutorialViewProtocol, UICollectionViewDele lazy var descLabel: UILabel = { let lbl = UILabel() - lbl.textColor = Color(hex: "#696a6b").getColor() + lbl.textColor = Constants.colors.subtitleColor.getColor() lbl.numberOfLines = 1 let width = Constraints.descLabel.width.adjustedByWidth diff --git a/Nynja/OptionsItemsFactory.swift b/Nynja/OptionsItemsFactory.swift index bd8ba79048fdfadcee57a22ce5c5c30f6a394cbf..0fa01c956a5836fbd65af44d6a13b1ec9ceb076f 100644 --- a/Nynja/OptionsItemsFactory.swift +++ b/Nynja/OptionsItemsFactory.swift @@ -20,12 +20,33 @@ class OptionsItemsFactory: WCBaseItemsFactory { // MARK: - Second lvl override var secondLevelItems: ItemModels { - return [logout, about, deleteAccount] + return [wheelPosition, buildNumber, theme, logout, about, deleteAccount] } // MARK: - Items + var wheelPosition: ImageActionItemModel { + let item = ImageActionItemModel(navItem: .wheelPosition, action: { [weak navigateDelegate] (item, indexPath) in + navigateDelegate?.showWheelPositionPicker(indexPath: indexPath) + }) + return item + } + + var buildNumber: ImageActionItemModel { + let item = ImageActionItemModel(navItem: .buildNumber, action: { [weak navigateDelegate] (item, indexPath) in + navigateDelegate?.showBuildNumber(indexPath: indexPath) + }) + return item + } + + var theme: ImageActionItemModel { + let item = ImageActionItemModel(navItem: .theme, action: { [weak navigateDelegate] (item, indexPath) in + navigateDelegate?.showThemePicker(indexPath: indexPath) + }) + return item + } + var about: ImageActionItemModel { let item = ImageActionItemModel(navItem: .about, action: { [weak navigateDelegate] (item, indexPath) in navigateDelegate?.showAbout(indexPath: indexPath) diff --git a/Nynja/Resources/Assets.xcassets/Logo/Contents.json b/Nynja/Resources/Assets.xcassets/Logo/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..da4a164c918651cdd1e11dca5cc62c333f097601 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Logo/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/auth_light_logo.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Logo/auth_light_logo.imageset/Contents.json similarity index 100% rename from Nynja/Resources/Assets.xcassets/auth_light_logo.imageset/Contents.json rename to Nynja/Resources/Assets.xcassets/Logo/auth_light_logo.imageset/Contents.json diff --git a/Nynja/Resources/Assets.xcassets/auth_light_logo.imageset/auth_light_logo@2x.png b/Nynja/Resources/Assets.xcassets/Logo/auth_light_logo.imageset/auth_light_logo@2x.png similarity index 100% rename from Nynja/Resources/Assets.xcassets/auth_light_logo.imageset/auth_light_logo@2x.png rename to Nynja/Resources/Assets.xcassets/Logo/auth_light_logo.imageset/auth_light_logo@2x.png diff --git a/Nynja/Resources/Assets.xcassets/auth_light_logo.imageset/auth_light_logo@3x.png b/Nynja/Resources/Assets.xcassets/Logo/auth_light_logo.imageset/auth_light_logo@3x.png similarity index 100% rename from Nynja/Resources/Assets.xcassets/auth_light_logo.imageset/auth_light_logo@3x.png rename to Nynja/Resources/Assets.xcassets/Logo/auth_light_logo.imageset/auth_light_logo@3x.png diff --git a/Nynja/Resources/Assets.xcassets/dark_logo.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Logo/dark_logo.imageset/Contents.json similarity index 100% rename from Nynja/Resources/Assets.xcassets/dark_logo.imageset/Contents.json rename to Nynja/Resources/Assets.xcassets/Logo/dark_logo.imageset/Contents.json diff --git a/Nynja/Resources/Assets.xcassets/dark_logo.imageset/dark_logo@2x.png b/Nynja/Resources/Assets.xcassets/Logo/dark_logo.imageset/dark_logo@2x.png similarity index 100% rename from Nynja/Resources/Assets.xcassets/dark_logo.imageset/dark_logo@2x.png rename to Nynja/Resources/Assets.xcassets/Logo/dark_logo.imageset/dark_logo@2x.png diff --git a/Nynja/Resources/Assets.xcassets/dark_logo.imageset/dark_logo@3x.png b/Nynja/Resources/Assets.xcassets/Logo/dark_logo.imageset/dark_logo@3x.png similarity index 100% rename from Nynja/Resources/Assets.xcassets/dark_logo.imageset/dark_logo@3x.png rename to Nynja/Resources/Assets.xcassets/Logo/dark_logo.imageset/dark_logo@3x.png diff --git a/Nynja/Resources/Assets.xcassets/Logo/ic_logo.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Logo/ic_logo.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..f81faf1890e9dc827904c3e91bd03c27a7b9c301 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Logo/ic_logo.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_logo.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Logo/ic_logo.imageset/ic_logo.pdf b/Nynja/Resources/Assets.xcassets/Logo/ic_logo.imageset/ic_logo.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7456f3e41f0146a0ac71814910b62891dac0dea7 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Logo/ic_logo.imageset/ic_logo.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/light_logo.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Logo/light_logo.imageset/Contents.json similarity index 100% rename from Nynja/Resources/Assets.xcassets/light_logo.imageset/Contents.json rename to Nynja/Resources/Assets.xcassets/Logo/light_logo.imageset/Contents.json diff --git a/Nynja/Resources/Assets.xcassets/light_logo.imageset/light_logo@2x.png b/Nynja/Resources/Assets.xcassets/Logo/light_logo.imageset/light_logo@2x.png similarity index 100% rename from Nynja/Resources/Assets.xcassets/light_logo.imageset/light_logo@2x.png rename to Nynja/Resources/Assets.xcassets/Logo/light_logo.imageset/light_logo@2x.png diff --git a/Nynja/Resources/Assets.xcassets/light_logo.imageset/light_logo@3x.png b/Nynja/Resources/Assets.xcassets/Logo/light_logo.imageset/light_logo@3x.png similarity index 100% rename from Nynja/Resources/Assets.xcassets/light_logo.imageset/light_logo@3x.png rename to Nynja/Resources/Assets.xcassets/Logo/light_logo.imageset/light_logo@3x.png diff --git a/Nynja/Resources/Assets.xcassets/New Folder/logo-1.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Logo/logo-1.imageset/Contents.json similarity index 100% rename from Nynja/Resources/Assets.xcassets/New Folder/logo-1.imageset/Contents.json rename to Nynja/Resources/Assets.xcassets/Logo/logo-1.imageset/Contents.json diff --git a/Nynja/Resources/Assets.xcassets/New Folder/logo-1.imageset/logo.png b/Nynja/Resources/Assets.xcassets/Logo/logo-1.imageset/logo.png similarity index 100% rename from Nynja/Resources/Assets.xcassets/New Folder/logo-1.imageset/logo.png rename to Nynja/Resources/Assets.xcassets/Logo/logo-1.imageset/logo.png diff --git a/Nynja/Resources/Assets.xcassets/New Folder/logo-1.imageset/logo@2x.png b/Nynja/Resources/Assets.xcassets/Logo/logo-1.imageset/logo@2x.png similarity index 100% rename from Nynja/Resources/Assets.xcassets/New Folder/logo-1.imageset/logo@2x.png rename to Nynja/Resources/Assets.xcassets/Logo/logo-1.imageset/logo@2x.png diff --git a/Nynja/Resources/Assets.xcassets/New Folder/logo-1.imageset/logo@3x.png b/Nynja/Resources/Assets.xcassets/Logo/logo-1.imageset/logo@3x.png similarity index 100% rename from Nynja/Resources/Assets.xcassets/New Folder/logo-1.imageset/logo@3x.png rename to Nynja/Resources/Assets.xcassets/Logo/logo-1.imageset/logo@3x.png diff --git a/Nynja/Resources/Assets.xcassets/logo.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Logo/logo.imageset/Contents.json similarity index 100% rename from Nynja/Resources/Assets.xcassets/logo.imageset/Contents.json rename to Nynja/Resources/Assets.xcassets/Logo/logo.imageset/Contents.json diff --git a/Nynja/Resources/Assets.xcassets/logo.imageset/logo@2x.png b/Nynja/Resources/Assets.xcassets/Logo/logo.imageset/logo@2x.png similarity index 100% rename from Nynja/Resources/Assets.xcassets/logo.imageset/logo@2x.png rename to Nynja/Resources/Assets.xcassets/Logo/logo.imageset/logo@2x.png diff --git a/Nynja/Resources/Assets.xcassets/logo.imageset/logo@3x.png b/Nynja/Resources/Assets.xcassets/Logo/logo.imageset/logo@3x.png similarity index 100% rename from Nynja/Resources/Assets.xcassets/logo.imageset/logo@3x.png rename to Nynja/Resources/Assets.xcassets/Logo/logo.imageset/logo@3x.png diff --git a/Nynja/Resources/Assets.xcassets/WheelPosition/Contents.json b/Nynja/Resources/Assets.xcassets/WheelPosition/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..da4a164c918651cdd1e11dca5cc62c333f097601 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/WheelPosition/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_left_image.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_left_image.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..bab7f7d16c0419003b4fba7627c67b3b587c5a66 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_left_image.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "left_image.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_left_image.imageset/left_image.pdf b/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_left_image.imageset/left_image.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a21b5c9f751612124205c9b533bb03172339cabe Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_left_image.imageset/left_image.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_right_image.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_right_image.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..3c7f1cbf179166e031b0b0647596c78be9bb6300 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_right_image.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "right_image.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_right_image.imageset/right_image.pdf b/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_right_image.imageset/right_image.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bf18d530ee760b9e9df2cc3b63238e98fc1a2391 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_right_image.imageset/right_image.pdf differ diff --git a/Nynja/Resources/Constants.swift b/Nynja/Resources/Constants.swift index f30ccfbc9a45b4afcad4b26eaa3b0ecec11a7c2f..6ed169b6ddc43da6939068fa1558f3d57ea4971d 100644 --- a/Nynja/Resources/Constants.swift +++ b/Nynja/Resources/Constants.swift @@ -72,6 +72,7 @@ struct Constants { static let greenForReturnToCallColor = Color(hex:"#00E359") static let blackTranslucent = Color(hex:"#272a30", alpha: 0.9) static let darkGreen = Color(hex: "#106543") + static let subtitleColor = Color(hex: "#696a6b") } struct Amazon { diff --git a/Nynja/Resources/en.lproj/Localizable.strings b/Nynja/Resources/en.lproj/Localizable.strings index fb913344a4486eeb33e3fd0fe4ddc67026a36d4f..96d8049a9c66e36b80d8b5486a1d77dcdd1073e6 100644 --- a/Nynja/Resources/en.lproj/Localizable.strings +++ b/Nynja/Resources/en.lproj/Localizable.strings @@ -386,3 +386,24 @@ "am" = "AM"; "pm" = "PM"; + +//MARK: Wheel Position Picker +"wheel position title" = "WHEEL POSITION"; +"choose wheel position" = "Choose wheel position."; +"wheel current position" = "current position"; +"wheel left hand" = "Left hand"; +"wheel right hand" = "Right hand"; +"wheel position picker alert title" = "Use this wheel position?"; + +//MARK: Theme Picker +"theme picker title" = "THEME"; +"choose theme title" = "Choose the theme that's best for you."; +"theme current name" = "current theme"; +"theme dark name" = "Back In Black"; +"theme light name" = "Stairway To Heaven"; +"theme picker alert title" = "Use this theme?"; +"theme cell subtitle text" = "MOBILE COMMUNICATOR"; + +//MARK: Build Number +"build number title" = "BUILD NUMBER"; +"build number version" = "NYNJA FOR iOS V%@"; diff --git a/Nynja/Services/WheelContainer/Manager/WCDataManager.swift b/Nynja/Services/WheelContainer/Manager/WCDataManager.swift index 48098ffaa118c96e2d737896947840354411a0ad..ef62bbdb55678234d21af24bc87db2d43915987f 100644 --- a/Nynja/Services/WheelContainer/Manager/WCDataManager.swift +++ b/Nynja/Services/WheelContainer/Manager/WCDataManager.swift @@ -6,7 +6,7 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // -class WCDataManager: WCDataManagerProtocol { +class WCDataManager: WCDataManagerProtocol, UISettingsRespondable { static let shared = WCDataManager() @@ -22,24 +22,59 @@ class WCDataManager: WCDataManagerProtocol { private var items: [ItemModels] = [] - private var _factory: WCItemsFactory? + private var _factory = WCItemsFactoryDecorator() + + + // MARK: - Init + + init() { + registerForUserInterfaceSettingsNotifications() + } + + deinit { + unregisterForUserInterfaceSettingsNotifications() + } + + + // MARK: - UISettingsRespondable + + func userInterfaceSettingsDidChange(_ newSettings: UISettings) { + switch newSettings.wheelPosition { + case .right: + self._factory.isReversed = false + case .left: + self._factory.isReversed = true + } + updateItems(for: self._factory) + } + + + // MARK: - Setup func setupItems(with factory: WCItemsFactory) { - if let prev = _factory, type(of: prev) == type(of: factory) { return } - + if let prev = _factory.originalFactory, type(of: prev) == type(of: factory) { + return + } invalidateRestoreTimer() + _factory.originalFactory = factory + updateItems(for: _factory) + } + + private func updateItems(for factory: WCItemsFactoryDecorator) { items = factory.items - _factory = factory //FIXME: - So hard, need more time to describe prepared states, something like "actions can be disabled and we should not show second lvl of wheel" - guard let chatFactory = factory as? ChatBaseFactory, !chatFactory.isActionsEnabled else { + guard let chatFactory = factory.originalFactory as? ChatBaseFactory, !chatFactory.isActionsEnabled else { return } items = [items[0]] } + + // MARK: - Restore Timer // MARK: Restore state after 5 sec + private var prevItems: [ItemModels]? private var restoreTimer: Timer? @@ -48,6 +83,20 @@ class WCDataManager: WCDataManagerProtocol { return true } + @objc private func declineRestore() { + wheelContainer?.shouldRestoreState = false + invalidateRestoreTimer() + } + + private func invalidateRestoreTimer() { + restoreTimer?.invalidate() + prevItems = nil + restoreTimer = nil + } + + + // MARK: - Actions + func showContainer() { if let _prevItems = prevItems { wheelContainerDS?.items = _prevItems @@ -64,7 +113,7 @@ class WCDataManager: WCDataManagerProtocol { wheelContainer?.reloadData() - if let indexPath = _factory?.initionalScrollStates, prevItems == nil { + if let indexPath = _factory.initionalScrollStates, prevItems == nil { wheelContainer?.scrollToItem(at: indexPath, animated: false) } } @@ -83,17 +132,54 @@ class WCDataManager: WCDataManagerProtocol { wheelContainer?.reloadData() } - // MARK: - Restore Timer - @objc private func declineRestore() { - wheelContainer?.shouldRestoreState = false - invalidateRestoreTimer() + func updateActionsState(_ isDisabled: Bool) { + let index: Int = 5 + let state: WheelItemState = isDisabled ? .disabled : .selected + let secondLevelItems: ItemModels = isDisabled ? [] : _factory.secondLevelItems + if var _dsItems = wheelContainerDS?.items, !_dsItems.isEmpty { + _dsItems.first?[index].state = state + + if secondLevelItems.isEmpty { + if _dsItems.count > 1 { + _dsItems = [_dsItems[0]] + } + } else { + if _dsItems.count == 1 { + _dsItems.append(secondLevelItems) + } + } + + wheelContainerDS?.items = _dsItems + } + + if var _prevItems = prevItems, !_prevItems.isEmpty { + _prevItems.first?[index].state = state + + if secondLevelItems.isEmpty { + if _prevItems.count > 1 { + _prevItems = [_prevItems[0]] + } + } else { + if _prevItems.count == 1 { + _prevItems.append(secondLevelItems) + } + } + } + + items.first?[index].state = state + if secondLevelItems.isEmpty { + if items.count > 1 { + items = [items[0]] + } + } else { + if items.count == 1 { + items.append(secondLevelItems) + } + } } - private func invalidateRestoreTimer() { - restoreTimer?.invalidate() - prevItems = nil - restoreTimer = nil - } + + // MARK: - Items func getSubItems(index: Int, of level: Int = 0) -> ItemModels? { guard level < items.count else { return nil } @@ -104,22 +190,38 @@ class WCDataManager: WCDataManagerProtocol { case .actions: return items[1] case .chats: - if _factory is P2pChatsItemsFactory { - return _factory?.secondLevelItems + if _factory.originalFactory is P2pChatsItemsFactory { + return _factory.secondLevelItems } - return P2pChatsItemsFactory().secondLevelItems.normal() + return WCItemsFactoryDecorator(self._factory) + .with(factory: P2pChatsItemsFactory()) + .secondLevelItems + .normal() + case .groups: - if _factory is GroupChatsItemsFactory { - return _factory?.secondLevelItems + if _factory.originalFactory is GroupChatsItemsFactory { + return _factory.secondLevelItems } - return GroupChatsItemsFactory().secondLevelItems.normal() + return WCItemsFactoryDecorator(self._factory) + .with(factory: GroupChatsItemsFactory()) + .secondLevelItems + .normal() + case .contacts: - if _factory is ContactsItemsFactory { - return _factory?.secondLevelItems + if _factory.originalFactory is ContactsItemsFactory { + return _factory.secondLevelItems } - return ContactsItemsFactory().secondLevelItems.normal() + return WCItemsFactoryDecorator(self._factory) + .with(factory: ContactsItemsFactory()) + .secondLevelItems + .normal() + case .options: - return OptionsItemsFactory().secondLevelItems + return WCItemsFactoryDecorator(self._factory) + .with(factory: OptionsItemsFactory()) + .secondLevelItems + .normal() + default: break } @@ -138,7 +240,7 @@ class WCDataManager: WCDataManagerProtocol { } - guard let factoryItem = _factory?[index]?.first(where: { + guard let factoryItem = _factory[index]?.first(where: { guard let factoryItem = $0 as? ImageWheelItemModel else { return false } @@ -149,50 +251,4 @@ class WCDataManager: WCDataManagerProtocol { return factoryItem.state == .highlighted ? .highlighted : .normal } - - func updateActionsState(_ isDisabled: Bool) { - let index: Int = 5 - let state: WheelItemState = isDisabled ? .disabled : .selected - let secondLevelItems : ItemModels = isDisabled ? [] : (_factory?.secondLevelItems ?? []) - if var _dsItems = wheelContainerDS?.items, !_dsItems.isEmpty { - _dsItems.first?[index].state = state - - if secondLevelItems.isEmpty { - if _dsItems.count > 1 { - _dsItems = [_dsItems[0]] - } - } else { - if _dsItems.count == 1 { - _dsItems.append(secondLevelItems) - } - } - - wheelContainerDS?.items = _dsItems - } - - if var _prevItems = prevItems, !_prevItems.isEmpty { - _prevItems.first?[index].state = state - - if secondLevelItems.isEmpty { - if _prevItems.count > 1 { - _prevItems = [_prevItems[0]] - } - } else { - if _prevItems.count == 1 { - _prevItems.append(secondLevelItems) - } - } - } - - items.first?[index].state = state - if secondLevelItems.isEmpty { - if items.count > 1 { - items = [items[0]] - } - } else { - if items.count == 1 { - items.append(secondLevelItems) - } - } - } } diff --git a/Nynja/ThemeItemsFactory.swift b/Nynja/ThemeItemsFactory.swift new file mode 100644 index 0000000000000000000000000000000000000000..f7e0724b16c179bbe625064613b2a096189c3fe0 --- /dev/null +++ b/Nynja/ThemeItemsFactory.swift @@ -0,0 +1,18 @@ +// +// ThemeItemsFactory.swift +// Nynja +// +// Created by Anton Poltoratskyi on 01.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +class ThemeItemsFactory: OptionsItemsFactory { + + // MARK: - Second lvl + + override var theme: ImageActionItemModel { + let item = super.theme + item.state = .highlighted + return item + } +} diff --git a/Nynja/UISettings/Theme.swift b/Nynja/UISettings/Theme.swift new file mode 100644 index 0000000000000000000000000000000000000000..4f59c3f1c77efca6fb7172f811ba2d2db8c71702 --- /dev/null +++ b/Nynja/UISettings/Theme.swift @@ -0,0 +1,35 @@ +// +// Theme.swift +// Nynja +// +// Created by Anton Poltoratskyi on 01.03.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +enum Theme: String { + static let `default` = Theme.dark + static let all: [Theme] = [light, dark] + + case light = "light" + case dark = "dark" + + var name: String { + switch self { + case .light: + return "theme light name".localized + case .dark: + return "theme dark name".localized + } + } + + var backgroundName: String { + switch self { + case .light: + return "splash_background" + case .dark: + return "background" + } + } +} diff --git a/Nynja/UISettings/UISettingsRespondable.swift b/Nynja/UISettings/UISettingsRespondable.swift new file mode 100644 index 0000000000000000000000000000000000000000..7e753a78b6326f590dfcfb25cb859792664d65f1 --- /dev/null +++ b/Nynja/UISettings/UISettingsRespondable.swift @@ -0,0 +1,28 @@ +// +// UISettingsRespondable.swift +// Nynja +// +// Created by Anton Poltoratskyi on 28.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol UISettingsRespondable: class { + func userInterfaceSettingsDidChange(_ newSettings: UISettings) +} + +extension UISettingsRespondable { + + func registerForUserInterfaceSettingsNotifications() { + UISettingsService.shared.addSubscriber(self) { [weak self] newSettings in + self?.userInterfaceSettingsDidChange(newSettings) + } + } + + func unregisterForUserInterfaceSettingsNotifications() { + UISettingsService.shared.removeSubscriber(self) + } + + func userInterfaceSettingsDidChange(_ newSettings: UISettings) { } +} diff --git a/Nynja/UISettings/UISettingsService.swift b/Nynja/UISettings/UISettingsService.swift new file mode 100644 index 0000000000000000000000000000000000000000..d4e8032a554a3da9b9b43d1c70e1aa0e0a860222 --- /dev/null +++ b/Nynja/UISettings/UISettingsService.swift @@ -0,0 +1,82 @@ +// +// UISettingsService.swift +// Nynja +// +// Created by Anton Poltoratskyi on 27.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +struct UISettings { + static let `default` = UISettings(wheelPosition: .default, + theme: .default) + + fileprivate(set) var wheelPosition: WheelPosition + fileprivate(set) var theme: Theme +} + +typealias UISettingsChangeHandler = (UISettings) -> Void + +final class UISettingsService { + private enum Keys { + static let wheelPosition = "wheelPosition" + static let theme = "theme" + } + static let shared = UISettingsService() + + private let storage = UserDefaults.standard + + // MARK: - Settings + + private lazy var settings: UISettings = { + return UISettings(wheelPosition: wheelPosition, theme: theme) + }() + + var wheelPosition: WheelPosition { + get { + guard let position = storage.string(forKey: Keys.wheelPosition) else { return .default } + return WheelPosition(rawValue: position) ?? .default + } + set { + storage.set(newValue.rawValue, forKey: Keys.wheelPosition) + settings.wheelPosition = newValue + notifySubscribers(with: settings) + } + } + + var theme: Theme { + get { + guard let position = storage.string(forKey: Keys.theme) else { return .default } + return Theme(rawValue: position) ?? .default + } + set { + storage.set(newValue.rawValue, forKey: Keys.theme) + settings.theme = newValue + notifySubscribers(with: settings) + } + } + + // MARK: - Subscribers + + private var subscribers: [AnyWeakSubscriber] = [] + + func addSubscriber(_ object: AnyObject, handler: @escaping UISettingsChangeHandler) { + guard !subscribers.contains(where: { $0.object.value === object }) else { + return + } + let subscriber = AnyWeakSubscriber(object: object, handler: handler) + subscribers.append(subscriber) + + // notify subscriber with current ui settings. + subscriber.handler(settings) + } + + func removeSubscriber(_ object: AnyObject) { + subscribers = subscribers.filter { $0.object.value != nil && $0.object.value !== object } + } + + func notifySubscribers(with settings: UISettings) { + subscribers.forEach { $0.handler(settings) } + } +} diff --git a/Nynja/UISettings/WheelPosition.swift b/Nynja/UISettings/WheelPosition.swift new file mode 100644 index 0000000000000000000000000000000000000000..ada5addccc391c4410f150b2d81544732a98e188 --- /dev/null +++ b/Nynja/UISettings/WheelPosition.swift @@ -0,0 +1,17 @@ +// +// WheelPosition.swift +// Nynja +// +// Created by Anton Poltoratskyi on 27.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +enum WheelPosition: String { + case left = "left" + case right = "right" + + static let `default` = WheelPosition.right + static let all: [WheelPosition] = [left, right] +} diff --git a/Nynja/Viper/BaseModule/BasePresenterProtocol.swift b/Nynja/Viper/BaseModule/BasePresenterProtocol.swift index afca9e38aa6cfb6d56eb3a37e59fcb6289653731..e86d339cb4c9942bfe6d00510adcfa9990a4995b 100644 --- a/Nynja/Viper/BaseModule/BasePresenterProtocol.swift +++ b/Nynja/Viper/BaseModule/BasePresenterProtocol.swift @@ -6,7 +6,7 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // -protocol BasePresenterProtocol { +protocol BasePresenterProtocol: class { var wcDataManager: WCDataManagerProtocol { get set } var itemsFactory: WCItemsFactory? { get } diff --git a/Nynja/WCItemsFactoryDecorator.swift b/Nynja/WCItemsFactoryDecorator.swift new file mode 100644 index 0000000000000000000000000000000000000000..4e61799c0c97c1f8d87e480a60fc9026ea44f960 --- /dev/null +++ b/Nynja/WCItemsFactoryDecorator.swift @@ -0,0 +1,86 @@ +// +// WCItemsFactoryDecorator.swift +// Nynja +// +// Created by Anton Poltoratskyi on 28.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +class WCItemsFactoryDecorator: WCItemsFactory { + + var originalFactory: WCItemsFactory? + + var isReversed: Bool + + private var map: ([WheelItemModel]) -> [WheelItemModel] { + if isReversed { + return { items in + items.map { + let clone = $0.cloned() + clone.isReversed = true + clone.subitems.reverse() + clone.subitems.forEach { $0.isReversed = true } + return clone + }.reversed() + } + } else { + return { items in items } + } + } + + + // MARK: - WCItemsFactory + + var navigateDelegate: NavigateProtocol? { + return originalFactory?.navigateDelegate + } + + var initionalScrollStates: IndexPath? { + guard isReversed else { + return originalFactory?.initionalScrollStates + } + guard let initialStates = originalFactory?.initionalScrollStates else { + return nil + } + + let levels = [firstLevelItems, secondLevelItems] + let indices: [Int] = zip(initialStates, levels).map { (scrollIndex, level) in + level.count - scrollIndex - 1 + } + return IndexPath(indexes: indices) + } + + var items: [ItemModels] { + return (originalFactory?.items ?? []).map { itemModels -> ItemModels in + return map(itemModels) + } + } + + var firstLevelItems: ItemModels { + return map(originalFactory?.firstLevelItems ?? []) + } + + var secondLevelItems: ItemModels { + return map(originalFactory?.secondLevelItems ?? []) + } + + + // MARK: - Init + + init(factory: WCItemsFactory? = nil, isReversed: Bool = false) { + self.originalFactory = factory + self.isReversed = isReversed + } + + init(_ decorator: WCItemsFactoryDecorator) { + self.originalFactory = decorator.originalFactory + self.isReversed = decorator.isReversed + } + + func with(factory: WCItemsFactory) -> Self { + self.originalFactory = factory + return self + } +} diff --git a/Nynja/WeakRef.swift b/Nynja/WeakRef.swift index c79f25327eda0a685daeca5d282d5e5e654fb2b3..7a5a0529999dd45870c2f476e1906f36875a4970 100644 --- a/Nynja/WeakRef.swift +++ b/Nynja/WeakRef.swift @@ -17,13 +17,12 @@ class WeakRef where T: AnyObject { } } - -//class DBModelRef where T: DBModelProtocol { -// -// private(set) var value: T? -// -// init(value: T?) { -// self.value = value -// } -//} - +class AnyWeakSubscriber { + let object: WeakRef + let handler: Handler + + init(object: AnyObject, handler: Handler) { + self.object = WeakRef(value: object) + self.handler = handler + } +} diff --git a/Nynja/WheelPositionItemsFactory.swift b/Nynja/WheelPositionItemsFactory.swift new file mode 100644 index 0000000000000000000000000000000000000000..7dbef17102656116628ce8ddfd7cb964b816d4f7 --- /dev/null +++ b/Nynja/WheelPositionItemsFactory.swift @@ -0,0 +1,18 @@ +// +// WheelPositionItemsFactory.swift +// Nynja +// +// Created by Anton Poltoratskyi on 27.02.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +class WheelPositionItemsFactory: OptionsItemsFactory { + + // MARK: - Second lvl + + override var wheelPosition: ImageActionItemModel { + let item = super.wheelPosition + item.state = .highlighted + return item + } +}