<file_sep>//
//  CommentsButton.swift
//  VKDemo
//
//  Created by <NAME> on 20/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit
import QuartzCore

@IBDesignable
class CommentsButton: UIControl {
    private let plashkaFillColor = UIColor.white
    private let fillColor = UIColor.clear
    private let strokeColor = UIColor(white: 0.7, alpha: 1.0)
    private let borderStrokeColor = #colorLiteral(red: 0.6666666667, green: 0.6666666667, blue: 0.6666666667, alpha: 0.5)
    
    private lazy var commentsIconLayer: CAShapeLayer = {
        let commentsPath = UIBezierPath()
        commentsPath.move(to: CGPoint(x: 30.29, y: 28.78))
        commentsPath.addCurve(to: CGPoint(x: 30.57, y: 29.42), controlPoint1: CGPoint(x: 30.38, y: 29.01), controlPoint2: CGPoint(x: 30.48, y: 29.22))
        commentsPath.addCurve(to: CGPoint(x: 30.92, y: 29.97), controlPoint1: CGPoint(x: 30.67, y: 29.61), controlPoint2: CGPoint(x: 30.79, y: 29.79))
        commentsPath.addCurve(to: CGPoint(x: 31.24, y: 30.39), controlPoint1: CGPoint(x: 31.06, y: 30.15), controlPoint2: CGPoint(x: 31.16, y: 30.28))
        commentsPath.addCurve(to: CGPoint(x: 31.6, y: 30.81), controlPoint1: CGPoint(x: 31.31, y: 30.49), controlPoint2: CGPoint(x: 31.43, y: 30.63))
        commentsPath.addCurve(to: CGPoint(x: 31.92, y: 31.17), controlPoint1: CGPoint(x: 31.77, y: 30.99), controlPoint2: CGPoint(x: 31.88, y: 31.11))
        commentsPath.addCurve(to: CGPoint(x: 31.98, y: 31.23), controlPoint1: CGPoint(x: 31.93, y: 31.18), controlPoint2: CGPoint(x: 31.95, y: 31.2))
        commentsPath.addCurve(to: CGPoint(x: 32.04, y: 31.3), controlPoint1: CGPoint(x: 32.01, y: 31.27), controlPoint2: CGPoint(x: 32.03, y: 31.29))
        commentsPath.addCurve(to: CGPoint(x: 32.1, y: 31.38), controlPoint1: CGPoint(x: 32.05, y: 31.32), controlPoint2: CGPoint(x: 32.07, y: 31.34))
        commentsPath.addCurve(to: CGPoint(x: 32.14, y: 31.46), controlPoint1: CGPoint(x: 32.12, y: 31.41), controlPoint2: CGPoint(x: 32.13, y: 31.44))
        commentsPath.addLine(to: CGPoint(x: 32.18, y: 31.53))
        commentsPath.addLine(to: CGPoint(x: 32.21, y: 31.61))
        commentsPath.addLine(to: CGPoint(x: 32.21, y: 31.71))
        commentsPath.addLine(to: CGPoint(x: 32.2, y: 31.8))
        commentsPath.addCurve(to: CGPoint(x: 32.02, y: 32.12), controlPoint1: CGPoint(x: 32.17, y: 31.93), controlPoint2: CGPoint(x: 32.11, y: 32.04))
        commentsPath.addCurve(to: CGPoint(x: 31.71, y: 32.22), controlPoint1: CGPoint(x: 31.93, y: 32.19), controlPoint2: CGPoint(x: 31.82, y: 32.23))
        commentsPath.addCurve(to: CGPoint(x: 30.51, y: 31.99), controlPoint1: CGPoint(x: 31.25, y: 32.15), controlPoint2: CGPoint(x: 30.85, y: 32.07))
        commentsPath.addCurve(to: CGPoint(x: 26.63, y: 30.15), controlPoint1: CGPoint(x: 29.08, y: 31.6), controlPoint2: CGPoint(x: 27.79, y: 30.99))
        commentsPath.addCurve(to: CGPoint(x: 24.18, y: 30.38), controlPoint1: CGPoint(x: 25.8, y: 30.3), controlPoint2: CGPoint(x: 24.98, y: 30.38))
        commentsPath.addCurve(to: CGPoint(x: 17.59, y: 28.48), controlPoint1: CGPoint(x: 21.66, y: 30.38), controlPoint2: CGPoint(x: 19.46, y: 29.75))
        commentsPath.addCurve(to: CGPoint(x: 18.82, y: 28.54), controlPoint1: CGPoint(x: 18.13, y: 28.52), controlPoint2: CGPoint(x: 18.54, y: 28.54))
        commentsPath.addCurve(to: CGPoint(x: 23.13, y: 27.89), controlPoint1: CGPoint(x: 20.32, y: 28.54), controlPoint2: CGPoint(x: 21.76, y: 28.33))
        commentsPath.addCurve(to: CGPoint(x: 26.82, y: 26.04), controlPoint1: CGPoint(x: 24.51, y: 27.46), controlPoint2: CGPoint(x: 25.74, y: 26.85))
        commentsPath.addCurve(to: CGPoint(x: 29.49, y: 23), controlPoint1: CGPoint(x: 27.98, y: 25.16), controlPoint2: CGPoint(x: 28.87, y: 24.15))
        commentsPath.addCurve(to: CGPoint(x: 30.43, y: 19.35), controlPoint1: CGPoint(x: 30.12, y: 21.85), controlPoint2: CGPoint(x: 30.43, y: 20.63))
        commentsPath.addCurve(to: CGPoint(x: 30.11, y: 17.17), controlPoint1: CGPoint(x: 30.43, y: 18.61), controlPoint2: CGPoint(x: 30.32, y: 17.89))
        commentsPath.addCurve(to: CGPoint(x: 32.95, y: 19.72), controlPoint1: CGPoint(x: 31.31, y: 17.85), controlPoint2: CGPoint(x: 32.26, y: 18.7))
        commentsPath.addCurve(to: CGPoint(x: 34, y: 23.03), controlPoint1: CGPoint(x: 33.65, y: 20.75), controlPoint2: CGPoint(x: 34, y: 21.85))
        commentsPath.addCurve(to: CGPoint(x: 33.01, y: 26.25), controlPoint1: CGPoint(x: 34, y: 24.18), controlPoint2: CGPoint(x: 33.67, y: 25.25))
        commentsPath.addCurve(to: CGPoint(x: 30.29, y: 28.78), controlPoint1: CGPoint(x: 32.35, y: 27.25), controlPoint2: CGPoint(x: 31.44, y: 28.1))
        commentsPath.close()
        commentsPath.move(to: CGPoint(x: 18.82, y: 12))
        commentsPath.addCurve(to: CGPoint(x: 23.75, y: 12.98), controlPoint1: CGPoint(x: 20.6, y: 12), controlPoint2: CGPoint(x: 22.24, y: 12.33))
        commentsPath.addCurve(to: CGPoint(x: 27.33, y: 15.66), controlPoint1: CGPoint(x: 25.26, y: 13.64), controlPoint2: CGPoint(x: 26.46, y: 14.53))
        commentsPath.addCurve(to: CGPoint(x: 28.64, y: 19.35), controlPoint1: CGPoint(x: 28.21, y: 16.79), controlPoint2: CGPoint(x: 28.64, y: 18.02))
        commentsPath.addCurve(to: CGPoint(x: 27.33, y: 23.04), controlPoint1: CGPoint(x: 28.64, y: 20.68), controlPoint2: CGPoint(x: 28.21, y: 21.91))
        commentsPath.addCurve(to: CGPoint(x: 23.75, y: 25.72), controlPoint1: CGPoint(x: 26.46, y: 24.17), controlPoint2: CGPoint(x: 25.26, y: 25.06))
        commentsPath.addCurve(to: CGPoint(x: 18.82, y: 26.7), controlPoint1: CGPoint(x: 22.24, y: 26.37), controlPoint2: CGPoint(x: 20.6, y: 26.7))
        commentsPath.addCurve(to: CGPoint(x: 16.37, y: 26.47), controlPoint1: CGPoint(x: 18.02, y: 26.7), controlPoint2: CGPoint(x: 17.2, y: 26.63))
        commentsPath.addCurve(to: CGPoint(x: 12.49, y: 28.31), controlPoint1: CGPoint(x: 15.21, y: 27.32), controlPoint2: CGPoint(x: 13.92, y: 27.93))
        commentsPath.addCurve(to: CGPoint(x: 11.29, y: 28.54), controlPoint1: CGPoint(x: 12.15, y: 28.4), controlPoint2: CGPoint(x: 11.75, y: 28.47))
        commentsPath.addLine(to: CGPoint(x: 11.25, y: 28.54))
        commentsPath.addCurve(to: CGPoint(x: 10.96, y: 28.43), controlPoint1: CGPoint(x: 11.14, y: 28.54), controlPoint2: CGPoint(x: 11.05, y: 28.5))
        commentsPath.addCurve(to: CGPoint(x: 10.8, y: 28.12), controlPoint1: CGPoint(x: 10.87, y: 28.35), controlPoint2: CGPoint(x: 10.82, y: 28.25))
        commentsPath.addCurve(to: CGPoint(x: 10.79, y: 28.03), controlPoint1: CGPoint(x: 10.79, y: 28.1), controlPoint2: CGPoint(x: 10.79, y: 28.06))
        commentsPath.addCurve(to: CGPoint(x: 10.79, y: 27.94), controlPoint1: CGPoint(x: 10.79, y: 28), controlPoint2: CGPoint(x: 10.79, y: 27.97))
        commentsPath.addCurve(to: CGPoint(x: 10.82, y: 27.85), controlPoint1: CGPoint(x: 10.8, y: 27.91), controlPoint2: CGPoint(x: 10.81, y: 27.88))
        commentsPath.addLine(to: CGPoint(x: 10.86, y: 27.78))
        commentsPath.addLine(to: CGPoint(x: 10.9, y: 27.7))
        commentsPath.addLine(to: CGPoint(x: 10.96, y: 27.63))
        commentsPath.addLine(to: CGPoint(x: 11.02, y: 27.56))
        commentsPath.addLine(to: CGPoint(x: 11.08, y: 27.49))
        commentsPath.addCurve(to: CGPoint(x: 11.4, y: 27.13), controlPoint1: CGPoint(x: 11.12, y: 27.43), controlPoint2: CGPoint(x: 11.23, y: 27.32))
        commentsPath.addCurve(to: CGPoint(x: 11.76, y: 26.71), controlPoint1: CGPoint(x: 11.57, y: 26.95), controlPoint2: CGPoint(x: 11.69, y: 26.81))
        commentsPath.addCurve(to: CGPoint(x: 12.08, y: 26.29), controlPoint1: CGPoint(x: 11.84, y: 26.61), controlPoint2: CGPoint(x: 11.94, y: 26.47))
        commentsPath.addCurve(to: CGPoint(x: 12.43, y: 25.74), controlPoint1: CGPoint(x: 12.21, y: 26.12), controlPoint2: CGPoint(x: 12.33, y: 25.93))
        commentsPath.addCurve(to: CGPoint(x: 12.71, y: 25.11), controlPoint1: CGPoint(x: 12.52, y: 25.55), controlPoint2: CGPoint(x: 12.62, y: 25.34))
        commentsPath.addCurve(to: CGPoint(x: 9.99, y: 22.57), controlPoint1: CGPoint(x: 11.56, y: 24.42), controlPoint2: CGPoint(x: 10.65, y: 23.57))
        commentsPath.addCurve(to: CGPoint(x: 9, y: 19.35), controlPoint1: CGPoint(x: 9.33, y: 21.56), controlPoint2: CGPoint(x: 9, y: 20.49))
        commentsPath.addCurve(to: CGPoint(x: 10.31, y: 15.66), controlPoint1: CGPoint(x: 9, y: 18.02), controlPoint2: CGPoint(x: 9.44, y: 16.79))
        commentsPath.addCurve(to: CGPoint(x: 13.89, y: 12.98), controlPoint1: CGPoint(x: 11.19, y: 14.53), controlPoint2: CGPoint(x: 12.38, y: 13.64))
        commentsPath.addCurve(to: CGPoint(x: 18.82, y: 12), controlPoint1: CGPoint(x: 15.4, y: 12.33), controlPoint2: CGPoint(x: 17.05, y: 12))
        commentsPath.close()
        commentsPath.move(to: CGPoint(x: 18.82, y: 13.84))
        commentsPath.addCurve(to: CGPoint(x: 14.83, y: 14.58), controlPoint1: CGPoint(x: 17.4, y: 13.84), controlPoint2: CGPoint(x: 16.07, y: 14.09))
        commentsPath.addCurve(to: CGPoint(x: 11.88, y: 16.61), controlPoint1: CGPoint(x: 13.59, y: 15.08), controlPoint2: CGPoint(x: 12.61, y: 15.76))
        commentsPath.addCurve(to: CGPoint(x: 10.79, y: 19.35), controlPoint1: CGPoint(x: 11.15, y: 17.46), controlPoint2: CGPoint(x: 10.79, y: 18.37))
        commentsPath.addCurve(to: CGPoint(x: 11.52, y: 21.62), controlPoint1: CGPoint(x: 10.79, y: 20.14), controlPoint2: CGPoint(x: 11.03, y: 20.89))
        commentsPath.addCurve(to: CGPoint(x: 13.6, y: 23.52), controlPoint1: CGPoint(x: 12.02, y: 22.35), controlPoint2: CGPoint(x: 12.71, y: 22.98))
        commentsPath.addLine(to: CGPoint(x: 14.96, y: 24.32))
        commentsPath.addLine(to: CGPoint(x: 14.47, y: 25.53))
        commentsPath.addCurve(to: CGPoint(x: 15.33, y: 24.97), controlPoint1: CGPoint(x: 14.79, y: 25.33), controlPoint2: CGPoint(x: 15.07, y: 25.15))
        commentsPath.addLine(to: CGPoint(x: 15.95, y: 24.52))
        commentsPath.addLine(to: CGPoint(x: 16.69, y: 24.66))
        commentsPath.addCurve(to: CGPoint(x: 18.82, y: 24.86), controlPoint1: CGPoint(x: 17.41, y: 24.8), controlPoint2: CGPoint(x: 18.12, y: 24.86))
        commentsPath.addCurve(to: CGPoint(x: 22.81, y: 24.12), controlPoint1: CGPoint(x: 20.24, y: 24.86), controlPoint2: CGPoint(x: 21.57, y: 24.62))
        commentsPath.addCurve(to: CGPoint(x: 25.76, y: 22.09), controlPoint1: CGPoint(x: 24.05, y: 23.62), controlPoint2: CGPoint(x: 25.03, y: 22.95))
        commentsPath.addCurve(to: CGPoint(x: 26.86, y: 19.35), controlPoint1: CGPoint(x: 26.49, y: 21.24), controlPoint2: CGPoint(x: 26.86, y: 20.33))
        commentsPath.addCurve(to: CGPoint(x: 25.76, y: 16.61), controlPoint1: CGPoint(x: 26.86, y: 18.37), controlPoint2: CGPoint(x: 26.49, y: 17.46))
        commentsPath.addCurve(to: CGPoint(x: 22.81, y: 14.58), controlPoint1: CGPoint(x: 25.03, y: 15.76), controlPoint2: CGPoint(x: 24.05, y: 15.08))
        commentsPath.addCurve(to: CGPoint(x: 18.82, y: 13.84), controlPoint1: CGPoint(x: 21.57, y: 14.09), controlPoint2: CGPoint(x: 20.24, y: 13.84))
        commentsPath.close()
        strokeColor.setStroke()
        commentsPath.lineWidth = 1
        commentsPath.stroke()

        let layer = CAShapeLayer()
        layer.path = commentsPath.cgPath
        layer.fillColor = fillColor.cgColor
        layer.strokeColor = strokeColor.cgColor
        layer.lineWidth = 1
        
        return layer
    }()
    
    private lazy var borderLayer: CAShapeLayer = {
        let layer = CAShapeLayer()
        let rectanglePath = UIBezierPath(roundedRect: CGRect(x: 1, y: 1, width: 93, height: 42), cornerRadius: 21)
        fillColor.setStroke()
        rectanglePath.lineWidth = 0.5
        rectanglePath.lineCapStyle = .round
        rectanglePath.lineJoinStyle = .round
        rectanglePath.stroke()

        layer.path = rectanglePath.cgPath
        layer.strokeColor = borderStrokeColor.cgColor
        layer.fillColor = plashkaFillColor.cgColor
        layer.lineWidth = 0.5
        
        return layer
    }()
    
    private lazy var countLabel: UILabel = {
        let label = UILabel()
        label.textColor = UIColor(white: 0.3, alpha: 1.0)
        label.text = "123"
        label.frame = CGRect(x: 42, y: 11, width: 41, height: 21)
        label.font = UIFont.systemFont(ofSize: 13.0)
        
        return label
    }()
    
    private func addLayers() {
        [borderLayer, commentsIconLayer].forEach { layer.addSublayer($0) }
        addSubview(countLabel)
    }
    
    private func removeLayersIfNeeded() {
        layer.sublayers?.forEach { $0.removeFromSuperlayer() }
    }
    
    private func configureLayers() {
        removeLayersIfNeeded()
        addLayers()
        
        for subLayer in [
          borderLayer,
          commentsIconLayer
        ] {
            subLayer.frame = bounds
            subLayer.setNeedsDisplay()
        }
    }
        
    override init(frame: CGRect) {
        super.init(frame: frame)
        addLayers()
        backgroundColor = .clear
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        addLayers()
        backgroundColor = .clear
    }
    
    public func setup(commentsCount: Int) {
        countLabel.text = "\(commentsCount)"
    }
}
<file_sep>//
//  ViperRouterProtocol.swift
//  Vanilla
//
//  Created by <NAME> on 6/8/17.
//  Copyright © 2017 BrightBox. All rights reserved.
//

import Foundation

public protocol ViperRouterProtocol {
    var transitionHandler: ViperModuleTransitionHandler! { get set }
}
<file_sep>//
//  FeedRouterInput.swift
//  VKDemo
//
//  Created by AntonKrylovBrightBox on 10/06/2019.
//  Copyright © 2019 BrightBox. All rights reserved.
//

import Foundation

protocol FeedRouterInput {

}
<file_sep>//
//  ConversationsResponse.swift
//  VKDemo
//
//  Created by <NAME> on 01.11.2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct ConversationsResponse: Codable {
    let count: Int
    let items: [ConversationDto]
}

struct ConversationDto: Codable {
    let peer: PeerDto
    let inRead: Int
    let outRead: Int
    let unreadCount: Int
    let important: BoolInt
    let unanswered: BoolInt
    let pushSettings: PushSettingsDto
    let canWrite: CanWriteDto
    let chatSettings: ChatSettingsDto
    
    private enum CodingKeys: String, CodingKey {
        case peer
        case inRead = "in_read"
        case outRead = "out_read"
        case unreadCount = "unread_count"
        case important
        case unanswered
        case pushSettings = "push_settings"
        case canWrite = "can_write"
        case chatSettings = "chat_settings"
    }
}

struct PeerDto: Codable {
    let id: Int
    let type: String
    let localId: Int
    
    private enum CodingKeys: String, CodingKey {
        case id
        case type
        case localId = "local_id"
    }
}

struct PushSettingsDto: Codable {
    let disabledUntil: Int
    let disabledForever: BoolInt
    let noSound: BoolInt
    
    private enum CodingKeys: String, CodingKey {
        case disabledUntil = "disabled_until"
        case disabledForever = "disabled_forever"
        case noSound = "no_sound"
    }
}

struct CanWriteDto: Codable {
    let allowed: BoolInt
    let reason: Int
}

struct ChatSettingsDto: Codable {
    let membersCount: Int
    let title: String
    let pinnedMessage: PinnedMessageDto
    let state: String
    let photo: ChatPhotoDto
    let activeIds: [Int]
    let isGroupChannel: BoolInt

    
    private enum CodingKeys: String, CodingKey {
        case membersCount = "members_count"
        case title
        case pinnedMessage = "pinned_message"
        case state
        case photo
        case activeIds = "active_ids"
        case isGroupChannel = "is_group_channel"
    }
}

struct PinnedMessageDto: Codable {
    let id: Int
    let date: Int
    let fromId: Int
    let text: String
    let attachments: String // TODO: Struct
    let geo: GeoDto
}

struct ChatPhotoDto: Codable {
    let photo50: String?
    let photo100: String?
    let photo200: String?
    
    private enum CodingKeys: String, CodingKey {
        case photo50 = "photo_50"
        case photo100 = "photo_100"
        case photo200 = "photo_200"
    }
}

struct GeoDto: Codable {
    let type: String
    let coordinates: String
    let place: GeoPlaceDto
}

struct GeoPlaceDto: Codable {
    let id: Int
    let title: String
    let latitude: Double
    let longitude: Double
    let created: Int
    let iconUrl: String
    let country: String
    let city: String
    
    private enum CodingKeys: String, CodingKey {
        case id
        case title
        case latitude
        case longitude
        case created
        case iconUrl = "icon"
        case country
        case city
    }
}


<file_sep>//
//  FeedTextView.swift
//  VKDemo
//
//  Created by <NAME> on 07.11.2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit

class FeedTextView: UITextView {
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        font = UIFont.systemFont(ofSize: 14.0)
        isScrollEnabled = false
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        font = UIFont.systemFont(ofSize: 14.0)
        isScrollEnabled = false
    }
}
<file_sep>//
//  FeedResponse.swift
//  VKDemo
//
//  Created by <NAME> on 10/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct FeedResponse: Codable {
    let items: [FeedItemDto]
    let profiles: [UserDto]?
//    TODO:
//    Поле profiles содержит массив объектов пользователей (UserDto) с дополнительными полями:
//    photo — адрес фотографии пользователя размером 50x50px;
//    photo_medium_rec — адрес фотографии пользователя раз мером 100x100px;
//    screen_name — короткий адрес страницы пользователя (например, andrew или id6492).

    let groups: [GroupDto]?
    let nextFrom: String
    
    private enum CodingKeys: String, CodingKey {
        case items
        case profiles
        case groups
        case nextFrom = "next_from"
    }
}
<file_sep>//
//  DI.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

class DI {
    private static let shared = DI()
    
    static var container: DIContainerProtocol {
        return shared.container
    }
    
    private let container: DIContainerProtocol
    
    init() {
        container = DIContainer()
    }
}
<file_sep>//
//  MessagesParser.swift
//  VKDemo
//
//  Created by <NAME> on 01.11.2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct ConversationItem {
//    let item: FeedItemDto
//    let profiles: [UserDto]?
//    let groups: [GroupDto]?
}

protocol IMessagesParser {
    func parse(conversationsResponse: ConversationsResponse) -> [ConversationItem]
}

class MessagesParser: IMessagesParser {
    func parse(conversationsResponse: ConversationsResponse) -> [ConversationItem] {
//        var feedItems = [FeedItem]()
//        let profiles = feedResponse.profiles ?? []
//        let groups = feedResponse.groups ?? []
//
//        feedResponse.items.forEach { feedItemDto in
//            let copyHistoryOwnerIds: [Int]
//            if let copyHistory = feedItemDto.copyHistory {
//                copyHistoryOwnerIds = copyHistory.map { $0.ownerId }
//            } else {
//                copyHistoryOwnerIds = [Int]()
//            }
//
//            let copyHistoryFromIds: [Int]
//            if let copyHistory = feedItemDto.copyHistory {
//                copyHistoryFromIds = copyHistory.map { $0.fromId }
//            } else {
//                copyHistoryFromIds = [Int]()
//            }
//
//            var optionalProfileAndGroupIds = [Int]()
//            if let signerId = feedItemDto.signerId {
//                optionalProfileAndGroupIds.append(signerId)
//            }
//
//            let distinctProfileAndGroupIds = Set(
//                copyHistoryOwnerIds
//                + copyHistoryFromIds
//                + optionalProfileAndGroupIds
//                + [feedItemDto.sourceId]
//            )
//
//            let distinctProfileIds = distinctProfileAndGroupIds
//                .filter { $0 > 0 }
//
//            let distinctGroupIds = distinctProfileAndGroupIds
//                .filter { $0 < 0 }
//                .map { abs($0) }
//
//            let profilesForCurrentFeedItem = profiles.filter { distinctProfileIds.contains($0.id) }
//            let groupsForCurrentFeedItem = groups.filter { distinctGroupIds.contains($0.id) }
//
//            let feedItem = FeedItem(item: feedItemDto,
//                                    profiles: profilesForCurrentFeedItem,
//                                    groups: groupsForCurrentFeedItem)
//            feedItems.append(feedItem)
//        }
//
//        return feedItems
        return [ConversationItem]()
    }
}
<file_sep>//
//  FeedTarget.swift
//  VKDemo
//
//  Created by <NAME> on 10/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import Moya
import Alamofire

enum FeedTarget {
    case getFeed(count: Int, startFrom: String?)
    case addLike(feedItem: FeedItem)
    case deleteLike(feedItem: FeedItem)
}

extension FeedTarget: TargetType {
    
    private var sessionManager: IUserSessionManager {
        return  DI.container.core.sessionManager
    }
    
    var baseURL: URL {
        return URL(string: "https://api.vk.com/method/")!
    }
    
    var path: String {
        switch self {
        case .getFeed:
            return "newsfeed.get"
        case .addLike:
            return "likes.add"
        case .deleteLike:
            return "likes.delete"
        }
    }
    
    var method: HTTPMethod {
        switch self {
        case .getFeed:
            return .get
        case .addLike:
            return .post
        case .deleteLike:
            return .post
        }
    }
    
    var sampleData: Data {
        return Data()
    }
    
    var task: Task {
        switch self {
        case .getFeed(let count, let startFrom):
            let startFromString = startFrom ?? ""
            
            return .requestParameters(parameters: ["v": 5.95,
                                                   "access_token": sessionManager.getAccessToken() ?? "",
                                                   "filters" : "post,photo",
                                                   "count": count,
                                                   "start_from": startFromString],
                                      encoding: URLEncoding.queryString)
        case .addLike(let feedItem):
            let feedType = feedItem.item.type
            let ownerId = feedItem.item.sourceId
            let itemId = (feedItem.item.postId != nil) ? String(describing: feedItem.item.postId!) : ""
            let standardParams = ["v": 5.103,
                                  "access_token": sessionManager.getAccessToken() ?? "",
                                  "type": feedType,
                                  "owner_id": ownerId,
                                  "item_id": itemId] as [String: Any]
            
            return .requestParameters(parameters: standardParams,
                                      encoding: URLEncoding.httpBody)
        case .deleteLike(let feedItem):
            let feedType = feedItem.item.type
            let ownerId = feedItem.item.sourceId
            let itemId = (feedItem.item.postId != nil) ? String(describing: feedItem.item.postId!) : ""
            let standardParams = ["v": 5.103,
                                  "access_token": sessionManager.getAccessToken() ?? "",
                                  "type": feedType,
                                  "owner_id": ownerId,
                                  "item_id": itemId] as [String: Any]
            
            return .requestParameters(parameters: standardParams,
                                      encoding: URLEncoding.httpBody)
        }
    }
    
    var headers: [String : String]? {
        switch self {
        default:
            return nil
        }
    }
}

extension FeedTarget: VKAccessTokenAuthorizable {
    var authorizationType: VKAuthorizationType {
        return .oauth
    }
}
<file_sep>//
//  MainTabBarCoordinator.swift
//  VKDemo
//
//  Created by <NAME> on 08/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit

protocol IMainTabBarCoordinator {
    func start()
}

class MainTabBarCoordinator {
}

extension MainTabBarCoordinator: IMainTabBarCoordinator {
    func start() {
        guard let window = (UIApplication.shared.delegate as? AppDelegate)?.window else {
            return
        }
        
        let tabBarController = UITabBarController()
        let feedVC = FeedRouter().createModule()
        let chatVC = ChatRouter().createModule()
        
        feedVC.tabBarItem = UITabBarItem(tabBarSystemItem: .recents, tag: 0)
        chatVC.tabBarItem = UITabBarItem(tabBarSystemItem: .contacts, tag: 1)
        
        let controllers = [feedVC, chatVC]
        
        tabBarController.viewControllers = controllers.map { UINavigationController(rootViewController: $0)}
        
        window.rootViewController = tabBarController
    }
}
<file_sep>//
//  VkLogoView.swift
//  VKDemo
//
//  Created by <NAME> on 13/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import UIKit

private let fillColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)
private let blueMain = UIColor(red: 0.294, green: 0.471, blue: 0.706, alpha: 1.000)

enum VkLogoViewShape {
    case rect
    case roundedRect
    case circle
    case star
}

enum VkLogoViewState {
    case normal
    case loading
    case success
    case fail
}

class VkLogoView: UIView {
    private var currentShape: VkLogoViewShape
    private var currentState: VkLogoViewState
    
    private let vkLayer = CAShapeLayer()
    private let backgroundLayer = CAShapeLayer()
    
    init(radius: CGFloat, currentShape: VkLogoViewShape?, currentState: VkLogoViewState?) {
        self.currentShape = currentShape ?? .roundedRect
        self.currentState = currentState ?? .normal
        
        super.init(frame: CGRect(origin: .zero, size: CGSize(width: 2*radius, height: 2*radius)))
        commonInit()
    }
    
    private func commonInit() {
        backgroundColor = .clear
    }

    // Сами буквы VK
    private func getVkPath(frame: CGRect) -> UIBezierPath {
        let vkPath = UIBezierPath()
        
        vkPath.move(to: CGPoint(x: frame.minX + 0.80737 * frame.width, y: frame.minY + 0.71355 * frame.height))
        vkPath.addLine(to: CGPoint(x: frame.minX + 0.73445 * frame.width, y: frame.minY + 0.71355 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.64906 * frame.width, y: frame.minY + 0.64193 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.70665 * frame.width, y: frame.minY + 0.71355 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.69849 * frame.width, y: frame.minY + 0.69136 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.57644 * frame.width, y: frame.minY + 0.59481 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.60579 * frame.width, y: frame.minY + 0.60021 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.58725 * frame.width, y: frame.minY + 0.59481 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.55736 * frame.width, y: frame.minY + 0.61980 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.56175 * frame.width, y: frame.minY + 0.59481 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.55736 * frame.width, y: frame.minY + 0.59896 * frame.height))
        vkPath.addLine(to: CGPoint(x: frame.minX + 0.55736 * frame.width, y: frame.minY + 0.68518 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.50529 * frame.width, y: frame.minY + 0.71355 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.55736 * frame.width, y: frame.minY + 0.70312 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.55164 * frame.width, y: frame.minY + 0.71355 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.28289 * frame.width, y: frame.minY + 0.58013 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.42803 * frame.width, y: frame.minY + 0.71355 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.34315 * frame.width, y: frame.minY + 0.66664 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.16779 * frame.width, y: frame.minY + 0.33833 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.19251 * frame.width, y: frame.minY + 0.45344 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.16779 * frame.width, y: frame.minY + 0.35764 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.19279 * frame.width, y: frame.minY + 0.31770 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.16779 * frame.width, y: frame.minY + 0.32752 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.17195 * frame.width, y: frame.minY + 0.31770 * frame.height))
        vkPath.addLine(to: CGPoint(x: frame.minX + 0.26571 * frame.width, y: frame.minY + 0.31770 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.29834 * frame.width, y: frame.minY + 0.34606 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.28434 * frame.width, y: frame.minY + 0.31770 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.29134 * frame.width, y: frame.minY + 0.32587 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.41885 * frame.width, y: frame.minY + 0.54074 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.33399 * frame.width, y: frame.minY + 0.44994 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.39414 * frame.width, y: frame.minY + 0.54074 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.43238 * frame.width, y: frame.minY + 0.51291 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.42812 * frame.width, y: frame.minY + 0.54074 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.43238 * frame.width, y: frame.minY + 0.53645 * frame.height))
        vkPath.addLine(to: CGPoint(x: frame.minX + 0.43238 * frame.width, y: frame.minY + 0.40554 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.40339 * frame.width, y: frame.minY + 0.33447 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.42966 * frame.width, y: frame.minY + 0.35611 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.40339 * frame.width, y: frame.minY + 0.35205 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.42196 * frame.width, y: frame.minY + 0.31770 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.40339 * frame.width, y: frame.minY + 0.32632 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.41036 * frame.width, y: frame.minY + 0.31770 * frame.height))
        vkPath.addLine(to: CGPoint(x: frame.minX + 0.53653 * frame.width, y: frame.minY + 0.31770 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.55736 * frame.width, y: frame.minY + 0.34451 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.55199 * frame.width, y: frame.minY + 0.31770 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.55736 * frame.width, y: frame.minY + 0.32597 * frame.height))
        vkPath.addLine(to: CGPoint(x: frame.minX + 0.55736 * frame.width, y: frame.minY + 0.48897 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.56871 * frame.width, y: frame.minY + 0.50983 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.55736 * frame.width, y: frame.minY + 0.50441 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.56408 * frame.width, y: frame.minY + 0.50983 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.60270 * frame.width, y: frame.minY + 0.48742 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.57798 * frame.width, y: frame.minY + 0.50983 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.58571 * frame.width, y: frame.minY + 0.50441 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.69231 * frame.width, y: frame.minY + 0.33833 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.65523 * frame.width, y: frame.minY + 0.42871 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.69231 * frame.width, y: frame.minY + 0.33833 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.72403 * frame.width, y: frame.minY + 0.31770 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.69695 * frame.width, y: frame.minY + 0.32752 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.70550 * frame.width, y: frame.minY + 0.31770 * frame.height))
        vkPath.addLine(to: CGPoint(x: frame.minX + 0.79695 * frame.width, y: frame.minY + 0.31770 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.81900 * frame.width, y: frame.minY + 0.34451 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.81900 * frame.width, y: frame.minY + 0.31770 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.82364 * frame.width, y: frame.minY + 0.32907 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.72089 * frame.width, y: frame.minY + 0.51214 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.80974 * frame.width, y: frame.minY + 0.38700 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.72089 * frame.width, y: frame.minY + 0.51214 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.72089 * frame.width, y: frame.minY + 0.54459 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.71316 * frame.width, y: frame.minY + 0.52451 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.71008 * frame.width, y: frame.minY + 0.53069 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.77110 * frame.width, y: frame.minY + 0.59712 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.72862 * frame.width, y: frame.minY + 0.55541 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.75411 * frame.width, y: frame.minY + 0.57703 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.83261 * frame.width, y: frame.minY + 0.68257 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.80243 * frame.width, y: frame.minY + 0.63235 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.82603 * frame.width, y: frame.minY + 0.66208 * frame.height))
        vkPath.addCurve(to: CGPoint(x: frame.minX + 0.80737 * frame.width, y: frame.minY + 0.71355 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.83862 * frame.width, y: frame.minY + 0.70312 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.82820 * frame.width, y: frame.minY + 0.71355 * frame.height))
        vkPath.close()
        
        return vkPath
    }
    
    // Фон
    private func getBackgroundPath(frame: CGRect) -> UIBezierPath {
        let backgroundPath = UIBezierPath()
        backgroundPath.move(to: CGPoint(x: frame.minX + 0.65333 * frame.width, y: frame.minY + 1.00000 * frame.height))
        backgroundPath.addLine(to: CGPoint(x: frame.minX + 0.34667 * frame.width, y: frame.minY + 1.00000 * frame.height))
        backgroundPath.addCurve(to: CGPoint(x: frame.minX + 0.00000 * frame.width, y: frame.minY + 0.65333 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.06667 * frame.width, y: frame.minY + 1.00000 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.00000 * frame.width, y: frame.minY + 0.93333 * frame.height))
        backgroundPath.addLine(to: CGPoint(x: frame.minX + 0.00000 * frame.width, y: frame.minY + 0.34666 * frame.height))
        backgroundPath.addCurve(to: CGPoint(x: frame.minX + 0.34667 * frame.width, y: frame.minY + 0.00000 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.00000 * frame.width, y: frame.minY + 0.06667 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.06667 * frame.width, y: frame.minY + 0.00000 * frame.height))
        backgroundPath.addLine(to: CGPoint(x: frame.minX + 0.65333 * frame.width, y: frame.minY + 0.00000 * frame.height))
        backgroundPath.addCurve(to: CGPoint(x: frame.minX + 1.00000 * frame.width, y: frame.minY + 0.34666 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.93333 * frame.width, y: frame.minY + 0.00000 * frame.height), controlPoint2: CGPoint(x: frame.minX + 1.00000 * frame.width, y: frame.minY + 0.06667 * frame.height))
        backgroundPath.addLine(to: CGPoint(x: frame.minX + 1.00000 * frame.width, y: frame.minY + 0.65333 * frame.height))
        backgroundPath.addCurve(to: CGPoint(x: frame.minX + 0.65333 * frame.width, y: frame.minY + 1.00000 * frame.height), controlPoint1: CGPoint(x: frame.minX + 1.00000 * frame.width, y: frame.minY + 0.93333 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.93333 * frame.width, y: frame.minY + 1.00000 * frame.height))
        fillColor.setFill()
        backgroundPath.fill()
        backgroundPath.usesEvenOddFillRule = true
        backgroundPath.close()
        
        return backgroundPath
    }
    
    private func getApplePath(frame: CGRect) -> UIBezierPath {
        let bezierPath = UIBezierPath()
        bezierPath.move(to: CGPoint(x: frame.minX + 0.62723 * frame.width, y: frame.minY + 0.51575 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.63882 * frame.width, y: frame.minY + 0.46806 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.62723 * frame.width, y: frame.minY + 0.49667 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.63212 * frame.width, y: frame.minY + 0.48070 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.67766 * frame.width, y: frame.minY + 0.42550 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.65323 * frame.width, y: frame.minY + 0.44021 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.67612 * frame.width, y: frame.minY + 0.42631 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.59226 * frame.width, y: frame.minY + 0.37806 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.65040 * frame.width, y: frame.minY + 0.38426 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.60795 * frame.width, y: frame.minY + 0.37885 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.50351 * frame.width, y: frame.minY + 0.40000 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.55625 * frame.width, y: frame.minY + 0.37446 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.52178 * frame.width, y: frame.minY + 0.40000 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.42635 * frame.width, y: frame.minY + 0.37911 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.48523 * frame.width, y: frame.minY + 0.40000 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.45644 * frame.width, y: frame.minY + 0.37885 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.32989 * frame.width, y: frame.minY + 0.43892 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.38698 * frame.width, y: frame.minY + 0.38012 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.35046 * frame.width, y: frame.minY + 0.40284 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.31600 * frame.width, y: frame.minY + 0.47476 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.32345 * frame.width, y: frame.minY + 0.45001 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.31908 * frame.width, y: frame.minY + 0.46214 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.35973 * frame.width, y: frame.minY + 0.67842 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.29851 * frame.width, y: frame.minY + 0.54361 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.32602 * frame.width, y: frame.minY + 0.62843 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.43304 * frame.width, y: frame.minY + 0.73850 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.37902 * frame.width, y: frame.minY + 0.70705 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.40268 * frame.width, y: frame.minY + 0.73981 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.50943 * frame.width, y: frame.minY + 0.71915 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.46262 * frame.width, y: frame.minY + 0.73748 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.47369 * frame.width, y: frame.minY + 0.71915 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.58634 * frame.width, y: frame.minY + 0.73774 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.54518 * frame.width, y: frame.minY + 0.71915 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.55497 * frame.width, y: frame.minY + 0.73850 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.65785 * frame.width, y: frame.minY + 0.67973 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.61823 * frame.width, y: frame.minY + 0.73748 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.63829 * frame.width, y: frame.minY + 0.70834 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.69000 * frame.width, y: frame.minY + 0.61218 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.68022 * frame.width, y: frame.minY + 0.64597 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.68925 * frame.width, y: frame.minY + 0.61374 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.62723 * frame.width, y: frame.minY + 0.51575 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.68925 * frame.width, y: frame.minY + 0.61168 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.62802 * frame.width, y: frame.minY + 0.58769 * frame.height))
        bezierPath.close()
        bezierPath.move(to: CGPoint(x: frame.minX + 0.56885 * frame.width, y: frame.minY + 0.33889 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.59329 * frame.width, y: frame.minY + 0.26333 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.58531 * frame.width, y: frame.minY + 0.31902 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.59587 * frame.width, y: frame.minY + 0.29091 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.52434 * frame.width, y: frame.minY + 0.29918 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.56962 * frame.width, y: frame.minY + 0.26410 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.54107 * frame.width, y: frame.minY + 0.27883 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.49991 * frame.width, y: frame.minY + 0.37293 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.50943 * frame.width, y: frame.minY + 0.31723 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.49630 * frame.width, y: frame.minY + 0.34534 * frame.height))
        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.56885 * frame.width, y: frame.minY + 0.33914 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.52564 * frame.width, y: frame.minY + 0.37472 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.55264 * frame.width, y: frame.minY + 0.35924 * frame.height))
        bezierPath.addLine(to: CGPoint(x: frame.minX + 0.56885 * frame.width, y: frame.minY + 0.33889 * frame.height))
        bezierPath.close()
        bezierPath.usesEvenOddFillRule = true
        blueMain.setFill()
        bezierPath.fill()
        blueMain.setStroke()
        bezierPath.lineWidth = 2.31
        bezierPath.stroke()
        
        return bezierPath
    }
    
    private func getDotPath(frame: CGRect) -> UIBezierPath {
        func fastFloor(_ x: CGFloat) -> CGFloat { return floor(x) }
        
        //// Oval Drawing
        let ovalPath = UIBezierPath(ovalIn: CGRect(x: frame.minX + fastFloor(frame.width * 0.44667 + 0.5), y: frame.minY + fastFloor(frame.height * 0.44667 + 0.5), width: fastFloor(frame.width * 0.55333 + 0.5) - fastFloor(frame.width * 0.44667 + 0.5), height: fastFloor(frame.height * 0.55333 + 0.5) - fastFloor(frame.height * 0.44667 + 0.5)))
        blueMain.setFill()
        ovalPath.fill()
        blueMain.setStroke()
        ovalPath.lineWidth = 10
        ovalPath.stroke()

        return ovalPath
    }
    
    private func getBlankPath(frame: CGRect) -> UIBezierPath {
        func fastFloor(_ x: CGFloat) -> CGFloat { return floor(x) }
        return UIBezierPath(rect: CGRect(x: frame.midX, y: frame.midY, width: 0, height: 0) )
    }

    required init?(coder aDecoder: NSCoder) {
        self.currentShape = .roundedRect
        self.currentState = .normal
        
        super.init(coder: aDecoder)
        commonInit()
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        commonInit()
    }
    
    override func draw(_ rect: CGRect) {
        let backgroundPath = getBackgroundPath(frame: rect)
        backgroundLayer.path = backgroundPath.cgPath
        backgroundLayer.fillColor = UIColor.white.cgColor
        layer.addSublayer(backgroundLayer)
        
        let vkPath = getVkPath(frame: rect)
        vkLayer.path = vkPath.cgPath
        vkLayer.fillColor = blueMain.cgColor
        layer.addSublayer(vkLayer)
    }
    
    func showAnimation() {
        
//        CATransaction.begin()
        let animation: CABasicAnimation = CABasicAnimation(keyPath: "path")
        animation.delegate = self
        animation.fromValue = vkLayer.path
        animation.toValue = getBlankPath(frame: bounds).cgPath
        animation.duration = 0.75
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
        animation.fillMode = kCAFillModeForwards
        animation.autoreverses = false
        vkLayer.add(animation, forKey: "pathAnimation")
//        CATransaction.commit()
        self.vkLayer.path = self.getBlankPath(frame: self.bounds).cgPath


    }
}

extension VkLogoView: CAAnimationDelegate {
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        let animatio: CABasicAnimation = CABasicAnimation(keyPath: "path")
//        animatio.delegate = self
        animatio.fromValue = self.getBlankPath(frame: self.bounds).cgPath
        animatio.toValue = self.getApplePath(frame: self.bounds).cgPath
        animatio.duration = 0.75
        animatio.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        animatio.fillMode = kCAFillModeForwards
        animatio.autoreverses = false
        self.vkLayer.add(animatio, forKey: "pathAnimation")
        
        self.vkLayer.path = self.getApplePath(frame: self.bounds).cgPath

    }
}
<file_sep>//
//  IFeedManager.swift
//  VKDemo
//
//  Created by <NAME> on 05/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

protocol IFeedManager {
    func getFeed(count: Int,
                 startFrom: String?,
                 success: @escaping ([FeedItem], String) -> Void,
                 failure: @escaping FailureClosure)
    
    func addLike(for feedItem: FeedItem,
                 success: @escaping (FeedItem) -> Void,
                 failure: @escaping FailureClosure)
    
    func deleteLike(from feedItem: FeedItem,
                    success: @escaping (FeedItem) -> Void,
                    failure: @escaping FailureClosure)
    
    func add(feedLikeUpdateHandler: @escaping (_ feedItem: FeedItem) -> Void)
}

extension IFeedManager {
    func getFeed(count: Int,
                 startFrom: String? = nil,
                 success: @escaping ([FeedItem], String) -> Void,
                 failure: @escaping FailureClosure) {
        getFeed(count: count,
                startFrom: nil,
                success: success,
                failure: failure)
    }
}
<file_sep>//
//  PresentationContainer.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

protocol PresentationContainerProtocol {
}

class PresentationContainer: PresentationContainerProtocol {
}
<file_sep>//
//  DaoContainer.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

protocol DaoContainerProtocol {
    var sessionDao: ISessionDao { get }
}

class DaoContainer {
    
    private let serviceContainer = ServiceContainer()
    
    init() {
        let sessionDao = SessionDao()
        
        serviceContainer.add(services: sessionDao)
    }
}

extension DaoContainer: DaoContainerProtocol {
    var sessionDao: ISessionDao {
        return serviceContainer.get(service: SessionDao.self)!
    }
}
<file_sep>//
//  WikiPageDto.swift
//  VKDemo
//
//  Created by <NAME> on 12/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct WikiPageDto: Codable {
    let id: Int
    let groupId: Int
    let creatorId: Int
    let title: String
    let currentUserCanEdit: BoolInt
    let currentUserCanEditAccess: BoolInt
    let readRights: WikiPageAccessRights
    let editRights: WikiPageAccessRights
    let edited: Int
    let created: Int
    let editorId: Int
    let viewsCount: Int
    let parent: String?
    let parent2: String?
    let source: String?
    let html: String?
    let viewUrlString: String
    
    enum WikiPageAccessRights: Int, Codable {
        case groupManagersOnly = 0
        case groupMembersOnly = 1
        case everybody = 2
    }
    
    private enum CodingKeys: String, CodingKey {
        case id
        case groupId = "group_id"
        case creatorId = "creator_id"
        case title
        case currentUserCanEdit = "current_user_can_edit"
        case currentUserCanEditAccess = "current_user_can_edit_access"
        case readRights = "who_can_view"
        case editRights = "who_can_edit"
        case edited
        case created
        case editorId = "editor_id"
        case viewsCount = "views"
        case parent
        case parent2
        case source
        case html
        case viewUrlString = "view_url"
    }
}
<file_sep>//
//  RepliesButton.swift
//  VKDemo
//
//  Created by <NAME> on 20/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit
import QuartzCore

@IBDesignable
class RepostsButton: UIControl {
    
    private let plashkaFillColor = UIColor.white
    private let fillColor = UIColor.clear
    private let strokeColor = UIColor(white: 0.7, alpha: 1.0)
    private let borderStrokeColor = #colorLiteral(red: 0.6666666667, green: 0.6666666667, blue: 0.6666666667, alpha: 0.5)
    
    private lazy var repliesIconLayer: CAShapeLayer = {
        let repliesIconPath = UIBezierPath()
        repliesIconPath.move(to: CGPoint(x: 15.88, y: 17.62))
        repliesIconPath.addCurve(to: CGPoint(x: 11.5, y: 22), controlPoint1: CGPoint(x: 13.46, y: 17.62), controlPoint2: CGPoint(x: 11.5, y: 19.58))
        repliesIconPath.addCurve(to: CGPoint(x: 15.88, y: 26.38), controlPoint1: CGPoint(x: 11.5, y: 24.42), controlPoint2: CGPoint(x: 13.46, y: 26.38))
        repliesIconPath.addCurve(to: CGPoint(x: 20.25, y: 22), controlPoint1: CGPoint(x: 18.29, y: 26.38), controlPoint2: CGPoint(x: 20.25, y: 24.42))
        repliesIconPath.addCurve(to: CGPoint(x: 15.88, y: 17.62), controlPoint1: CGPoint(x: 20.25, y: 19.58), controlPoint2: CGPoint(x: 18.29, y: 17.62))
        repliesIconPath.close()
        repliesIconPath.move(to: CGPoint(x: 21.63, y: 19.92))
        repliesIconPath.addLine(to: CGPoint(x: 26.43, y: 17.36))
        repliesIconPath.addCurve(to: CGPoint(x: 29, y: 18.5), controlPoint1: CGPoint(x: 27.07, y: 18.06), controlPoint2: CGPoint(x: 27.98, y: 18.5))
        repliesIconPath.addCurve(to: CGPoint(x: 32.5, y: 15), controlPoint1: CGPoint(x: 30.93, y: 18.5), controlPoint2: CGPoint(x: 32.5, y: 16.93))
        repliesIconPath.addCurve(to: CGPoint(x: 29, y: 11.5), controlPoint1: CGPoint(x: 32.5, y: 13.07), controlPoint2: CGPoint(x: 30.93, y: 11.5))
        repliesIconPath.addCurve(to: CGPoint(x: 25.5, y: 15), controlPoint1: CGPoint(x: 27.07, y: 11.5), controlPoint2: CGPoint(x: 25.5, y: 13.07))
        repliesIconPath.addCurve(to: CGPoint(x: 25.61, y: 15.82), controlPoint1: CGPoint(x: 25.5, y: 15.28), controlPoint2: CGPoint(x: 25.54, y: 15.55))
        repliesIconPath.addLine(to: CGPoint(x: 20.8, y: 18.38))
        repliesIconPath.addCurve(to: CGPoint(x: 21.63, y: 19.92), controlPoint1: CGPoint(x: 21.15, y: 18.85), controlPoint2: CGPoint(x: 21.43, y: 19.37))
        repliesIconPath.close()
        repliesIconPath.move(to: CGPoint(x: 29, y: 25.5))
        repliesIconPath.addCurve(to: CGPoint(x: 26.43, y: 26.64), controlPoint1: CGPoint(x: 27.98, y: 25.5), controlPoint2: CGPoint(x: 27.07, y: 25.94))
        repliesIconPath.addLine(to: CGPoint(x: 21.63, y: 24.08))
        repliesIconPath.addCurve(to: CGPoint(x: 20.8, y: 25.62), controlPoint1: CGPoint(x: 21.43, y: 24.63), controlPoint2: CGPoint(x: 21.15, y: 25.15))
        repliesIconPath.addLine(to: CGPoint(x: 25.61, y: 28.18))
        repliesIconPath.addCurve(to: CGPoint(x: 25.5, y: 29), controlPoint1: CGPoint(x: 25.54, y: 28.45), controlPoint2: CGPoint(x: 25.5, y: 28.72))
        repliesIconPath.addCurve(to: CGPoint(x: 29, y: 32.5), controlPoint1: CGPoint(x: 25.5, y: 30.93), controlPoint2: CGPoint(x: 27.07, y: 32.5))
        repliesIconPath.addCurve(to: CGPoint(x: 32.5, y: 29), controlPoint1: CGPoint(x: 30.93, y: 32.5), controlPoint2: CGPoint(x: 32.5, y: 30.93))
        repliesIconPath.addCurve(to: CGPoint(x: 29, y: 25.5), controlPoint1: CGPoint(x: 32.5, y: 27.07), controlPoint2: CGPoint(x: 30.93, y: 25.5))
        repliesIconPath.close()
        strokeColor.setStroke()
        repliesIconPath.stroke()

        let layer = CAShapeLayer()
        layer.path = repliesIconPath.cgPath
        layer.fillColor = fillColor.cgColor
        layer.strokeColor = strokeColor.cgColor
        layer.lineWidth = 1.0
        
        return layer
    }()
    
    private lazy var borderLayer: CAShapeLayer = {
        let layer = CAShapeLayer()
        let rectanglePath = UIBezierPath(roundedRect: CGRect(x: 1, y: 1, width: 93, height: 42), cornerRadius: 21)
        fillColor.setStroke()
        rectanglePath.lineWidth = 0.5
        rectanglePath.lineCapStyle = .round
        rectanglePath.lineJoinStyle = .round
        rectanglePath.stroke()

        layer.path = rectanglePath.cgPath
        layer.strokeColor = borderStrokeColor.cgColor
        layer.fillColor = plashkaFillColor.cgColor
        layer.lineWidth = 0.5
        
        return layer
    }()
    
    private lazy var countLabel: UILabel = {
        let label = UILabel()
        label.textColor = UIColor(white: 0.3, alpha: 1.0)
        label.text = "123"
        label.frame = CGRect(x: 42, y: 11, width: 41, height: 21)
        label.font = UIFont.systemFont(ofSize: 13.0)
        
        return label
    }()
    
    private func addLayers() {
        [borderLayer, repliesIconLayer].forEach { layer.addSublayer($0) }
        addSubview(countLabel)
    }
    
    private func removeLayersIfNeeded() {
        layer.sublayers?.forEach { $0.removeFromSuperlayer() }
    }
    
    private func configureLayers() {
        removeLayersIfNeeded()
        addLayers()
        
        for subLayer in [
          borderLayer,
          repliesIconLayer
        ] {
            subLayer.frame = bounds
            subLayer.setNeedsDisplay()
        }
    }
        
    override init(frame: CGRect) {
        super.init(frame: frame)
        addLayers()
        backgroundColor = .clear
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        addLayers()
        backgroundColor = .clear
    }
    
    public func setup(repliesCount: Int) {
        countLabel.text = "\(repliesCount)"
    }
}
<file_sep>platform :ios, '12.0'
supports_swift_versions '>= 4.2'

use_frameworks!

source 'https://cdn.cocoapods.org/'

target 'VKDemo' do
  pod 'RxSwift'
  pod 'RxCocoa'
  pod 'RxAlamofire'
  pod 'Moya'
  pod 'Reachability'
  pod 'Moya/RxSwift'
  pod 'RxWebKit'
  pod 'SkeletonView'
end
<file_sep>import Foundation
import UIKit
import QuartzCore


protocol LikeButtonDelegate: class {
    func didLike(likeButton: LikeButton?)
    func didUnlike(likeButton: LikeButton?)
}

enum LikeState {
    case liked
    case notLiked
    case inProgress
}

@IBDesignable
class LikeButton: UIControl {
    
    private weak var delegate: LikeButtonDelegate?
    private var likeState: LikeState = LikeState.notLiked
    private var isIdle: Bool {
        get {
            return (likeState == .inProgress) ? false : true
        }
    }
    
    private let fillColor = #colorLiteral(red: 0.6666666667, green: 0.6666666667, blue: 0.6666666667, alpha: 0.5)
    private let heartStrokeColor = UIColor(white: 0.7, alpha: 1.0)
    private let borderStrokeColor = #colorLiteral(red: 0.6666666667, green: 0.6666666667, blue: 0.6666666667, alpha: 0.5)
    private let likedFillColor = #colorLiteral(red: 0.9960784314, green: 0.4901960784, blue: 0.6980392157, alpha: 1)
    private let notLikedFillColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
    
    private func showInProgress(_ show: Bool = true) {
        if show {
            inProgressLayer.isHidden = false
            let pulseAnimation = CASpringAnimation(keyPath: "transform.scale")
            pulseAnimation.duration = 0.45
            pulseAnimation.fromValue = 1.0
            pulseAnimation.toValue = 1.15
            pulseAnimation.autoreverses = true
            pulseAnimation.repeatCount = 1
            pulseAnimation.initialVelocity = 0.5
            pulseAnimation.damping = 0.4

            let animationGroup = CAAnimationGroup()
            animationGroup.duration = 1.1
            animationGroup.repeatCount = .greatestFiniteMagnitude
            animationGroup.animations = [pulseAnimation]

            inProgressLayer.add(animationGroup, forKey: "pulse")
        } else {
            inProgressLayer.isHidden = true
            inProgressLayer.removeAnimation(forKey: "pulse")
        }
    }

    private func animateTo(_ state: LikeState) {
        switch state {
        case .inProgress:
            showInProgress(true)
            break
        case .liked:
            showInProgress(false)
            
            let fromPath =
                UIBezierPath(ovalIn: CGRect(x: 14.25, y: 13.25, width: 13, height: 13))
            let toPath = UIBezierPath(cgPath: Utils.pathForCircleThatContains(rect: self.frame))
            let animation = CABasicAnimation(keyPath: "path")
            activeBackgroundLayer.mask = borderLayer2
            animation.fromValue = fromPath.cgPath
            animation.toValue = toPath.cgPath
            animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
            animation.duration = 0.6
            
            activeBackgroundLayer.add(animation, forKey: "pathAnimation")
            
            activeBackgroundLayer.path = toPath.cgPath
            borderLayer.fillColor = UIColor.clear.cgColor
            borderLayer.setNeedsLayout()
            break
        case .notLiked:
            showInProgress(false)
            
            let fromPath = UIBezierPath(cgPath: Utils.pathForCircleThatContains(rect: self.frame))
            let toPath =
                UIBezierPath(ovalIn: CGRect(x: 14.25, y: 13.25, width: 13, height: 13))
            let animation = CABasicAnimation(keyPath: "path")
            animation.fromValue = fromPath
            animation.toValue = toPath
            animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
            animation.duration = 0.6
            
            activeBackgroundLayer.add(animation, forKey: "pathAnimation")
            activeBackgroundLayer.path = toPath.cgPath
            borderLayer.fillColor = UIColor.clear.cgColor
            borderLayer.setNeedsLayout()
            break
        }
    }
    
    private func goTo(_ state: LikeState) {
        switch state {
        case .inProgress:
            showInProgress(true)
            break
        case .liked:
            showInProgress(false)
            
            let activeBackgroundPath = UIBezierPath(cgPath: Utils.pathForCircleThatContains(rect: self.frame))
            activeBackgroundLayer.mask = borderLayer2
            activeBackgroundLayer.path = activeBackgroundPath.cgPath
            borderLayer.fillColor = UIColor.clear.cgColor
            borderLayer.setNeedsLayout()
            break
        case .notLiked:
            showInProgress(false)
            
            let activeBackgroundPath = UIBezierPath(ovalIn: CGRect(x: 14.25, y: 13.25, width: 13, height: 13))
            activeBackgroundLayer.path = activeBackgroundPath.cgPath
            borderLayer.fillColor = UIColor.clear.cgColor
            borderLayer.setNeedsLayout()
            break
        }
    }


    private lazy var heartLayer: CAShapeLayer = {
        let heartPath = UIBezierPath()
        heartPath.move(to: CGPoint(x: 21.5, y: 32))
        heartPath.addLine(to: CGPoint(x: 20.03, y: 30.49))
        heartPath.addCurve(to: CGPoint(x: 11, y: 17.3), controlPoint1: CGPoint(x: 14.57, y: 25.21), controlPoint2: CGPoint(x: 11, y: 21.66))
        heartPath.addCurve(to: CGPoint(x: 16.77, y: 11), controlPoint1: CGPoint(x: 11, y: 13.75), controlPoint2: CGPoint(x: 13.52, y: 11))
        heartPath.addCurve(to: CGPoint(x: 21.5, y: 13.41), controlPoint1: CGPoint(x: 18.56, y: 11), controlPoint2: CGPoint(x: 20.34, y: 11.92))
        heartPath.addCurve(to: CGPoint(x: 26.22, y: 11), controlPoint1: CGPoint(x: 22.66, y: 11.92), controlPoint2: CGPoint(x: 24.44, y: 11))
        heartPath.addCurve(to: CGPoint(x: 32, y: 17.3), controlPoint1: CGPoint(x: 29.48, y: 11), controlPoint2: CGPoint(x: 32, y: 13.75))
        heartPath.addCurve(to: CGPoint(x: 22.97, y: 30.49), controlPoint1: CGPoint(x: 32, y: 21.66), controlPoint2: CGPoint(x: 28.43, y: 25.21))
        heartPath.addLine(to: CGPoint(x: 21.5, y: 32))
        heartPath.close()
        heartPath.lineCapStyle = .round

        let layer = CAShapeLayer()
        layer.path = heartPath.cgPath
        layer.fillColor = UIColor.white.cgColor
        layer.strokeColor = heartStrokeColor.cgColor
        layer.lineWidth = 1

        return layer
    }()
    
    private lazy var borderLayer: CAShapeLayer = {
        let layer = CAShapeLayer()
        let rectanglePath = UIBezierPath(roundedRect: CGRect(x: 1, y: 1, width: 93, height: 42), cornerRadius: 21)
        rectanglePath.lineCapStyle = .round
        rectanglePath.lineJoinStyle = .round
        UIColor.clear.setFill()
        rectanglePath.fill()

        layer.path = rectanglePath.cgPath
        layer.strokeColor = borderStrokeColor.cgColor
        layer.fillColor = UIColor.white.cgColor
        layer.lineWidth = 0.5
        
        layer.backgroundColor  = UIColor.clear.cgColor
        
        return layer
    }()
    
    private lazy var borderLayer2: CAShapeLayer = {
        let layer = CAShapeLayer()
        let rectanglePath = UIBezierPath(roundedRect: CGRect(x: 1.5, y: 1.5, width: 92, height: 41), cornerRadius: 20.5)
        rectanglePath.lineCapStyle = .round
        rectanglePath.lineJoinStyle = .round
        UIColor.clear.setFill()
        rectanglePath.fill()

        layer.path = rectanglePath.cgPath
        layer.strokeColor = borderStrokeColor.cgColor
        layer.fillColor = UIColor.white.cgColor
        layer.lineWidth = 0
        
        layer.backgroundColor  = UIColor.clear.cgColor
        
        return layer
    }()
    
    private lazy var inProgressLayer: CALayer = {
        let inProgressLayer = CALayer()
        inProgressLayer.backgroundColor = likedFillColor.cgColor
        inProgressLayer.frame = CGRect(x: 0.0, y: 0.0, width: 40.0, height: 40.0)
                
        let outerHeartLayer = CAShapeLayer()
        let outerHeartPath = UIBezierPath()
                
        outerHeartPath.move(to: CGPoint(x: 21.5, y: 33))
        outerHeartPath.addLine(to: CGPoint(x: 19.89, y: 31.34))
        outerHeartPath.addCurve(to: CGPoint(x: 10, y: 16.91), controlPoint1: CGPoint(x: 13.91, y: 25.57), controlPoint2: CGPoint(x: 10, y: 21.68))
        outerHeartPath.addCurve(to: CGPoint(x: 16.32, y: 10), controlPoint1: CGPoint(x: 10, y: 13.01), controlPoint2: CGPoint(x: 12.76, y: 10))
        outerHeartPath.addCurve(to: CGPoint(x: 21.5, y: 12.64), controlPoint1: CGPoint(x: 18.28, y: 10), controlPoint2: CGPoint(x: 20.23, y: 11))
        outerHeartPath.addCurve(to: CGPoint(x: 26.67, y: 10), controlPoint1: CGPoint(x: 22.77, y: 11), controlPoint2: CGPoint(x: 24.72, y: 10))
        outerHeartPath.addCurve(to: CGPoint(x: 33, y: 16.91), controlPoint1: CGPoint(x: 30.24, y: 10), controlPoint2: CGPoint(x: 33, y: 13.01))
        outerHeartPath.addCurve(to: CGPoint(x: 23.11, y: 31.34), controlPoint1: CGPoint(x: 33, y: 21.68), controlPoint2: CGPoint(x: 29.09, y: 25.57))
        outerHeartPath.addLine(to: CGPoint(x: 21.5, y: 33))
        
        outerHeartPath.close()
        outerHeartPath.lineWidth = 0.1
        outerHeartPath.lineCapStyle = .round
        
        outerHeartLayer.path = outerHeartPath.cgPath
        inProgressLayer.mask = outerHeartLayer
        inProgressLayer.isHidden = true
        
        return inProgressLayer
    }()
    
    private lazy var activeBackgroundLayer: CAShapeLayer = {
        let layer = CAShapeLayer()
        
        layer.path = UIBezierPath(ovalIn: CGRect(x: 14.25, y: 13.25, width: 13, height: 13)).cgPath
        layer.fillColor = likedFillColor.cgColor
        layer.strokeColor = likedFillColor.cgColor
        layer.backgroundColor = likedFillColor.cgColor

        return layer
    }()
    
    private lazy var countLabel: UILabel = {
        let label = UILabel()
        label.textColor = UIColor(white: 0.3, alpha: 1.0)
        label.frame = CGRect(x: 42, y: 11, width: 41, height: 21)
        label.font = UIFont.systemFont(ofSize: 13.0)
        
        return label
    }()
    
    private func addLayers() {
        [activeBackgroundLayer,
         borderLayer,
         borderLayer2,
         inProgressLayer,
         heartLayer].forEach { layer.addSublayer($0) }
        addSubview(countLabel)
    }
    
    private func removeLayersIfNeeded() {
        layer.sublayers?.forEach { $0.removeFromSuperlayer() }
    }
    
    private func configureLayers() {
        removeLayersIfNeeded()
        borderLayer.backgroundColor = UIColor.clear.cgColor
        addLayers()
    }
        
    override init(frame: CGRect) {
        super.init(frame: frame)
        addLayers()
        backgroundColor = .clear
        
        addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        addLayers()
        backgroundColor = .clear
        
        addTarget(self,
                  action: #selector(buttonPressed),
                  for: .touchUpInside)
    }
    
    func setup(likesCount: Int,
               state: LikeState,
               animated: Bool,
               delegate: LikeButtonDelegate? = nil) {
        self.likeState = state
        self.delegate = delegate
        countLabel.text = "\(likesCount)"
        
        if animated {
            animateTo(state)
        } else {
            goTo(state)
        }
    }
    
    private func getNextStateOnButtonPressed() -> LikeState {
        switch likeState {
        case .inProgress:
            return .inProgress
        case .liked:
            return .notLiked
        case .notLiked:
            return .inProgress
        }
    }

    @objc private func buttonPressed() {
        guard isIdle else { return }
                
        let nextState = getNextStateOnButtonPressed()
        animateTo(nextState)
        
        switch nextState {
        case .liked:
            break
        case .notLiked:
            delegate?.didUnlike(likeButton: self)
        case .inProgress:
            delegate?.didLike(likeButton: self)
            break
        }
    }
}
<file_sep>//
//  FeedViewInput.swift
//  VKDemo
//
//  Created by AntonKrylovBrightBox on 10/06/2019.
//  Copyright © 2019 BrightBox. All rights reserved.
//

protocol FeedViewInput: AnyObject {
    func setup(feedItems: [FeedItem])
    func addNewFeedItems(feedItems: [FeedItem])
    func updateFeedAfterLike(feedItem: FeedItem)
}
<file_sep>//
//  BaseCoordinator.swift
//  VKDemo
//
//  Created by <NAME> on 24/04/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

public enum RequestError: Error {
    case error(code: HTTPStatusCode)
    case mappingError
    case badResponse
    case unauthorized
    case noInternetConnection
    case other(error: CustomRequestError)
    
    case vkError(error: VkError)
}

public struct ErrorDto: Codable {
    let error: VkError
}

public struct VkError: Codable {
    let errorCode: Int
    let errorMsg: String
    
    private enum CodingKeys: String, CodingKey {
        case errorCode = "error_code"
        case errorMsg = "error_msg"
    }
}

public protocol CustomRequestError {
    var title: String { get }
    var description: String? { get }
}

public struct TextRequestError: CustomRequestError {
    public let title: String
    public let description: String?

    public init(title: String, description: String? = nil) {
        self.title = title
        self.description = description
    }
}
<file_sep>//
//  UIView+Constraints.swift
//  VKDemo
//
//  Created by <NAME> on 25/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import UIKit

extension UIView {
    func addSubviewOnBorders(_ subview: UIView) {
        self.addSubview(subview)
        subview.translatesAutoresizingMaskIntoConstraints = false

        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[subview]|", options: [], metrics: nil, views: ["subview": subview]))
        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[subview]|", options: [], metrics: nil, views: ["subview": subview]))
    }

    func addSubviewOnCenter(_ subview: UIView) {
        self.addSubview(subview)
        subview.translatesAutoresizingMaskIntoConstraints = false

        self.addConstraints([NSLayoutConstraint(item: subview, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)])
        self.addConstraints([NSLayoutConstraint(item: subview, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)])
    }
}
<file_sep>//
//  ChatViewController.swift
//  VKDemo
//
//  Created by AntonKrylovBrightBox on 10/06/2019.
//  Copyright © 2019 BrightBox. All rights reserved.
//

import UIKit

class ChatViewController: BaseViewController, ViperModuleTransitionHandler {
    var presenter: ChatViewOutput?

    override func viewDidLoad() {
        super.viewDidLoad()

        presenter?.viewLoaded()
    }
}

extension ChatViewController: ChatViewInput {
    func setup() {

    }
}
<file_sep>//
//  AuthViewModel.swift
//  VKDemo
//
//  Created by <NAME> on 13/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import RxSwift

class AuthViewModel {
    
    // Input
    let showSignInScreen: AnyObserver<Void>
    
    // Output
    let authorize: Observable<Void>
    
    init() {
        let _showSignInScreen = PublishSubject<Void>()
        self.showSignInScreen = _showSignInScreen.asObserver()
        self.authorize = _showSignInScreen.asObservable()
    }
}
<file_sep>//
//  UIBezierPath+MyPaths.swift
//  VKDemo
//
//  Created by <NAME> on 15/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit

private let fillColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
private let blueMain = UIColor(red: 0.294, green: 0.471, blue: 0.706, alpha: 1.000)

//extension UIBezierPath {
//    static func heartPath(frame: CGRect, fillColor: UIColor = fillColor, strokeColor: UIColor = blueMain) -> UIBezierPath {
//        /*
//        let bezierPath = UIBezierPath()
//        bezierPath.move(to: CGPoint(x: frame.minX + 0.50000 * frame.width, y: frame.minY + 0.98298 * frame.height))
//        bezierPath.addLine(to: CGPoint(x: frame.minX + 0.43192 * frame.width, y: frame.minY + 0.90900 * frame.height))
//        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.01373 * frame.width, y: frame.minY + 0.30128 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.17906 * frame.width, y: frame.minY + 0.66591 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.01373 * frame.width, y: frame.minY + 0.50209 * frame.height))
//        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.28118 * frame.width, y: frame.minY + 0.01064 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.01373 * frame.width, y: frame.minY + 0.13747 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.13043 * frame.width, y: frame.minY + 0.01064 * frame.height))
//        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.50000 * frame.width, y: frame.minY + 0.12161 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.36384 * frame.width, y: frame.minY + 0.01064 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.44651 * frame.width, y: frame.minY + 0.05291 * frame.height))
//        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.71882 * frame.width, y: frame.minY + 0.01064 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.55349 * frame.width, y: frame.minY + 0.05291 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.63616 * frame.width, y: frame.minY + 0.01064 * frame.height))
//        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.98627 * frame.width, y: frame.minY + 0.30128 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.86957 * frame.width, y: frame.minY + 0.01064 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.98627 * frame.width, y: frame.minY + 0.13747 * frame.height))
//        bezierPath.addCurve(to: CGPoint(x: frame.minX + 0.56808 * frame.width, y: frame.minY + 0.90900 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.98627 * frame.width, y: frame.minY + 0.50209 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.82094 * frame.width, y: frame.minY + 0.66591 * frame.height))
//        bezierPath.addLine(to: CGPoint(x: frame.minX + 0.50000 * frame.width, y: frame.minY + 0.98298 * frame.height))
//
//        bezierPath.close()
//        fillColor.setFill()
//        bezierPath.fill()
//        strokeColor.setStroke()
//        bezierPath.lineWidth = 8
//        bezierPath.stroke()
//        */
//        
//    }
//}
<file_sep>//
//  UIView+Init.swift
//  VKDemo
//
//  Created by <NAME> on 05.11.2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import UIKit

extension UIView {
    static func loadFromXib() -> UIView? {
        let bundle = Bundle(for: self)
        let nib = UINib(nibName: String(describing: self), bundle: bundle)
        let view = nib.instantiate(withOwner: self, options: nil).first as? UIView

        return view
    }
}
<file_sep>//
//  NoteDto.swift
//  VKDemo
//
//  Created by <NAME> on 12/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct NoteDto: Codable {
    let id: Int
    let ownerId: Int
    let title: String
    let text: String
    let date: Int
    let commentsCount: Int
    let readComments: Int?
    let viewUrlString: String
    
    private enum CodingKeys: String, CodingKey {
        case id
        case ownerId = "owner_id"
        case title
        case text
        case date
        case commentsCount = "comments"
        case readComments = "read_comments"
        case viewUrlString = "view_url"
    }
}
<file_sep>//
//  WelcomeCoordinator.swift
//  VKDemo
//
//  Created by <NAME> on 08/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import UIKit
import RxSwift
import RxCocoa
import RxWebKit
import WebKit
import SafariServices

class WelcomeCoordinator: BaseCoordinator<Void> {
    private let window: UIWindow
    
    private let _authorizedSubject: PublishSubject<Void>
    private let authorizedObservable: Observable<Void>
    
    init(window: UIWindow) {
        self.window = window
        
        _authorizedSubject = PublishSubject<Void>()
        authorizedObservable = _authorizedSubject.asObservable()
        
        super.init()
    }
    
    override func start() -> Observable<Void> {
        
        let authVc = AuthViewController.loadFromXib()
        let authVm = AuthViewModel()
        let navigationController = UINavigationController(rootViewController: authVc)
        authVc.viewModel = authVm
        
        window.rootViewController = navigationController
        window.makeKeyAndVisible()
        
        authVm.authorize
            .subscribe(onNext:{ [weak self] in
                guard let `self` = self,
                    let rootVC = self.window.rootViewController else { return }
                
                _ = self.showSignInVC(on: rootVC)
                .filter({ signInResult -> Bool in
                    return signInResult == .success
                })
                .subscribe({ _ in
                    self._authorizedSubject.onNext(Void())
                })
                
            })
            .disposed(by: disposeBag)
        
        return authorizedObservable
    }
    
    private func showSignInVC(on rootViewController: UIViewController) -> Observable<SignInCoordinationResult> {
        let signInCoordinator = SignInCoordinator(rootViewController: rootViewController)
        
        return coordinate(to: signInCoordinator)
    }
}
<file_sep>//
//  PollDto.swift
//  VKDemo
//
//  Created by <NAME> on 12/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct PollDto: Codable {
    let id: Int
    let ownerId: Int
    let created: Int
    let question: String
    let votesCount: Int
    let answers: [PossibleAnswer]
    let isAnonymous: Bool
    let isMultiple: Bool
    let answerIds: [Int]
    let endDate: Int
    let isClosed: Bool
    let isBoard: Bool
    let canEdit: Bool
    let canVote: Bool
    let canReport: Bool
    let canShare: Bool
    let authorId: Int
    let photo: PhotoDto?
    let background: PollBackground?
    let friends: [FriendIdDto]?
    
    private enum CodingKeys: String, CodingKey {
        case id
        case ownerId = "owner_id"
        case created
        case question
        case votesCount = "votes"
        case answers
        case isAnonymous = "anonymous"
        case isMultiple = "multiple"
        case answerIds = "answer_ids"
        case endDate = "end_date"
        case isClosed = "closed"
        case isBoard = "is_board"
        case canEdit = "can_edit"
        case canVote = "can_vote"
        case canReport = "can_report"
        case canShare = "can_share"
        case authorId = "author_id"
        case photo
        case background
        case friends
    }
}

struct PossibleAnswer: Codable {
    let id: Int
    let text: String
    let votes: Int
    let rate: Double
}

struct FriendIdDto: Codable {
    let id: Int
}

struct PollBackground: Codable {
    let id: Int
    let type: PollBackgroundType
    let angle: Int?
    let color: String
    let width: Int?
    let height: Int?
    let images: [PhotoDto]?
    let points: [PollBackgroundPoint]?
    
    enum PollBackgroundType: String, Codable {
        case gradient = "gradient"
        case tile = "tile"
    }
    
    struct PollBackgroundPoint: Codable {
        let position: Double
        let color: String
    }
}
<file_sep>//
//  ChatRouterInput.swift
//  VKDemo
//
//  Created by AntonKrylovBrightBox on 10/06/2019.
//  Copyright © 2019 BrightBox. All rights reserved.
//

import Foundation

protocol ChatRouterInput {

}
<file_sep>//
//  AppCoordinator.swift
//  VKDemo
//
//  Created by <NAME> on 24/04/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import UIKit
import RxSwift

protocol IAppCoordinator {
    func start() -> Observable<Void>
}

class AppCoordinator: BaseCoordinator<Void>, IAppCoordinator {
    
    private var window: UIWindow {
        guard let window = (UIApplication.shared.delegate as? AppDelegate)?.window else {
            fatalError("No UIWindow found!")
        }
        return window
    }
    
    private lazy var mainCoordinator = DI.container.coordinator.mainTabBarCoordinator
    
    override func start() -> Observable<Void> {
        // Если нет пользака
        let welcomeCoordinator = WelcomeCoordinator(window: window)
        let observable = coordinate(to: welcomeCoordinator)
        
//        Вернуть когда понадобится авторизация
//        _ = observable.subscribe({ event in
//            let mainCoordinator = MainTabBarCoordinator()
//            mainCoordinator.start()
//        })

        
//      Вернуть когда НЕ НУЖНА авторизация
        let mainCoordinator = MainTabBarCoordinator()
        mainCoordinator.start()

        
        return .never()
        
        // Если есть пользак, сразу переход в MainTabBarCoordinator,
        // который не реактивный
    }
}
<file_sep>//
//  PlayerView.swift
//  VKDemo
//
//  Created by <NAME> on 25/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import UIKit
import AVKit
import AVFoundation

protocol PlayerViewDelegate: class {
    func videoTapped(videoUrl: String, formTime: CMTime?)
}

class PlayerView: UIView {
    private var playerLayer: AVPlayerLayer?
    private var url: String?
    private weak var delegate: PlayerViewDelegate?
    private var statusObservation: NSKeyValueObservation?
    private let loadingView = UIView()
    private let activityIndicator = UIActivityIndicatorView(frame: CGRect.zero)

    override func awakeFromNib() {
        super.awakeFromNib()

        self.backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)

        let player = AVPlayer()
        player.isMuted = true

        if #available(iOS 12.0, *) {
            player.preventsDisplaySleepDuringVideoPlayback = false
        }
        
        let playerLayer = AVPlayerLayer(player: player)
        self.playerLayer = playerLayer
        self.layer.addSublayer(playerLayer)

        playerLayer.frame = self.bounds

        let videoButton = UIButton()

        videoButton.addTarget(self, action: #selector(videoButtonTapped), for: .touchUpInside)
        self.addSubviewOnBorders(videoButton)

        loadingView.addSubviewOnCenter(activityIndicator)

        activityIndicator.startAnimating()

        self.addSubviewOnBorders(loadingView)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer?.frame = self.bounds
    }

    func playVideo(urlString: String, delegate: PlayerViewDelegate) {
        if playerLayer?.player?.currentItem != nil,
            url == urlString {
            playerLayer?.player?.play()
            return
        }

        guard let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed),
            let videoURL = URL(string: encodedUrlString) else {
            return
        }

        self.url = urlString
        self.delegate = delegate

        let videoPlayerItem = AVPlayerItem(url: videoURL)

        NotificationCenter.default.removeObserver(self)
        NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
                                               object: videoPlayerItem,
                                               queue: .main) { [weak self] _ in
                                                self?.playerLayer?.player?.seek(to: kCMTimeZero)
                                                self?.playerLayer?.player?.play()
        }

        playerLayer?.player?.replaceCurrentItem(with: videoPlayerItem)
        playerLayer?.player?.seek(to: kCMTimeZero)

        playerLayer?.player?.play()
        showLoading()

        statusObservation?.invalidate()
        statusObservation = videoPlayerItem.observe(\.status, options: [.new]) { [weak self] (_, _) in
            if videoPlayerItem.status == .readyToPlay {
                self?.loadingView.isHidden = true
            }
        }
    }

    func showLoading() {
        loadingView.isHidden = false
        activityIndicator.startAnimating()
    }

    func stopPlaying() {
        playerLayer?.player?.pause()
    }

    @objc func videoButtonTapped() {
        guard let url = url else {
            return
        }

        delegate?.videoTapped(videoUrl: url, formTime: playerLayer?.player?.currentTime())
    }

    deinit {
        playerLayer?.player?.replaceCurrentItem(with: nil)
        playerLayer?.removeFromSuperlayer()
        statusObservation?.invalidate()
        statusObservation = nil
        NotificationCenter.default.removeObserver(self)
    }
}
<file_sep>//
//  SessionDao.swift
//  VKDemo
//
//  Created by <NAME> on 31/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

protocol ISessionDao {
    
    func saveAccessToken(_ accessToken: String?)
    func getAccessToken() -> String?
}

class SessionDao {
    
    private lazy var userDefaults = UserDefaults.standard
    private lazy var accessTokenKey = "accessToken"
}

extension SessionDao: ISessionDao {
    
    func saveAccessToken(_ accessToken: String?) {
        userDefaults.set(accessToken, forKey: accessTokenKey)
    }
    
    func getAccessToken() -> String? {
        return userDefaults.object(forKey: accessTokenKey) as? String
    }
}
<file_sep>//
//  FeedManager.swift
//  VKDemo
//
//  Created by <NAME> on 05/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import Moya

class FeedManager {
    private let feedProvider: MoyaProvider<FeedTarget>
    private let feedParser: IFeedParser
    
    private let onFeedLikeUpdate = Events<FeedItem>()
    
    init(feedProvider: MoyaProvider<FeedTarget>,
         feedParser: IFeedParser) {
        self.feedProvider = feedProvider
        self.feedParser = feedParser
    }
}


extension FeedManager: IFeedManager {
    func addLike(for feedItem: FeedItem, success: @escaping (FeedItem) -> Void, failure: @escaping FailureClosure) {
        _ = feedProvider.request(.addLike(feedItem: feedItem), success: { [weak self, feedItem] (likesCountResponse: LikesCountResponse) in
            var feedItem = feedItem
            feedItem.item.likes?.count = likesCountResponse.likesCount
            feedItem.item.likes?.userLikes = BoolInt.true
            self?.onFeedLikeUpdate.raise(feedItem)
            success(feedItem)
        }, failure: { error in
            failure(error)
        })
    }
    
    func deleteLike(from feedItem: FeedItem,
                         success: @escaping (FeedItem) -> Void,
                         failure: @escaping FailureClosure) {
        _ = feedProvider.request(.deleteLike(feedItem: feedItem), success: { [weak self, feedItem] (likesCountResponse: LikesCountResponse) in
            var feedItem = feedItem
            feedItem.item.likes?.count = likesCountResponse.likesCount
            feedItem.item.likes?.userLikes = BoolInt.false
            self?.onFeedLikeUpdate.raise(feedItem)
            success(feedItem)
        }, failure: { error in
            failure(error)
        })
    }
    
    func getFeed(count: Int,
             startFrom: String?,
               success: @escaping ([FeedItem], String) -> Void,
               failure: @escaping FailureClosure) {
        _ = feedProvider.request(.getFeed(count: count, startFrom: startFrom),
                                 success: { (feedResponse: FeedResponse) in
            let feedItems = self.feedParser.parse(feedResponse: feedResponse)
            success(feedItems, feedResponse.nextFrom)
        }, failure: { error in
            failure(error)
        })
    }
    
    func add(feedLikeUpdateHandler: @escaping (_ feedItem: FeedItem) -> Void) {
        onFeedLikeUpdate.addHandler(feedLikeUpdateHandler)
    }
}
<file_sep>//
//  Events.swift
//  VKDemo
//
//  Created by <NAME> on 08.11.2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

class Events<T> {
    typealias EventHandler = (T) -> Void

    private var eventHandlers = [EventHandler]()

    func addHandler(_ handler: @escaping EventHandler) {
        eventHandlers.append(handler)
    }

    func raise(_ data: T) {
        for handler in eventHandlers {
            handler(data)
        }
    }

    func clearHandlers() {
        eventHandlers.removeAll()
    }
}
<file_sep>//
//  FeedViewController.swift
//  VKDemo
//
//  Created by AntonKrylovBrightBox on 10/06/2019.
//  Copyright © 2019 BrightBox. All rights reserved.
//

import UIKit

class FeedViewController: BaseViewController, ViperModuleTransitionHandler {
    var presenter: FeedViewOutput?
    
    @IBOutlet var dataSource: FeedTableViewDataSource!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupAppearance()
        
        if let output = presenter as? (FeedViewOutput & SocialButtonsDelegate){
            dataSource.output = output
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
            self.presenter?.viewLoaded()
        }
    }
    
    private func setupAppearance() {
        navigationController?.navigationBar.prefersLargeTitles = true
    }
}

extension FeedViewController: FeedViewInput {
    func setup(feedItems: [FeedItem]) {
        dataSource.setup(with: feedItems)
    }
        
    func addNewFeedItems(feedItems: [FeedItem]) {
        dataSource.addNew(feedItems: feedItems)
    }
    
    func updateFeedAfterLike(feedItem: FeedItem) {
        dataSource.updateFeedItem(feedItem: feedItem)
    }
}

extension FeedViewController: UITableViewDelegate {
    
}
<file_sep>//
//  BoolInt.swift
//  VKDemo
//
//  Created by <NAME> on 12/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

enum BoolInt: Int, Codable {
    case `true` = 1
    case `false` = 0
}

extension BoolInt {
    func asBool() -> Bool {
        return self == .true
    }
}
<file_sep>//
//  ViperModuleOutput.swift
//  Vanilla
//
//  Created by <NAME> on 11/05/2017.
//  Copyright © 2017 BrightBox. All rights reserved.
//

import Foundation

public protocol ViperModuleOutputProtocol: class {

}
<file_sep>//
//  MoyaProvider+Mapping.swift
//  VKDemo
//
//  Created by <NAME> on 10/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import Moya
import Alamofire

public typealias FailureClosure = (RequestError) -> Void

extension MoyaProvider {
    open func request<T: Codable>(_ target: Target,
                                  callbackQueue: DispatchQueue? = .none,
                                  progress: ProgressBlock? = .none,
                                  success: @escaping (T) -> Void,
                                  failure: @escaping FailureClosure) -> Cancellable {
        
        return request(target, callbackQueue: callbackQueue, progress: progress) { result in
            switch result {
            case let .success(moyaResponse):
                do {
                    _ = try moyaResponse.filterSuccessfulStatusCodes()
                }
                catch _ {
                    // Неправильный статус код
                    failure(.error(code: HTTPStatusCode(rawValue: moyaResponse.statusCode) ?? .badRequest))
                }
                
                let jsonDecoder = JSONDecoder()
                jsonDecoder.dateDecodingStrategy = .secondsSince1970
                do {
                    let mappedData = try jsonDecoder.decode(MappedResponse<T>.self, from: moyaResponse.data)
                    // Все прошло хорошо, размапилось и не было VkError
                    success(mappedData.response)
                } catch let error1 {
                    do {
                        let errorMappedData = try jsonDecoder.decode(ErrorDto.self, from: moyaResponse.data)
                        // Пришел VkError
                        failure(.vkError(error: errorMappedData.error))
                        return
                    } catch {
                        // Ошибка маппинга
                        guard let decodingError = error1 as? DecodingError else {
                            failure(.mappingError)
                            return
                        }
                        
                        switch decodingError {
                        case .typeMismatch(let type, let context):
                            print(type)
                            print(context)
                            break
                        case .dataCorrupted(let context):
                            print("dataCorrupted, context:")
                            print(context)
                            break
                        case .keyNotFound(let codingKey, let context):
                            let a = context.codingPath
                            let b = context.debugDescription
                            let c = context.underlyingError
                            let d = codingKey
                            print("keyNotFound")
                            break
                        case .valueNotFound(let type, let context):
                            print("valueNotFound")
                            break
                        }
                        failure(.mappingError)
                    }
                }
            case .failure(_):
                // Сюда не должен попадать
                fatalError("FATAL ERROR!!!")
            }
        }
    }
}

// Не используется
extension MoyaProvider {
    // TODO: удалить или привести к нормальному виду!
    // Идея: нам может понадобиться response object
    open func request1<T: Codable>(_ target: Target,
                                   callbackQueue: DispatchQueue? = .none,
                                   progress: ProgressBlock? = .none,
                                   success: @escaping (RequestResponse<T>) -> Void,
                                   failure: @escaping FailureClosure) -> Cancellable {
        
        return request(target, callbackQueue: callbackQueue, progress: progress) { result in
            switch result {
            case let .success(moyaResponse):
                do {
                    let decoder = JSONDecoder()
                    let filteredResponse = try moyaResponse.filterSuccessfulStatusCodes()
                    let json = try filteredResponse.mapJSON() // type Any
                    let mappedData = try decoder.decode(T.self, from: filteredResponse.data)
                    print("JSON: \(json)")
                    print("Mapped data: \(mappedData)")
                    // Do something with your json.
                }
                catch let error {
                    // Here we get either statusCode error or jsonMapping error.
                    // TODO: handle the error == best. comment. ever.
                    print(error)
                }
            case .failure(let error):
                print("case .failure(error):")
                print(error)
            }
        }
    }
}

// TODO:
//extension Bool {
//    override public init(from decoder: Decoder) throws {
//        do {
//            let container = try decoder.singleValueContainer()
//            self = try container.decode(Bool.self)
//        } catch {
//            assertionFailure("ERROR: \(error)")
//            // TODO: automatically send a report about a corrupted data
//            self = false
//        }
//    }
//}

extension Bool {
    init(_ int: Int) {
        self = (int == 1)
    }
}
<file_sep>//
//  AudioDto.swift
//  VKDemo
//
//  Created by <NAME> on 12/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct AudioDto: Codable {
    let id: Int
    let ownerId: Int
    let artist: String
    let title: String
    let duration: Int
    let urlString: String
    let lyricsId: Int?
    let albumId: Int?
    let genreId: Int?
    let date: Int
    let noSearch: Int?
    let isHq: Bool?
    
    private enum CodingKeys: String, CodingKey {
        case id
        case ownerId = "owner_id"
        case artist
        case title
        case duration
        case urlString = "url"
        case lyricsId = "lyrics_id"
        case albumId = "album_id"
        case genreId = "genre_id"
        case date
        case noSearch = "no_search"
        case isHq = "is_hq"
    }
}
<file_sep>//
//  FeedRouter.swift
//  VKDemo
//
//  Created by AntonKrylovBrightBox on 10/06/2019.
//  Copyright © 2019 BrightBox. All rights reserved.
//

import UIKit

class FeedRouter {
    private weak var transitionHandler: ViperModuleTransitionHandler?

    func createModule() -> UIViewController {
        let vc = FeedViewController.loadFromXib()

        transitionHandler = vc

        let presenter = FeedPresenter(view: vc,
                                      router: self,
                                      feedManager: DI.container.data.feedManager)

        vc.presenter = presenter
        vc.title = "Feed"
        vc.view.backgroundColor = UIColor.orange
        
        return vc
    }

    func openModule(with transitionHandler: ViperModuleTransitionHandler, transitionStyle: TransitionStyle) {
        transitionHandler.openModule(vc: createModule(), style: transitionStyle)
    }
}

extension FeedRouter: FeedRouterInput {

}
<file_sep>//
//  ViperViewController.swift
//  Vanilla
//
//  ViperViewController is essentially a mere UIViewController
//  which hides boilerplate ModuleViewWithOutput protocol code
//  and overrides prepare(for segue) func.
//
//  This is a base class for UIViewController instances which
//  implement VIPER and it should not be edited.
//
//  Created by <NAME> on 6/5/17.
//  Copyright © 2017 BrightBox. All rights reserved.
//

/// IMPORTANT:
/// THIS .swift FILE SHOULD NOT BE CHANGED OR UPDATED!
/// ANY MODIFICATIONS CAN LEAD TO LOSS OF INTEGRITY!

import UIKit

public protocol ModuleViewWithOutput {
    var presenter: ViperModuleInputProtocol { get }
}

public protocol ViperViewControllerProtocol: ModuleViewWithOutput {
    associatedtype OutputType
    var output: OutputType! { get set }
}

open class ViperViewController: UIViewController {
    open override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        handleSegue(segue, sender: sender)
    }
}

open class ViperTableViewController: UITableViewController {
    open override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        handleSegue(segue, sender: sender)
    }
}

open class ViperCollectionViewController: UICollectionViewController {
    open override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        self.handleSegue(segue, sender: sender)
    }
}

class ViperNavigationController: UINavigationController {
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        handleSegue(segue, sender: sender)
    }
}

public extension ViperViewControllerProtocol where Self: UIViewController {
    var presenter: ViperModuleInputProtocol {
        guard let output = output as? ViperModuleInputProtocol else {
            fatalError("Presenter should conform 'ViperModuleInputProtocol'")
        }

        return output
    }
}

extension ViperViewController: ViperModuleTransitionHandler {}
extension ViperNavigationController: ViperModuleTransitionHandler {}
extension ViperTableViewController: ViperModuleTransitionHandler {}
extension ViperCollectionViewController: ViperModuleTransitionHandler {}
<file_sep>//
//  GroupDto.swift
//  VKDemo
//
//  Created by <NAME> on 12/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct GroupDto: Codable {
    let id: Int
    let name: String
    let screenName: String
    let accessType: GroupAccessType
    let deactivated: String?
    let isAdmin: BoolInt?
    let adminLevel: GroupAdminLevelType?
    let isMember: BoolInt?
    let isAdvertiser: BoolInt?
    let invitedBy: Int?
    let type: GroupType
    let photo50: String
    let photo100: String
    let photo200: String
    
    private enum CodingKeys: String, CodingKey {
        case id
        case name
        case screenName = "screen_name"
        case accessType = "is_closed"
        case deactivated
        case isAdmin = "is_admin"
        case adminLevel = "admin_level"
        case isMember = "is_member"
        case isAdvertiser = "is_advertiser"
        case invitedBy = "invited_by"
        case type
        case photo50 = "photo_50"
        case photo100 = "photo_100"
        case photo200 = "photo_200"
    }
    
    enum GroupAccessType: Int, Codable {
        case open = 0
        case closed = 1
        case `private` = 2
    }
    
    enum GroupAdminLevelType: Int, Codable {
        case moderator
        case editor
        case admin
    }
    
    enum GroupType: String, Codable {
        case group
        case page
        case event
    }
}
<file_sep>//
//  SignInViewModel.swift
//  VKDemo
//
//  Created by <NAME> on 27/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import RxSwift
import RxCocoa
import WebKit

class SignInViewModel {
    
    private let sessionManager: UserSessionManager
    
    // Input
    let decidePolicyNavigationActionObserver: AnyObserver<URL>
    
    // Output
    let authorized: Observable<SignInCoordinationResult>

    init(sessionManager: UserSessionManager) {
        
        let _authorized = PublishSubject<SignInCoordinationResult>()
        authorized = _authorized.asObservable()
        
        decidePolicyNavigationActionObserver = AnyObserver(eventHandler: { event in
            switch event {
            case .next(let url):
                guard let parametersSubstring = url.absoluteString.split(separator: "#").last else { return }
                let parametersComponents = parametersSubstring.split(separator: "&")
                var parametersDict = [String: String]()
                
                parametersComponents.forEach({ substring in
                    let keyValueArray = substring.split(separator: "=")
                    parametersDict[String(keyValueArray[0])] = String(keyValueArray[1])
                })
                
                sessionManager.saveAccessToken(parametersDict["access_token"])
                _authorized.onNext(.success)
                break
            default:
                break
            }
        })
        
        self.sessionManager = sessionManager
    }
}
<file_sep>import PlaygroundSupport
import Foundation
import UIKit

@IBDesignable
class LikeButton: UIControl {
    
    private let fillColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)
    private let likedFillColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
    private let notLikedFillColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
    private let blueMain = UIColor(red: 0.294, green: 0.471, blue: 0.706, alpha: 1.000)
    
    override var frame: CGRect {
        didSet {
            configureAppearance()
        }
    }

    override func layoutSublayers(of layer: CALayer) {
        super.layoutSublayers(of: layer)
                
        for layer in [
          borderLayer,
          heartLayer
        ] {
          layer.bounds = bounds
          layer.position = center
        }
        configureAppearance()
    }
    
    private lazy var heartLayer: CAShapeLayer = {
        let bezierPath = UIBezierPath()
        bezierPath.move(to: CGPoint(x: 50, y: 81))
        bezierPath.addLine(to: CGPoint(x: 45.66, y: 76.97))
        bezierPath.addCurve(to: CGPoint(x: 19, y: 41.81), controlPoint1: CGPoint(x: 29.54, y: 62.91), controlPoint2: CGPoint(x: 19, y: 53.43))
        bezierPath.addCurve(to: CGPoint(x: 36.05, y: 25), controlPoint1: CGPoint(x: 19, y: 32.34), controlPoint2: CGPoint(x: 26.44, y: 25))
        bezierPath.addCurve(to: CGPoint(x: 50, y: 31.42), controlPoint1: CGPoint(x: 41.32, y: 25), controlPoint2: CGPoint(x: 46.59, y: 27.45))
        bezierPath.addCurve(to: CGPoint(x: 63.95, y: 25), controlPoint1: CGPoint(x: 53.41, y: 27.45), controlPoint2: CGPoint(x: 58.68, y: 25))
        bezierPath.addCurve(to: CGPoint(x: 81, y: 41.81), controlPoint1: CGPoint(x: 73.56, y: 25), controlPoint2: CGPoint(x: 81, y: 32.34))
        bezierPath.addCurve(to: CGPoint(x: 54.34, y: 76.97), controlPoint1: CGPoint(x: 81, y: 53.43), controlPoint2: CGPoint(x: 70.46, y: 62.91))
        bezierPath.addLine(to: CGPoint(x: 50, y: 81))
        bezierPath.close()
        UIColor.white.setFill()
        bezierPath.fill()
        UIColor.lightGray.setStroke()
        bezierPath.lineWidth = 6.5
        bezierPath.miterLimit = 4
        bezierPath.stroke()

        let layer = CAShapeLayer()
        layer.path = bezierPath.cgPath
        layer.fillColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0).cgColor
        layer.strokeColor = #colorLiteral(red: 0.6000000238, green: 0.6000000238, blue: 0.6000000238, alpha: 1).cgColor
        
        return layer
    }()
    
    private lazy var borderLayer: CAShapeLayer = {
        let layer = CAShapeLayer()
        let rectanglePath = UIBezierPath(roundedRect: CGRect(x: 4, y: 7, width: 201.5, height: 91.5), cornerRadius: 39)
        fillColor.setStroke()
        rectanglePath.lineWidth = 3
        rectanglePath.lineCapStyle = .round
        rectanglePath.lineJoinStyle = .round
        rectanglePath.stroke()

        layer.path = rectanglePath.cgPath
        layer.strokeColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0).cgColor
        layer.fillColor = #colorLiteral(red: 0.501960814, green: 0.501960814, blue: 0.501960814, alpha: 1).cgColor
        
        return layer
    }()
            
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        configureAppearance()
    }
    
    private func configureAppearance() {
        configureLayers()
        configOther()
    }
    
    private func configureLayers() {
            layer.addSublayer(borderLayer)
            layer.addSublayer(heartLayer)
    }
    
    private func configOther() {
        
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        configureAppearance()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        backgroundColor = .clear
        configureAppearance()
    }
}


let likeButton = LikeButton(frame: CGRect(x: 0, y: 0, width: 210, height: 105))
PlaygroundPage.current.liveView = likeButton
<file_sep>//
//  UserSessionManager.swift
//  VKDemo
//
//  Created by <NAME> on 28/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

class UserSessionManager {
    
    private let sessionDao: ISessionDao
    
    private var accessToken: String?
    
    init(sessionDao: ISessionDao) {
        self.sessionDao = sessionDao
    }
}

extension UserSessionManager: IUserSessionManager {
    func clearSession() {
        accessToken = nil
        sessionDao.saveAccessToken(nil)
    }
    
    func getAccessToken() -> String? {
        return accessToken ?? sessionDao.getAccessToken()
    }
    
    func saveAccessToken(_ accessToken: String?) {
        self.accessToken = accessToken
        sessionDao.saveAccessToken(accessToken)
    }
}
<file_sep>import UIKit

var str = "Hello, playground"



//// Present the view controller in the Live View window
//PlaygroundPage.current.liveView = button
<file_sep>//
//  BaseCoordinator.swift
//  VKDemo
//
//  Created by <NAME> on 24/04/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import RxSwift

class BaseCoordinator<ResultType> {
    
    typealias CoordinationResult = ResultType
    
    let disposeBag = DisposeBag()
    
    private let identifier = UUID()
    
    private var childCoordinators: [UUID: Any] = [UUID: Any]()
    
    private func store<T>(coordinator: BaseCoordinator<T>) {
        childCoordinators[coordinator.identifier] = coordinator
    }
    
    private func free<T>(coordinator: BaseCoordinator<T>) {
        childCoordinators[coordinator.identifier] = nil
    }
    
    func coordinate<T>(to coordinator: BaseCoordinator<T>) -> Observable<T> {
        store(coordinator: coordinator)
        return coordinator.start()
            .do(onNext: { [weak self] _ in
                self?.free(coordinator: coordinator)
            })
    }
    
    func start() -> Observable<ResultType> {
        fatalError("Should be implemneted in concrete implementation!")
    }
}
<file_sep>//
//  FeedTableViewDataSource.swift
//  VKDemo
//
//  Created by <NAME> on 25/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit

class FeedTableViewDataSource: NSObject {

    weak var output: (FeedViewOutput & SocialButtonsDelegate)?
    private let cellName = FeedTableViewCell.cellName
    private var feedCellModels = [FeedCellModel]()
    
    @IBOutlet weak var tableView: UITableView! {
        didSet {
            initialSetup()
        }
    }
    
    private lazy var templateFeedTextView: UITextView = {
        let textView = FeedTextView(frame: CGRect(x: 0.0,
                                                  y: 0.0,
                                                  width: tableView.frame.size.width - 32.0,
                                                  height: 0.0))
        return textView
    }()
    
    private func templateFeedTextViewHeight(text: String) -> Double {
        guard text.count > 0 else {
            return 0.0
        }
        templateFeedTextView.text = text
        let fixedWidth = templateFeedTextView.frame.size.width
        let textViewSize = templateFeedTextView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
        return Double(textViewSize.height)
    }
    
    func setup(with feedItems: [FeedItem]) {
        feedCellModels = feedItems.map({ FeedCellModel(feedItem: $0) })
        tableView.reloadData()
        tableView.refreshControl?.endRefreshing()
    }
    
    func addNew(feedItems: [FeedItem]) {
        let newFeedCellModels = feedItems.map({ FeedCellModel(feedItem: $0) })
        let startIndex = feedCellModels.count
        feedCellModels += newFeedCellModels
        let endIndex = feedCellModels.count - 1
        let newIndexPaths = Array(startIndex...endIndex).map { IndexPath(item: $0, section: 0)
        }
        tableView.insertRows(at: newIndexPaths, with: .none)
    }
    
    func updateFeedItem(feedItem: FeedItem) {
        guard let indexOfCellModelToUpdate = feedCellModels.firstIndex (where: {
            $0.feedItem.item.postId == feedItem.item.postId
        }) else { return }
        let updatedFeedCellModel = FeedCellModel.init(feedItem: feedItem)
        feedCellModels[indexOfCellModelToUpdate] = updatedFeedCellModel
    }
}

// MARK: - Private functions
extension FeedTableViewDataSource {
    private func initialSetup() {
        tableView.register(UINib(nibName: cellName, bundle: nil), forCellReuseIdentifier: cellName)
        tableView.dataSource = self
        tableView.delegate = self
                
        tableView.estimatedRowHeight = 300.0
        tableView.tableFooterView = FeedLoadMoreFooterView.loadFromXib()
        
        let refreshControl = UIRefreshControl(frame: .zero)
        refreshControl.addTarget(self,
                                 action: #selector(didPulledRefresh(_:)),
                                 for: .valueChanged)
        tableView.refreshControl = refreshControl
    }
    
    @objc private func didPulledRefresh(_: UIRefreshControl) {
        output?.didRefresh()
    }
}

// MARK: - UITableViewDataSource, UITableViewDelegate
extension FeedTableViewDataSource: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        guard feedCellModels.count == 0,
            let cell = tableView.dequeueReusableCell(withIdentifier: cellName) as? FeedTableViewCell else { return nil }

        cell.showSkeleton()
        return cell
    }
    
    
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        guard feedCellModels.count == 0 else { return CGFloat(0) }
        return CGFloat(300.0)
    }
    
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if indexPath.row == (feedCellModels.count - 1) {
            DispatchQueue.main.asyncAfter(deadline: .now() + 2.2) {
                self.output?.didScrollForMore()
            }
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return feedCellModels.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: cellName, for: indexPath) as? FeedTableViewCell else {
            return UITableViewCell()
        }
        
        let feedCellModel = feedCellModels[indexPath.row]
        // TODO: output as! SocialButtonsDelegate переделать
        if let outputAsSocialButtonsDelegate = output as? SocialButtonsDelegate {
            
        }
        cell.setup(feedCellModel: feedCellModel, delegate: output as! SocialButtonsDelegate)

        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//        82+169+44+52
        if indexPath.row >= feedCellModels.count { return 347.0 }
        
        let feedCellModel = feedCellModels[indexPath.row]
        let textViewHeight = templateFeedTextViewHeight(text: feedCellModel.feedItem.item.text ?? "")
        let collectionViewHeigh = collectionViewHeight(for: feedCellModel.feedItem.item.attachments ?? [AttachmentDto]())
//            ceil(Double(feedCellModel.photos.count) / 2.0) * 125.0 + 10
        
        let headerViewHeight = 82.0
        let socialButtonsViewHeight = 44.0
        let sumMarginsHeight = 50.5 + 2.0 * 0.5
        
        let overallHeight = headerViewHeight +
                            textViewHeight +
                            Double(collectionViewHeigh) +
                            socialButtonsViewHeight +
                            sumMarginsHeight
        
        return CGFloat(overallHeight + 200.0)
    }
    
    func collectionViewHeight(for attachments: [AttachmentDto]) -> CGFloat {
        if (attachments.count == 1),
            let photo = attachments.first?.photo,
            photo.sizes.count > 0 {
            let photoSizes = photo.sizes
            let photoSizesOrderArray = ["z", "y", "x", "w"]
            let properPhotoSize = photoSizes.sorted(by: {
                guard let index0 = photoSizesOrderArray.index(of: $0.type) else { return false }
                guard let index1 = photoSizesOrderArray.index(of: $1.type) else { return true }
                return index0 <= index1
                }).first
            let width = tableView.frame.width - 32.0
            let height = CGFloat(properPhotoSize?.height ?? 0) / CGFloat(properPhotoSize?.width ?? 1) * width
            
            return height
        }
            
            
        let height = CGFloat(120.0)
        let photosCount = attachments.filter({
            $0.type == .photo
            }).count
        
        return ceil(CGFloat(photosCount) / 2.0) * height
    }

}
<file_sep>//
//  AttachmentPhotoCollectionViewCell.swift
//  VKDemo
//
//  Created by <NAME> on 29/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit


class AttachmentPhotoCollectionViewCell: UICollectionViewCell {
    static let cellName: String = String(describing: AttachmentPhotoCollectionViewCell.self)
    
    var photoDto: PhotoDto?
    var photoUrlString: String?
    
    let photoSizesOrderArray = ["z", "y", "x", "w"]
    
    
    
    @IBOutlet weak var imageView: UIImageView!
    
    func setup(photoDto: PhotoDto) {
        self.photoDto = photoDto
    
        photoUrlString?.append("")
        guard let properSizedPhotoDto = photoDto.sizes.correctSize() else { return }
//        sorted(by: {
//            guard let index0 = photoSizesOrderArray.index(of: $0.type) else { return false }
//            guard let index1 = photoSizesOrderArray.index(of: $1.type) else { return true }
//            return  index0 <= index1
//        }).first else {
//            return
//        }
        
        imageView.image = nil
        properSizedPhotoDto.url.getAsyncImageFromUrl(completion: { [weak self] image, urlStrig in
            guard urlStrig == properSizedPhotoDto.url else { return }
            self?.imageView.image = image
        })
    }
}
<file_sep>//
//  UserDto.swift
//  VKDemo
//
//  Created by <NAME> on 12/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct UserDto: Codable {
    let id: Int
    let firstName: String
    let lastName: String
    let isClosed: Bool
    let canAccessClosed: Bool
    let sex: Sex
    let screenName: String
    let photo50: String
    let photo100: String
    let online: BoolInt
    
    enum Sex: Int, Codable {
        case unknown = 0
        case male
        case female
    }
    
    private enum CodingKeys: String, CodingKey {
        case id
        case firstName = "first_name"
        case lastName = "last_name"
        case isClosed = "is_closed"
        case canAccessClosed = "can_access_closed"
        case sex
        case screenName = "screen_name"
        case photo50 = "photo_50"
        case photo100 = "photo_100"
        case online
    }
}
<file_sep>//
//  VkEnterButton.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit

class VkEnterButton: UIButton {
    
    private let activeBgColor = UIColor(red: 60/255, green: 100/255, blue: 160/255, alpha: 1.0)
    private let highlightedBgColor = UIColor(red: 62/255, green: 90/255, blue: 140/255, alpha: 1.0)
    private let disabledBgColor = UIColor(red: 105/255, green: 145/255, blue: 205/255, alpha: 1.0)
    
    private let activeFontColor = UIColor(red: 250/255, green: 250/255, blue: 250/255, alpha: 1.0)
    private let highlightedFontColor = UIColor(red: 156/255, green: 176/255, blue: 212/255, alpha: 1.0)
    private let disabledFontColor = UIColor(red: 170/255, green: 170/255, blue: 190/255, alpha: 1.0)
    
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }
    
    private func commonInit() {
        setTitle("Войти", for: .normal)
        
        setBackgroundColor(color: activeBgColor, forState: .normal)
        setBackgroundColor(color: highlightedBgColor, forState: .highlighted)
        setBackgroundColor(color: disabledBgColor, forState: .disabled)
        
        setTitleColor(activeFontColor, for: .normal)
        setTitleColor(disabledFontColor, for: .disabled)
        setTitleColor(highlightedFontColor, for: .highlighted)
        
        layer.cornerRadius = 15.0
    }
}
<file_sep>//
//  AuthViewController.swift
//  VKDemo
//
//  Created by <NAME> on 13/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit
import RxSwift
import RxCocoa

class AuthViewController: UIViewController {
    
    @IBOutlet weak var vkLogoView: VkLogoView!
    @IBOutlet weak var logoViewCenterYConstraint: NSLayoutConstraint!
    @IBOutlet weak var logoViewTopConstraint: NSLayoutConstraint!
    @IBOutlet weak var enterButton: VkEnterButton!
    
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    
    var viewModel: AuthViewModel!
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        setupBindings()
        startWelcomeAnimation()
    }
    
    private func setupBindings() {
        enterButton.rx
            .tap
            .bind(to: viewModel.showSignInScreen)
            .disposed(by: disposeBag)
        
        emailTextField.rx
            .controlEvent(.editingDidBegin)
            .do(onNext: { [weak self] in
                self?.emailTextField.endEditing(true)
            })
            .bind(to: viewModel.showSignInScreen)
            .disposed(by: disposeBag)
        
        passwordTextField.rx
            .controlEvent(.editingDidBegin)
            .do(onNext: { [weak self] in
                self?.passwordTextField.endEditing(true)
            })
            .bind(to: viewModel.showSignInScreen)
            .disposed(by: disposeBag)
    }
    
    private func startWelcomeAnimation() {
        logoViewCenterYConstraint.isActive = false
        logoViewTopConstraint.isActive = true
        
        UIView.animate(withDuration: 1.0, animations: {
            self.vkLogoView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
            self.view.layoutIfNeeded()
        }) { completed in
            self.enterButton.isEnabled = true
        }
    }
}
<file_sep>//
//  VideoDto.swift
//  VKDemo
//
//  Created by <NAME> on 12/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct VideoDto: Codable {
    let id: Int
    let ownerId: Int
    let title: String
    let description: String
    let duration: Int
    let photo130: String
    let photo320: String
    let photo640: String?
    let photo800: String?
    let photo1280: String?
    let firstFrame130: String?
    let firstFrame320: String?
    let firstFrame640: String?
    let firstFrame800: String?
    let firstFrame1280: String?
    let date: Int
    let addingDate: Int?
    let views: Int
    let comments: Int
    let player: String?
    let platform: String?
    let canEdit: Bool?
    let canAdd: BoolInt?
    let isPrivate: Bool?
    let accessKey: String
    let processing: Bool?
    let live: BoolInt?
    let upcoming: BoolInt?
    let isFavorite: Bool
    let canComment: BoolInt?
    let canRepost: BoolInt?
    
    private enum CodingKeys: String, CodingKey {
        case id
        case ownerId = "owner_id"
        case title
        case description
        case duration
        case photo130 = "photo_130"
        case photo320 = "photo_320"
        case photo640 = "photo_640"
        case photo800 = "photo_800"
        case photo1280 = "photo_1280"
        case firstFrame130 = "first_frame_130"
        case firstFrame320 = "first_frame_320"
        case firstFrame640 = "first_frame_640"
        case firstFrame800 = "first_frame_800"
        case firstFrame1280 = "first_frame_1280"
        case date
        case addingDate = "adding_date"
        case views
        case comments
        case player
        case platform
        case canEdit = "can_edit"
        case canAdd = "can_add"
        case isPrivate = "is_private"
        case accessKey = "access_key"
        case processing
        case live
        case upcoming
        case isFavorite = "is_favorite"
        case canRepost = "can_repost"
        case canComment = "can_comment"
    }
}

struct VideoListDto: Codable {
    let count: Int
    let items: [VideoDto]
}
<file_sep>//
//  BaseViewController.swift
//  Vanilla
//
//  Created by <NAME> on 5/21/18.
//  Copyright © 2018 BrightBox. All rights reserved.
//

import UIKit

class BaseViewController: UIViewController {
    
    var statusBarStyle: UIStatusBarStyle = .default {
        didSet {
            setNeedsStatusBarAppearanceUpdate()
        }
    }

    override var preferredStatusBarStyle: UIStatusBarStyle {
        return statusBarStyle
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        hideKeyboardOnTappingAround()

        view.backgroundColor = UIColor.white
    }

    func setNavBarStyle(_ style: BarStyle) {
        navigationItem.backBarButtonItem?.title = ""

        switch style {
        case .standard:
            navigationController?.isNavigationBarHidden = false
            navigationController?.navigationBar.backgroundColor = UIColor.blueMain
            navigationController?.navigationBar.barTintColor = UIColor.blueMain
            statusBarStyle = .default
            setLargeTitlesEnabled(false)
            setSeparatorHidden(false)
        case .standardNoSeparator:
            navigationController?.isNavigationBarHidden = false
            navigationController?.navigationBar.backgroundColor = UIColor.blueMain
            navigationController?.navigationBar.barTintColor = UIColor.blueMain
            statusBarStyle = .default
            setLargeTitlesEnabled(false)
            setSeparatorHidden(true)
        case .translucent:
            navigationController?.isNavigationBarHidden = true
            statusBarStyle = .lightContent
        case .transparent:
            navigationController?.navigationBar.isTranslucent = true
            navigationController?.navigationBar.backgroundColor = .clear
            navigationController?.navigationBar.barTintColor = .clear
            statusBarStyle = .lightContent
            setSeparatorHidden(true)
        case .hidden:
            navigationController?.isNavigationBarHidden = true
        case .standardLargeTitles:
            navigationController?.isNavigationBarHidden = false
            navigationController?.navigationBar.backgroundColor = UIColor.blueMain
            navigationController?.navigationBar.barTintColor = UIColor.blueMain
            statusBarStyle = .default
            setLargeTitlesEnabled(true)
            setSeparatorHidden(false)
        }
        navigationController?.navigationBar.isExclusiveTouch = true
    }

    private func setLargeTitlesEnabled(_ enabled: Bool) {
        guard #available(iOS 11.0, *) else {
            return
        }

        if navigationController?.navigationBar.prefersLargeTitles == true {
            navigationItem.largeTitleDisplayMode = enabled ? .automatic : .never
        } else if enabled {
            navigationController?.navigationBar.prefersLargeTitles = true
        }
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        setNavBarStyle(.standard)
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        view.setInCallSafeFrame()
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

// MARK: - Hide Keyboard When View Tapped
extension BaseViewController {
    private func hideKeyboardOnTappingAround() {
        let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self,
                                                                 action: #selector(hideKeyboard))
        tap.cancelsTouchesInView = false
        view.addGestureRecognizer(tap)
    }

    @objc private func hideKeyboard() {
        view.endEditing(true)
    }
}

// MARK: - Keyboard Show/Hide Observers (Subclassing)
extension BaseViewController {
    open func addKeyboardWillShowObserver(selector: Selector) {
        NotificationCenter.default.addObserver(self,
                                               selector: selector,
                                               name: .UIKeyboardWillShow,
                                               object: nil)
    }
    
    open func addKeyboardWillHideObserver(selector: Selector) {
        NotificationCenter.default.addObserver(self,
                                               selector: selector,
                                               name: .UIKeyboardWillHide,
                                               object: nil)
    }
    
    open func addKeyboardDidHideObserver(selector: Selector) {
        NotificationCenter.default.addObserver(self,
                                               selector: selector,
                                               name: .UIKeyboardDidHide,
                                               object: nil)
    }
}
<file_sep>//
//  UIViewController+Init.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import UIKit

extension UIViewController {
    static func loadFromXib(bundle: Bundle? = nil, overriddenXib: String? = nil) -> Self {
        return self.init(nibName: overriddenXib ?? String(describing: self), bundle: bundle)
    }
}
<file_sep>//
//  FeedPresenter.swift
//  VKDemo
//
//  Created by AntonKrylovBrightBox on 10/06/2019.
//  Copyright © 2019 BrightBox. All rights reserved.
//

import UIKit

class FeedPresenter {
    private weak var view: FeedViewInput?
    private let router: FeedRouterInput
    private let feedManager: IFeedManager
    
    var feedItems =  [FeedItem]()
    var nextFrom: String?
    
    init(view: FeedViewInput,
         router: FeedRouterInput,
         feedManager: IFeedManager) {
        self.view = view
        self.router = router
        self.feedManager = feedManager
    }
    
    private func loadFeed(take: Int,
                     startFrom: String?,
                    completion: (() -> Void)? = nil) {
        feedManager.getFeed(count: take, startFrom: startFrom, success: { [weak self] feedItems, nextFrom in
            self?.nextFrom = nextFrom
            
            let isDownloadingFromBeginning = (startFrom == nil)
            if isDownloadingFromBeginning {
                self?.feedItems = feedItems
                self?.view?.setup(feedItems: feedItems)
            } else {
                self?.feedItems += feedItems
                self?.view?.addNewFeedItems(feedItems: feedItems)
                completion?()
            }
        }) { [weak self] error in
            completion?()
        }
    }

    private func likeFeed(feedItem: FeedItem, completion: @escaping ((FeedItem) -> Void)) {
        feedManager.addLike(for: feedItem,
                            success: { [weak self] feedItem in
                                completion(feedItem)
        }) { [weak self] error in
            
        }
        
    }
    
    private func unlikeFeed(feedItem: FeedItem, completion: @escaping ((FeedItem) -> Void)) {
        feedManager.deleteLike(from: feedItem, success: { feedItem in
            completion(feedItem)
        }) { error in
            
        }
    }
}

extension FeedPresenter: FeedViewOutput {
    func didRefresh() {
        loadFeed(take: 15, startFrom: nil)
    }
    
    func viewLoaded() {
        feedManager.add(feedLikeUpdateHandler: { [weak self] feedItem in
            guard let strongSelf = self else { return }
            
            strongSelf.feedItems.updateWithFeedItem(feedItem)
            strongSelf.view?.updateFeedAfterLike(feedItem: feedItem)
        })
        
        loadFeed(take: 15, startFrom: nil, completion: nil)
    }
    
    func didScrollForMore() {
        loadFeed(take: 15, startFrom: nextFrom, completion: nil)
    }
}

extension FeedPresenter: SocialButtonsDelegate {
    func didLike(feedItem: FeedItem, completion: @escaping ((FeedItem) -> Void)) {
        likeFeed(feedItem: feedItem, completion: completion)
    }
    
    func didUnlike(feedItem: FeedItem, completion: @escaping ((FeedItem) -> Void)) {
        unlikeFeed(feedItem: feedItem, completion: completion)
    }
}
<file_sep>//
//  FeedItemCellViewModel.swift
//  VKDemo
//
//  Created by <NAME> on 25/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct FeedCellModel {
    let sourceIconUrl: String
    let sourceName: String
    let dateAdded: Date
    let likes: LikesCountDto?
    let comments: CommentsCountDto?
    let reposts: RepostCountDto?
    let photos: [PhotoDto]
    let videos: [VideoDto]
    let audios: [AudioDto]
    let docs: [DocDto]
    let links: [LinkDto]
    let notes: [NoteDto]
    let polls: [PollDto]
    let wikiPages: [WikiPageDto]
    let marketItems: [MarketItem]
    let marketAlbums: [MarketAlbum]
    let stickers: [Sticker]
    let podcasts: [PodcastDto]
        
    var feedItem: FeedItem
    
    // Для большой картинки подходят:
    // w, z, y, x, m, s
    // Для превью картинки, если картинка 1:
    // w, z, y, x, m, s
    
    init(feedItem: FeedItem) {
        self.feedItem = feedItem
        
        // Для шапки: sourceName, sourceIconUrl
        // dateAdded
        if feedItem.item.sourceId > 0 {
            // Post napisan user'om
            guard let sourceUserDto = feedItem.profiles?
                .filter({ $0.id == feedItem.item.sourceId })
                .first else {
                    fatalError("No source user found!")
            }
            sourceName = "\(sourceUserDto.lastName) \(sourceUserDto.firstName)"
            sourceIconUrl = sourceUserDto.photo100
        } else {
            // Post napisan group'oy
            let sourceGroupId = -1 * feedItem.item.sourceId
            guard let sourceGroupDto = feedItem.groups?
                .filter({ $0.id == sourceGroupId })
                .first else {
                    fatalError("No source user found!")
            }
            sourceName = sourceGroupDto.name
            sourceIconUrl = sourceGroupDto.photo100
        }
        
        dateAdded = feedItem.item.date
        // ************************* //
        
        // Attachments //
        self.photos = feedItem.item.attachments?.compactMap({ $0.photo }) ?? [PhotoDto]()
        self.videos = feedItem.item.attachments?.compactMap({ $0.video }) ?? [VideoDto]()
        self.audios = feedItem.item.attachments?.compactMap({ $0.audio }) ?? [AudioDto]()
        self.docs = feedItem.item.attachments?.compactMap({ $0.doc }) ?? [DocDto]()
        self.links = feedItem.item.attachments?.compactMap({ $0.link }) ?? [LinkDto]()
        self.notes = feedItem.item.attachments?.compactMap({ $0.note }) ?? [NoteDto]()
        self.polls = feedItem.item.attachments?.compactMap({ $0.poll }) ?? [PollDto]()
        self.wikiPages = feedItem.item.attachments?.compactMap({ $0.wikiPage }) ?? [WikiPageDto]()
        self.marketItems = feedItem.item.attachments?.compactMap({ $0.marketItem }) ?? [MarketItem]()
        self.marketAlbums = feedItem.item.attachments?.compactMap({ $0.marketAlbum }) ?? [MarketAlbum]()
        self.stickers = feedItem.item.attachments?.compactMap({ $0.sticker }) ?? [Sticker]()
        self.podcasts = feedItem.item.attachments?.compactMap({ $0.podcast }) ?? [PodcastDto]()
        self.likes = feedItem.item.likes
        self.comments = feedItem.item.comments
        self.reposts = feedItem.item.reposts
        // ****************** //
        
        // Likes, shares, views and info for these buttons
        // Mojno vzat' iz feedItem, mojno perenesti yavno
        // TODO: podumat'!
        // ****************** //
    }
}
<file_sep>//
//  CircleImageView.swift
//  VKDemo
//
//  Created by <NAME> on 25/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit

class CircleImageView: UIImageView {
    override func layoutSubviews() {
        super.layoutSubviews()
        layer.cornerRadius = bounds.size.height / 2.0
    }
}
<file_sep>//
//  DataContainer.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import Moya

protocol DataContainerProtocol {
    var feedManager: IFeedManager { get }
    var messagesManager: IMessagesManager { get }
}

class DataContainer {
    private let serviceContainer = ServiceContainer()
    private let core: CoreContainerProtocol
    private let parser: ParserContainerProtocol
    
    init(core: CoreContainerProtocol,
         parser: ParserContainerProtocol) {
        
        self.core = core
        self.parser = parser
        
        let authPlugin = VKAccessTokenPlugin(userSessionManager: core.sessionManager)
        let loggerPlugin = NetworkLoggerPlugin(verbose: true)
        let feedProvider = MoyaProvider<FeedTarget>(plugins: [authPlugin, loggerPlugin])
        
        serviceContainer.add(services: feedProvider)
        
        let feedManager = FeedManager(feedProvider: feedProvider,
                                      feedParser: parser.feedParser)
        serviceContainer.add(services: feedManager)
        
        let messagesProvider = MoyaProvider<MessagesTarget>(plugins: [authPlugin, loggerPlugin])
        serviceContainer.add(services: messagesProvider)
        
        let messagesManager = MessagesManager(messagesProvider: messagesProvider,
                                              messagesParser: parser.messagesParser)
        serviceContainer.add(services: messagesManager)
    }
}

extension DataContainer: DataContainerProtocol {
    var messagesManager: IMessagesManager {
        serviceContainer.get(service: MessagesManager.self)!
    }
    
    var feedManager: IFeedManager {
        return serviceContainer.get(service: FeedManager.self)!
    }    
}
<file_sep>//
//  ImageLoadingCache.swift
//  Vanilla
//
//  Created by <NAME> on 15.03.18.
//  Copyright © 2018 BrightBox. All rights reserved.
//

import Foundation

class ImageLoadingCache {
    static let sharedCache: NSCache = { () -> NSCache<AnyObject, AnyObject> in
        let cache = NSCache<AnyObject, AnyObject>()
        cache.name = "ImageLoadingCache"
        cache.countLimit = 200
        cache.totalCostLimit = 10*1024*1024

        return cache
    }()
}
<file_sep>//
//  FeedViewOutput.swift
//  VKDemo
//
//  Created by AntonKrylovBrightBox on 10/06/2019.
//  Copyright © 2019 BrightBox. All rights reserved.
//

protocol FeedViewOutput: class {
    func viewLoaded()
    func didRefresh()
    func didScrollForMore()
}
<file_sep>//
//  FeedParser.swift
//  VKDemo
//
//  Created by <NAME> on 22/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct FeedItem {
    var item: FeedItemDto
    let profiles: [UserDto]?
    let groups: [GroupDto]?
}

extension Array where Element == FeedItem {
    mutating func updateWithFeedItem(_ feedItem: FeedItem) {
        guard let index = firstIndex (where: { $0.item.postId == feedItem.item.postId }) else { return }
        self[index] = feedItem
    }
}

protocol IFeedParser {
    func parse(feedResponse: FeedResponse) -> [FeedItem]
}

class FeedParser: IFeedParser {
    func parse(feedResponse: FeedResponse) -> [FeedItem] {
        var feedItems = [FeedItem]()
        let profiles = feedResponse.profiles ?? []
        let groups = feedResponse.groups ?? []
        
        feedResponse.items.forEach { feedItemDto in
            let copyHistoryOwnerIds: [Int]
            if let copyHistory = feedItemDto.copyHistory {
                copyHistoryOwnerIds = copyHistory.map { $0.ownerId }
            } else {
                copyHistoryOwnerIds = [Int]()
            }
            
            let copyHistoryFromIds: [Int]
            if let copyHistory = feedItemDto.copyHistory {
                copyHistoryFromIds = copyHistory.map { $0.fromId }
            } else {
                copyHistoryFromIds = [Int]()
            }
            
            var optionalProfileAndGroupIds = [Int]()
            if let signerId = feedItemDto.signerId {
                optionalProfileAndGroupIds.append(signerId)
            }

            let distinctProfileAndGroupIds = Set(
                copyHistoryOwnerIds
                + copyHistoryFromIds
                + optionalProfileAndGroupIds
                + [feedItemDto.sourceId]
            )
            
            let distinctProfileIds = distinctProfileAndGroupIds
                .filter { $0 > 0 }
            
            let distinctGroupIds = distinctProfileAndGroupIds
                .filter { $0 < 0 }
                .map { abs($0) }
            
            let profilesForCurrentFeedItem = profiles.filter { distinctProfileIds.contains($0.id) }
            let groupsForCurrentFeedItem = groups.filter { distinctGroupIds.contains($0.id) }
            
            let feedItem = FeedItem(item: feedItemDto,
                                    profiles: profilesForCurrentFeedItem,
                                    groups: groupsForCurrentFeedItem)
            feedItems.append(feedItem)
        }
        
        return feedItems
    }
}
<file_sep>//
//  ChatViewOutput.swift
//  VKDemo
//
//  Created by AntonKrylovBrightBox on 10/06/2019.
//  Copyright © 2019 BrightBox. All rights reserved.
//

protocol ChatViewOutput {
    func viewLoaded()
}
<file_sep>//
//  AttachmentDto.swift
//  VKDemo
//
//  Created by <NAME> on 12/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct AttachmentDto: Codable {
    let type: AttachmentType
    
    let photo: PhotoDto?
    let video: VideoDto?
    let audio: AudioDto?
    let doc: DocDto?
    let link: LinkDto?
    let note: NoteDto?
    let poll: PollDto?
    let wikiPage: WikiPageDto?
    let marketItem: MarketItem?
    let marketAlbum: MarketAlbum?
    let sticker: Sticker?
    let podcast: PodcastDto?
    
    //    case album = "album" TODO:доделать
    //    case photosList = "photos_list" Массив из строк, содержащих идентификаторы фотографий
    
    private enum CodingKeys: String, CodingKey {
        case type
        case photo
        case video
        case audio
        case doc
        case link
        case note
        case poll
        case wikiPage = "page"
        case marketItem
        case marketAlbum
        case sticker
        case podcast
    }
}

enum AttachmentType: String, Codable {
    case photo = "photo"
    case video = "video"
    case audio = "audio"
    case doc = "doc"
    case link = "link"
    case note = "note"
    case poll = "poll"
    case page = "page"
    case album = "album"
    case photosList = "photos_list"
    case market = "market"
    case marketAlbum = "market_album"
    case prettyCards = "pretty_cards"
    case podcast = "podcast"
}
<file_sep>//
//  LinkDto.swift
//  VKDemo
//
//  Created by <NAME> on 12/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct LinkDto: Codable {
    let urlString: String
    let title: String
    let caption: String?
    let description: String
    let photo: PhotoDto?
    let product: Product?
    let button: ButtonDto?
    let previewPage: String?
    let previewUrl: String?
    
    private enum CodingKeys: String, CodingKey {
        case urlString = "url"
        case title
        case caption
        case description
        case photo
        case product
        case button
        case previewPage = "preview_page"
        case previewUrl = "preview_url"
    }
}

struct ButtonDto: Codable {
    let title: String
    let action: ButtonActionDto
}

struct ButtonActionDto: Codable {
    let type: String
    let urlString: String
    
    private enum CodingKeys: String, CodingKey {
        case type
        case urlString = "url"
    }
}
<file_sep>//
//  ChatRouter.swift
//  VKDemo
//
//  Created by AntonKrylovBrightBox on 10/06/2019.
//  Copyright © 2019 BrightBox. All rights reserved.
//

import UIKit

class ChatRouter {
    private weak var transitionHandler: ViperModuleTransitionHandler?

    func createModule() -> UIViewController {
        let vc = ChatViewController.loadFromXib()

        transitionHandler = vc

        let presenter = ChatPresenter(view: vc,
                                      router: self,
                                      messagesManager: DI.container.data.messagesManager)

        vc.presenter = presenter
        vc.title = "Chat"
        vc.view.backgroundColor = UIColor.blue
        
        
        return vc
    }

    func openModule(with transitionHandler: ViperModuleTransitionHandler, transitionStyle: TransitionStyle) {
        transitionHandler.openModule(vc: createModule(), style: transitionStyle)
    }
}

extension ChatRouter: ChatRouterInput {

}
<file_sep>//
//  AppDelegate.swift
//  VKDemo
//
//  Created by <NAME> on 24/04/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import UIKit
import RxSwift

@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    let disposeBag = DisposeBag()
    
    private let appCoordinator = DI.container.coordinator.appCoordinator

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        setupAppearance()
        _ = appCoordinator.start()
        
        return true
    }

    func applicationWillResignActive(_ application: UIApplication) {
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
    }

    func applicationWillTerminate(_ application: UIApplication) {
    }
    
    private func setupAppearance() {
        UINavigationBar.appearance().isTranslucent = false
        UINavigationBar.appearance().barTintColor = UIColor.blueMain
        UINavigationBar.appearance().tintColor = .white
        UINavigationBar.appearance().shadowImage = UIImage()
        UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
    }
}
<file_sep>//
//  PodcastDto.swift
//  VKDemo
//
//  Created by <NAME> on 25/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct PodcastDto: Codable {
    let artist: String
    let id: Int
    let ownerId: Int
    let title: String
    let duration: Int
    let trackCode: String
    let url: String
    let date: Int
    let lyricsId: Int
    let noSearch: BoolInt
    let isExplicit: BoolInt?
    let podcastInfo: PodcastInfoDto
    
    
    private enum CodingKeys: String, CodingKey {
        case artist
        case id
        case ownerId = "owner_id"
        case title
        case duration
        case trackCode = "track_code"
        case url
        case date
        case lyricsId = "lyrics_id"
        case noSearch = "no_search"
        case isExplicit = "is_explicit"
        case podcastInfo = "podcast_info"
    }
}

struct PodcastInfoDto: Codable {
    let cover: PodcastInfoCoverDto
    let plays: Int
    let position: Int?
    let isFavorite: Bool?
    let description: String
    
    private enum CodingKeys: String, CodingKey {
        case cover
        case plays
        case position
        case isFavorite = "is_favorite"
        case description
    }
}

struct PodcastInfoCoverDto: Codable {
    let sizes: [PhotoSizeCopy]
}
<file_sep>//
//  BaseCoordinator.swift
//  VKDemo
//
//  Created by <NAME> on 24/04/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import Alamofire

public enum DateError: Error {
    case invalidDate
}

public enum Result<Value> {
    case success(Value)
    case failure(RequestError)

    public var value: Value? {
        switch self {
        case .success(let value):
            return value
        case .failure:
            return nil
        }
    }

    public var error: Error? {
        switch self {
        case .success:
            return nil
        case .failure(let error):
            return error
        }
    }
}

public struct NoData: Codable {}

public struct RequestResponse<T: Codable> {
    public private(set) var data: Data?
    public private(set) var mappedData: T!
    public private(set) var response: HTTPURLResponse?
    public var code: HTTPStatusCode {
        return response != nil ? HTTPStatusCode(HTTPResponse: response)! : .internalServerError
    }
    public private(set) var error: Error?
    public private(set) var result: Result<T>

    init(data: Data?, response: URLResponse?, error: Error?, successCodes: Range<Int>? = nil) {
        let decoder = JSONDecoder()
//        decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
//            let container = try decoder.singleValueContainer()
//            let dateStr = try container.decode(String.self)
//
//            if let date = Date.create(from: dateStr, format: "yyyy-MM-dd") {
//                return date
//            }
//            if let date = Date.create(from: dateStr, format: "yyyy-MM-dd'T'HH:mm") {
//                return date
//            }
//            if let date = Date.create(from: dateStr, format: "yyyy-MM-dd'T'HH:mm:ss") {
//                return date
//            }
//            if let date = Date.create(from: dateStr, format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ") {
//                return date
//            }
//            if let date = Date.create(from: dateStr, format: "yyyy-MM-dd'T'HH:mm:ssZ") {
//                return date
//            }
//            throw DateError.invalidDate
//        })

        self.response = response as? HTTPURLResponse
        self.data = data
        self.error = error

        let httpCode = HTTPStatusCode(HTTPResponse: self.response) ?? .internalServerError

        guard httpCode != .unauthorized else {
            result = .failure(RequestError.unauthorized)
            return
        }

        guard (error as? URLError)?.code != .notConnectedToInternet else {
            result = .failure(RequestError.noInternetConnection)
            return
        }

        guard let code = self.response?.statusCode, 200...299 ~= code else {
            self.result = .failure(RequestError.error(code: httpCode))
            return
        }

        guard let data = data else {
            result = .failure(RequestError.error(code: httpCode))
            return
        }

        if let noData = NoData() as? T {
            self.result = .success(noData)
            return
        }

        do {
            self.mappedData = try decoder.decode(T.self, from: data)

            self.result = .success(mappedData)
        } catch let error {
            print(error.localizedDescription)

            self.error = error
            result = .failure(RequestError.error(code: httpCode))
        }
    }
}
<file_sep>//
//  FeedTableViewCell.swift
//  VKDemo
//
//  Created by <NAME> on 10/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit
import SkeletonView

protocol SocialButtonsDelegate: class {
    func didLike(feedItem: FeedItem, completion: @escaping ((FeedItem) -> Void))
    func didUnlike(feedItem: FeedItem, completion: @escaping ((FeedItem) -> Void))
}


class FeedTableViewCell: UITableViewCell {
    static let cellName: String = String(describing: FeedTableViewCell.self)
    
    @IBOutlet weak var sourceIconImageView: UIImageView!
    @IBOutlet weak var sourceNameLabel: UILabel!
    @IBOutlet weak var dateAddedLabel: UILabel!
    
    @IBOutlet weak var textView: UITextView!
    
    @IBOutlet weak var likeButton: LikeButton!
    @IBOutlet weak var commentsButton: CommentsButton!
    @IBOutlet weak var repostsButton: RepostsButton!
    
    @IBOutlet weak var previewCollectionView: UICollectionView! {
        didSet {
            previewCollectionViewDataSource.collectionView = previewCollectionView
        }
    }
    private lazy var previewCollectionViewDataSource = { AttachmentsCollectionViewDataSource()
    }()
    
    private var cellModel: FeedCellModel?
    private weak var delegate: SocialButtonsDelegate?
    
    @IBOutlet weak var textViewHeightConstraint: NSLayoutConstraint!
    @IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!
    
    func setup(feedCellModel: FeedCellModel, delegate: SocialButtonsDelegate? = nil) {
        self.cellModel = feedCellModel
        self.delegate = delegate
        
        let feedText = feedCellModel.feedItem.item.text
        let feedTextLength = feedText?.count ?? 0
        
        textView.text = feedText
        sourceNameLabel.text = feedCellModel.sourceName
        dateAddedLabel.text = "Date added"
        
        let likesCount = feedCellModel.likes?.count ?? 0
        let userLiked = (feedCellModel.likes?.userLikes == BoolInt.true) ? true : false
        let commentsCount = feedCellModel.comments?.count ?? 0
        let repostsCount = feedCellModel.reposts?.count ?? 0
        
        likeButton.setup(likesCount: likesCount,
                         state: userLiked ? .liked : .notLiked,
                         animated: false,
                         delegate: self)
        commentsButton.setup(commentsCount: commentsCount)
        repostsButton.setup(repliesCount: repostsCount)
        
        
        feedCellModel.sourceIconUrl.getAsyncImageFromUrl { [weak self] image, url in
            guard let strongSelf = self,
                let cellModel = strongSelf.cellModel,
                cellModel.sourceIconUrl == url,
                let image = image,
                let strongImageView = strongSelf.sourceIconImageView  else {
                    return
            }
            
            UIView.transition(with: strongImageView,
                              duration: 0.3,
                              options: .transitionCrossDissolve,
                              animations: {
                                strongImageView.image = image
            }, completion: nil)
        }
        
        if feedTextLength > 0 {
            let fixedWidth = textView.frame.size.width
            let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
            textViewHeightConstraint.constant = newSize.height
        } else {
            textViewHeightConstraint.constant = 0.0
        }
        
        collectionViewHeightConstraint.constant = previewCollectionViewDataSource.collectionViewHeight(for: cellModel?.feedItem.item.attachments ?? [AttachmentDto]()) + 200.0
        previewCollectionViewDataSource.setup(attachments: cellModel?.feedItem.item.attachments ?? [])
    }
    
    func showSkeleton() {
        textView.showAnimatedGradientSkeleton()
        sourceIconImageView.showAnimatedGradientSkeleton()
        sourceNameLabel.showAnimatedGradientSkeleton()
        dateAddedLabel.showAnimatedGradientSkeleton()
        likeButton.showAnimatedGradientSkeleton()
        commentsButton.showAnimatedGradientSkeleton()
        repostsButton.showAnimatedGradientSkeleton()
        previewCollectionView.showAnimatedGradientSkeleton()
    }
    
    func hideSkeleton() {
        textView.hideSkeleton()
        sourceIconImageView.hideSkeleton()
        sourceNameLabel.hideSkeleton()
        dateAddedLabel.hideSkeleton()
        likeButton.hideSkeleton()
        commentsButton.hideSkeleton()
        repostsButton.hideSkeleton()
        previewCollectionView.hideSkeleton()
    }
    
    private func hideCollectionView() {
        collectionViewHeightConstraint.constant = 0.0
    }
}

extension FeedTableViewCell: LikeButtonDelegate {
    func didLike(likeButton: LikeButton?) {
        guard let feedItem = cellModel?.feedItem else { return }
        
        delegate?.didLike(feedItem: feedItem, completion: { [weak self] updatedFeedItem in
            guard self?.cellModel?.feedItem.item.postId == updatedFeedItem.item.postId else { return }
            self?.cellModel?.feedItem = updatedFeedItem
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
                likeButton?.setup(likesCount: updatedFeedItem.item.likes?.count ?? 0,
                                  state: .liked,
                                  animated: true,
                                  delegate: self)
            }
        })
    }
    
    func didUnlike(likeButton: LikeButton?) {
        guard let feedItem = cellModel?.feedItem else { return }
        
        delegate?.didUnlike(feedItem: feedItem, completion: { [weak self] updatedFeedItem in
            guard self?.cellModel?.feedItem.item.sourceId == updatedFeedItem.item.sourceId else { return }
            self?.cellModel?.feedItem = updatedFeedItem
            
            likeButton?.setup(likesCount: updatedFeedItem.item.likes?.count ?? 0,
                              state: .notLiked,
                              animated: true,
                              delegate: self)
        })
    }
}
<file_sep>//
//  AppCoordinator.swift
//  VKDemo
//
//  Created by <NAME> on 24/04/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import UIKit
import RxSwift

class AppCoordinator: BaseCoordinator<Void> {
    
    private let window: UIWindow
    
    init(window: UIWindow) {
        self.window = window
        super.init()
    }
    
    override func start() -> Observable<Void> {
        let welcomeCoordinator = WelcomeCoordinator(window: window)
        let observable = coordinate(to: welcomeCoordinator).share()
        
        _ = observable.subscribe({ event in
            
        })
        
        return observable
    }
}
<file_sep>//
//  FeedItemDto.swift
//  VKDemo
//
//  Created by <NAME> on 10/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct FeedItemDto: Codable {
    let type: FeedType
    let sourceId: Int
    let date: Date
    let postId: Int?
    let postType: String? // TODO: enum
    let text: String?
    let markedAsAds: BoolInt?
    let attachments: [AttachmentDto]?
    let postSource: FeedPostSource?
    let comments: CommentsCountDto?
    var likes: LikesCountDto?
    let reposts: RepostCountDto?
    let views: ViewsCountDto?
    let isFavorite: Bool?
    let photos: PhotosListDto?
    let friends: FriendsDto?
    let signerId: Int?
    let video: VideoListDto?
    let copyHistory: [CopyHistory]? // Если есть, то это репост
    let copyOwnerId: Int?
    let copyPostId: Int?
    let copyPostDate: Date?
    let canEdit: Bool?
    let canDelete: Bool?
    
    private enum CodingKeys: String, CodingKey {
        case sourceId = "source_id"
        case text
        case postType = "post_type"
        case markedAsAds = "marked_as_ads"
        case postSource = "post_source"
        case isFavorite = "is_favorite"
        case postId = "post_id"
        case signerId = "signer_id"
        case copyHistory = "copy_history"
        
        case type
        case date
        case attachments
        case comments
        case likes
        case reposts
        case views
        case photos
        case friends
        case video
        case copyOwnerId = "copy_owner_id" //Не факт, что приходит вообще
        case copyPostId = "copy_post_id" //Не факт, что приходит вообще
        case copyPostDate = "copy_post_date" //Не факт, что приходит вообще
        case canEdit = "can_edit"
        case canDelete = "can_delete"
    }
}

enum FeedType: String, Codable {
    case post = "post"
    case photo = "photo"
    case photoTag = "photo_tag"
    case wallPhoto = "wall_photo"
    case friend = "friend"
    case note = "note"
    case audio = "audio"
    case video = "video"
}

struct FeedPostSource: Codable {
    let type: String
    let platform: String?
}

struct CommentsCountDto: Codable {
    let count: Int
    let canPost: BoolInt?
    let groupsCanPost: Bool?
    
    private enum CodingKeys: String, CodingKey {
        case count
        case canPost = "can_post"
        case groupsCanPost = "groups_can_post"
    }
}

struct LikesCountDto: Codable {
    var count: Int
    var userLikes: BoolInt
    let canLike: BoolInt?
    let canPublish: BoolInt?

    private enum CodingKeys: String, CodingKey {
        case count
        case userLikes = "user_likes"
        case canLike = "can_like"
        case canPublish = "can_publish"
    }
}

struct RepostCountDto: Codable {
    let count: Int
    let userReposted: BoolInt
    
    private enum CodingKeys: String, CodingKey {
        case count
        case userReposted = "user_reposted"
    }
}

struct ViewsCountDto: Codable {
    let count: Int
}

struct FriendsDto: Codable {
    let count: Int
    let items: [UserIdDto]
}

struct UserIdDto: Codable {
    let userId: Int
    
    private enum CodingKeys: String, CodingKey {
        case userId = "user_id"
    }
}

struct CopyHistory: Codable {
    let id: Int
    let ownerId: Int
    let fromId: Int
    let date: Date
    let postType: String? // TODO: enum
    let text: String
    let attachments: [AttachmentDto]?
    let postSource: FeedPostSource
    
    private enum CodingKeys: String, CodingKey {
        case id
        case ownerId = "owner_id"
        case fromId = "from_id"
        case date
        case postType = "post_type"
        case text
        case attachments
        case postSource = "post_source"
    }
}


struct MarketItem: Codable {
    
}

struct MarketAlbum: Codable {
    
}

struct Sticker: Codable {
    
}

struct Product: Codable {
    let price: Price
}

struct Price: Codable {
    let amount: Int
    let currency: Currency
    let text: String
}

struct Currency: Codable {
    let id: Int
    let name: String
}
<file_sep>//
//  SignInCoordinator.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import UIKit
import RxSwift
import RxCocoa
import RxWebKit
import WebKit
import SafariServices

enum SignInCoordinationResult {
    case success
    case fail
}

class SignInCoordinator: BaseCoordinator<SignInCoordinationResult> {
    private let rootViewController: UIViewController
    
    // Input
    private var didAuthorized: AnyObserver<SignInCoordinationResult>?
    
    // Output
    private var authorized: Observable<SignInCoordinationResult>?
    
    init(rootViewController: UIViewController) {
        self.rootViewController = rootViewController
        super.init()
    }

    override func start() -> Observable<SignInCoordinationResult> {
        
        let signInVC = SignInViewController()
        let signInVM = SignInViewModel(sessionManager: DI.container.core.sessionManager)
        signInVC.viewModel = signInVM
        
        guard let navC = rootViewController as? UINavigationController else {
            fatalError("123!!!")
        }
        navC.pushViewController(signInVC, animated: true)
        
        return signInVM.authorized
    }
}

<file_sep>//
//  UIImageView+Loading.swift
//  Vanilla
//
//  Created by <NAME> on 28.06.17.
//  Copyright © 2017 BrightBox. All rights reserved.
//

import UIKit

extension UIImageView {
    public func imageFromUrl(urlString: String) {
        self.imageFromUrl(urlString: urlString, heightCConstraint: nil)
    }

    public func imageFromUrl(urlString: String, heightCConstraint: NSLayoutConstraint?) {
        if let data = ImageLoadingCache.sharedCache.object(forKey: urlString as AnyObject) as? Data {
            self.image = UIImage(data: data)

        } else {
            let sessionConfig = URLSessionConfiguration.default
            let url = URL(string: urlString)
            let session = URLSession(configuration: sessionConfig)
            let request = URLRequest(url: url!)
            updateHeightConstreint(heightCConstraint: heightCConstraint)
            let dataTask = session.dataTask(with: request) { responseData, _, error in
                if let error = error {
                    print(error)
                } else {
                    if let data = responseData {
                        ImageLoadingCache.sharedCache.setObject(data as AnyObject, forKey: urlString as AnyObject)
                        DispatchQueue.main.async {
                            self.image = UIImage(data: data)
                            self.updateHeightConstreint(heightCConstraint: heightCConstraint)
                        }
                    } else {
                        print("no data")
                    }
                }
            }

            dataTask.resume()
        }
    }

    private func updateHeightConstreint(heightCConstraint: NSLayoutConstraint?) {
        guard let heightCConstraint = heightCConstraint, let image = self.image else {
            return
        }
        let width = image.size.width
        let height = image.size.height
        heightCConstraint.constant = height*self.frame.size.width/width

    }
}

extension String {

    public func imageFromUrlAsync(_ completion: ((UIImage?) -> Void)?) {
        guard let url = URL(string: self) else {
            return
        }
        if let data = ImageLoadingCache.sharedCache.object(forKey: self as AnyObject) as? Data {
            async_main {
                completion?(UIImage(data: data))
            }
            return
        }
        let sessionConfig = URLSessionConfiguration.default
        let session = URLSession(configuration: sessionConfig)
        let request = URLRequest(url: url)

        let dataTask = session.dataTask(with: request) { responseData, _, error in
            if let error = error {
                print(error)
            } else {
                async_main {
                    if let data = responseData {
                        ImageLoadingCache.sharedCache.setObject(data as AnyObject, forKey: self as AnyObject)
                        completion?(UIImage(data: data))
                    } else {
                        completion?(nil)
                        print("no data")
                    }
                }
            }
        }
        dataTask.resume()
    }

    public func getImageFromCache() -> UIImage? {
        if let data = ImageLoadingCache.sharedCache.object(forKey: self as AnyObject) as? Data {
            return UIImage(data: data)
        } else {
            return nil
        }
    }

    public func getAsyncImageFromUrl(completion: @escaping (UIImage?, String) -> Void) {//
        async {
            if let data = ImageLoadingCache.sharedCache.object(forKey: self as AnyObject) as? Data {
                async_main {
                    completion(UIImage(data: data), self)
                }
                return
            }
            self.forceLoadImageFromUrl(completion: completion)
        }
    }
    
    func forceLoadImageFromUrl(completion: @escaping (UIImage?, String) -> Void) {
        let sessionConfig = URLSessionConfiguration.default
        let url = URL(string: self)
        let session = URLSession(configuration: sessionConfig)
        let request = URLRequest(url: url!)
        
        let dataTask = session.dataTask(with: request) { responseData, _, error in
            if let error = error {
                print(error)
            } else {
                async_main {
                    if let data = responseData {
                        ImageLoadingCache.sharedCache.setObject(data as AnyObject, forKey: self as AnyObject)
                        completion(UIImage(data: data), self)
                    } else {
                        completion(nil, self)
                        print("no data")
                    }
                }
            }
        }
        dataTask.resume()
    }

    public func dataFromUrlSync(timeout: Int = 10) -> Data! {

        let sessionConfig = URLSessionConfiguration.default
        let url = URL(string: self)
        let session = URLSession(configuration: sessionConfig)
        let request = URLRequest(url: url!)

        var asyncData: Data!

        let sema = DispatchSemaphore(value: 0)
        let dataTask = session.dataTask(with: request) { responseData, _, error in
            if let error = error {
                print(error)
            } else {
                if let data = responseData {
                    asyncData = data
                } else {
                    print("no data")
                }
            }
            sema.signal()
        }
        dataTask.resume()

        _ = sema.wait(timeout: .now() + Double(timeout))
        return asyncData
    }

    public func imageFromUrlSync(timeout: Int = 20) -> UIImage! {
        if let data = ImageLoadingCache.sharedCache.object(forKey: self as AnyObject) as? Data {
            return UIImage(data: data)
        }
        let sessionConfig = URLSessionConfiguration.default
        let url = URL(string: self)
        let session = URLSession(configuration: sessionConfig)
        let request = URLRequest(url: url!)

        var syncImage: UIImage!

        let sema = DispatchSemaphore(value: 0)
        let dataTask = session.dataTask(with: request) { responseData, response, error in
            if let error = error {
                if let response = response as? HTTPURLResponse {
                    print(response.description)
                    print(response.allHeaderFields)
                    print(response.statusCode)
                }
                print(error)
            } else {
                if let data = responseData {
                    ImageLoadingCache.sharedCache.setObject(data as AnyObject, forKey: self as AnyObject)
                    syncImage = UIImage(data: data)
                } else {
                    print("no data")
                }
            }
        }
        dataTask.resume()

        _ = sema.wait(timeout: .now() + .seconds(timeout))
        return syncImage
    }
}
<file_sep>//
//  FeedLoadMoreFooterView.swift
//  VKDemo
//
//  Created by <NAME> on 05.11.2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit

class FeedLoadMoreFooterView: UIView {
    @IBOutlet weak var loadingLabel: UILabel!
    @IBOutlet weak var copyLoadingLabel: UILabel!
    
    @IBOutlet weak var borderView: UIView! {
        didSet {
            borderView.layer.cornerRadius = borderView.frame.size.height / 2.0
            borderView.layer.borderColor = UIColor.blueMain.withAlphaComponent(0.6).cgColor
            borderView.layer.borderWidth = CGFloat(1)
        }
    }
    
    override func didMoveToSuperview() {
        startAnimating()
    }
    
    lazy var gradientLayer: CAGradientLayer = {
        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = [UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor]
        gradientLayer.locations = [0, 0.5, 1]
        gradientLayer.frame = copyLoadingLabel.frame
        
        let angle = 45 * CGFloat.pi / 180
        gradientLayer.transform = CATransform3DMakeRotation(angle, 0, 0, 1)
        return gradientLayer
    }()
    
    private func startAnimating() {
        let animation = CABasicAnimation(keyPath: "transform.translation.x")
        animation.duration = 3
        animation.fromValue = -2*loadingLabel.frame.width
        animation.toValue = loadingLabel.frame.width
        animation.repeatCount = Float.infinity
        
        gradientLayer.add(animation, forKey: "transform.x")
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        loadingLabel.layer.mask = gradientLayer
    }
    
    override init(frame: CGRect) {
        fatalError("Not for initializng in code")
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
}
<file_sep>//
//  MessagesManager.swift
//  VKDemo
//
//  Created by <NAME> on 01.11.2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import Moya

class MessagesManager {
    private let messagesProvider: MoyaProvider<MessagesTarget>
    private let messagesParser: IMessagesParser
    
    init(messagesProvider: MoyaProvider<MessagesTarget>,
               messagesParser: IMessagesParser) {
        self.messagesProvider = messagesProvider
        self.messagesParser = messagesParser
    }
}

extension MessagesManager: IMessagesManager {
    func getConversations(success: @escaping ([FeedItem]) -> Void,
                          failure: @escaping FailureClosure) {
        
        _ = messagesProvider.request(.getConversations, success: { (conversationsResponse: ConversationsResponse) in
            print("HOORAY!!!!!!! CONVERSATIONS ARE OK")
            print(conversationsResponse)
        }, failure: { error in
            print("OMG! SO BAD! CONVERSATIONS FAILED !! ERROR:")
            print(error)
        })
    }
}
<file_sep>//
//  MessagesTarget.swift
//  VKDemo
//
//  Created by <NAME> on 01.11.2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import Moya
import Alamofire

enum MessagesTarget {
    case getConversations
}

extension MessagesTarget: TargetType {
    
    private var sessionManager: IUserSessionManager {
        return  DI.container.core.sessionManager
    }
    
    var baseURL: URL {
        return URL(string: "https://api.vk.com/method/")!
    }
    
    var path: String {
        switch self {
        case .getConversations:
            return "messages.getConversations"
        }
    }
    
    var method: HTTPMethod {
        switch self {
        default:
            return .get
        }
    }
    
    var sampleData: Data {
        return Data()
    }
    
    var task: Task {
        switch self {
        case .getConversations:
            return .requestParameters(parameters: ["v": 5.108, "access_token": sessionManager.getAccessToken() ?? "", "count" : "200"], encoding: URLEncoding.queryString)
        default:
            return .requestPlain
        }
    }
    
    var headers: [String : String]? {
        switch self {
        default:
            return nil
        }
    }
}

extension MessagesTarget: VKAccessTokenAuthorizable {
    var authorizationType: VKAuthorizationType {
        return .oauth
    }
}
<file_sep>//
//  UIButton+BackgroundColor.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit

extension UIButton {
    func setBackgroundColor(color: UIColor, forState: UIControl.State) {
        self.clipsToBounds = true
        UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
        if let context = UIGraphicsGetCurrentContext() {
            context.setFillColor(color.cgColor)
            context.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
            let colorImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            self.setBackgroundImage(colorImage, for: forState)
        }
    }
}
<file_sep>//
//  DocDto.swift
//  VKDemo
//
//  Created by <NAME> on 12/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct DocDto: Codable {
    let id: Int
    let ownerId: Int
    let title: String
    let size: Int
    let ext: String
    let url: String
    let date: Int
    let type: Int
    let preview: DocPreview
    
    private enum CodingKeys: String, CodingKey {
        case id
        case ownerId = "owner_id"
        case title
        case size
        case ext
        case url
        case date
        case type
        case preview
    }
}

struct DocPreview: Codable {
    let photo: DocPreviewPhoto?
    let audioMessage: DocPreviewAudio?
    let video: DocPreviewVideo?
    
    private enum CodingKeys: String, CodingKey {
        case photo
        case audioMessage = "audio_message"
        case video
    }
}

struct DocPreviewPhoto: Codable {
    let sizes: [DocPhotoSizeCopy]
}

struct DocPreviewVideo: Codable {
    let src: String
    let width: Int
    let height: Int
    let fileSize: Int
    
    private enum CodingKeys: String, CodingKey {
        case src
        case width
        case height
        case fileSize = "file_size"
    }
}

struct DocPhotoSizeCopy: Codable {
    let src: String
    let width: Int
    let height: Int
    let type: String
}

struct DocPreviewAudio: Codable {
    let duration: Int
    let waveform: [Int]
    let linkOgg: String
    let linkMp3: String
    
    private enum CodingKeys: String, CodingKey {
        case duration
        case waveform
        case linkOgg = "link_ogg"
        case linkMp3 = "link_mp3"
    }
}
<file_sep>//
//  VKAccessTokenPlugin.swift
//  VKDemo
//
//  Created by <NAME> on 10/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import Result
import Moya

public protocol VKAccessTokenAuthorizable {
    var authorizationType: VKAuthorizationType { get }
}

public enum VKAuthorizationType {
    case none
    case oauth
}

public struct VKAccessTokenPlugin: PluginType {
    
    let userSessionManager: IUserSessionManager
    
    init(userSessionManager: IUserSessionManager) {
        self.userSessionManager = userSessionManager
    }
    
    public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
//         guard let authorizable = target as? VKAccessTokenAuthorizable else {
//            return request
//        }
//
//        var changedRequest = request
//
//        switch authorizable.authorizationType {
//        case .oauth:
//            guard let accessToken = userSessionManager.getAccessToken() else {
//                break
//            }
////            changedRequest.setValue(accessToken, forHTTPHeaderField: "access_token")
////            changedRequest.allHTTPHeaderFields?["access_token"] = accessToken
//            let accessTokenPathComponent = "access_token=\(accessToken)"
//            changedRequest.url?.appendPathComponent(accessTokenPathComponent)
//
//        default:
//            break
//        }
//
//        return changedRequest
        
        return request
    }
    
    public func willSend(_ request: RequestType, target: TargetType) {
        print(request)
    }
}
<file_sep>//
//  ViperModuleTransitionHandler.swift
//  Vanilla
//
//  Created by <NAME> on 10/05/2017.
//  Copyright © 2017 BrightBox. All rights reserved.
//

import UIKit

public enum TransitionStyle {
    case push
    case modal
    case fade
}

public typealias ConfigurationBlock = (ViperModuleInputProtocol) -> Void

public protocol ViperModuleTransitionHandler: class {
    func openModule(segueIdentifier: String, configurationBlock: @escaping ConfigurationBlock)
    func openModule(segueIdentifier: String, source: ViperModuleOutputProtocol)
    func openModule(segueIdentifier: String)
    func openModule(vc: UIViewController, style: TransitionStyle, completion: (() -> Swift.Void)?)
    func openModule(transitionHandler: ViperModuleTransitionHandler, style: TransitionStyle)
    func closeModule()
    func popModule()
    func popToRootViewController()
    func swapAndPopToRootViewController(vc: UIViewController)
    func swapToViewController(vc: UIViewController)
}

extension ViperModuleTransitionHandler {
    func openModule(vc: UIViewController, style: TransitionStyle, completion: (() -> Swift.Void)? = nil) {
        self.openModule(vc: vc, style: style, completion: completion)
    }
}

public extension ViperModuleTransitionHandler where Self: UIViewController {
    func openModule(segueIdentifier: String, configurationBlock: @escaping ConfigurationBlock) {
        performSegue(withIdentifier: segueIdentifier, sender: configurationBlock)
    }

    func openModule(segueIdentifier: String, source: ViperModuleOutputProtocol) {
        performSegue(withIdentifier: segueIdentifier, sender: source)
    }

    func openModule(segueIdentifier: String) {
        performSegue(withIdentifier: segueIdentifier, sender: nil)
    }

    func openModule(vc: UIViewController, style: TransitionStyle, completion: (() -> Swift.Void)? = nil) {
        switch style {
        case .modal:
            self.present(vc, animated: true, completion: completion)
        case .push:
            self.navigationController?.pushViewController(vc, animated: true)
            defer {
                completion?()
            }
        case .fade:
            vc.modalTransitionStyle = .crossDissolve
            if let nc = self.navigationController {
                nc.present(vc, animated: true, completion: completion)
            } else {
                self.present(vc, animated: true, completion: completion)
            }
        }
    }

    func openModule(transitionHandler: ViperModuleTransitionHandler, style: TransitionStyle) {
        guard let vc = transitionHandler as? UIViewController else {
            return
        }

        switch style {
        case .modal:
            self.present(vc, animated: true, completion: nil)
        case .push:
            self.navigationController?.pushViewController(vc, animated: true)
        case .fade:
            vc.modalTransitionStyle = .crossDissolve
            self.present(vc, animated: true, completion: nil)
        }
    }

    //TODO: Подумать как сохранять TransitionStyle и уходить с экрана соответственно
    func closeModule() {
        if presentingViewController != nil {
            dismiss(animated: true, completion: nil)
        } else {
            let animated = presentedViewController == nil
            navigationController?.popToRootViewController(animated: animated)
        }
    }

    func popModule() {
        self.navigationController?.popViewController(animated: true)
    }

    func popToRootViewController() {
        self.navigationController?.popToRootViewController(animated: true)
    }

    func swapAndPopToRootViewController(vc: UIViewController) {
        self.navigationController?.viewControllers = [vc]
        self.navigationController?.popToRootViewController(animated: true)
    }

    func swapToViewController(vc: UIViewController) {
        CATransaction.begin()
        CATransaction.setCompletionBlock {
            if let count = self.navigationController?.viewControllers.count,
                count > 1 {
                self.navigationController?.viewControllers.remove(at: count - 2)
            }
        }
        self.navigationController?.pushViewController(vc, animated: true)
        CATransaction.commit()
    }

    func handleSegue(_ segue: UIStoryboardSegue, sender: Any?) {
        if sender == nil {
            return
        }

        guard let configurationHolder = segue.destination as? ModuleViewWithOutput else {
            fatalError("Destination should conform 'ModuleViewWithOutput'")
        }

        if let source = sender as? ViperModuleOutputProtocol {
            configurationHolder.presenter.output = source
        } else if let configure = sender as? ConfigurationBlock {
            configure(configurationHolder.presenter)
        }
    }
}
<file_sep>//
//  UIViewController+Style.swift
//  Vanilla
//
//  Created by Alexander on 06.08.2018.
//  Copyright © 2018 BrightBox. All rights reserved.
//

import UIKit

enum BarStyle {
    case standard
    case standardNoSeparator
    case translucent // With gradient
    case transparent
    case hidden
    case standardLargeTitles
}

extension UIViewController {

    @available(*, deprecated, message: "Use setNavBarStyle(_ style: BarStyle) from BaseViewController superclass instead")
    func setBarStyle(_ style: BarStyle) {
        navigationItem.backBarButtonItem?.title = ""

        switch style {
        case .standard:
            navigationController?.isNavigationBarHidden = false
            navigationController?.navigationBar.backgroundColor = UIColor.blueMain
            navigationController?.navigationBar.barTintColor = UIColor.blueMain
            setSeparatorHidden(false)
            setLargeTitlesEnabled(false)
        case .standardNoSeparator:
            navigationController?.isNavigationBarHidden = false
            navigationController?.navigationBar.backgroundColor = UIColor.blueMain
            navigationController?.navigationBar.barTintColor = UIColor.blueMain
            setSeparatorHidden(true)
            setLargeTitlesEnabled(false)
        case .translucent:
            navigationController?.isNavigationBarHidden = true
        case .transparent:
            navigationController?.navigationBar.isTranslucent = true
            navigationController?.navigationBar.backgroundColor = .clear
            navigationController?.navigationBar.barTintColor = .clear
            setSeparatorHidden(true)
        case .hidden:
            navigationController?.isNavigationBarHidden = true
        case .standardLargeTitles:
            navigationController?.isNavigationBarHidden = false
            navigationController?.navigationBar.backgroundColor = UIColor.blueMain
            navigationController?.navigationBar.barTintColor = UIColor.blueMain
            setSeparatorHidden(false)
            setLargeTitlesEnabled(true)
        }
        navigationController?.navigationBar.isExclusiveTouch = true
    }

    private func setLargeTitlesEnabled(_ enabled: Bool) {
        guard #available(iOS 11.0, *) else {
            return
        }

        if navigationController?.navigationBar.prefersLargeTitles == true {
            navigationItem.largeTitleDisplayMode = enabled ? .automatic : .never
        } else if enabled {
            navigationController?.navigationBar.prefersLargeTitles = true
        }
    }

    func setBarTitle(_ title: String?) {
        navigationItem.title = title
    }

    func setSeparatorHidden(_ hidden: Bool) {
        if #available(iOS 11.0, *) {
            navigationController?.navigationBar.shadowImage = (hidden ? UIImage() : nil)
        } else {
            navigationController?.navigationBar.shadowImage = (hidden ? UIImage() : nil)
            navigationController?.navigationBar.setBackgroundImage((hidden ? UIImage() : nil), for: .default)
        }
    }
}
<file_sep>//
//  CoordinatorContainer.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

protocol CoordinatorContainerProtocol {
    var appCoordinator: IAppCoordinator { get }
    var mainTabBarCoordinator: IMainTabBarCoordinator { get }
}

class CoordinatorContainer {
    private let serviceContainer = ServiceContainer()
    
    init() {
        let mainTabBarCoordinator = MainTabBarCoordinator()
        serviceContainer.add(services: mainTabBarCoordinator)
        
        let appCoordinator = AppCoordinator()
        serviceContainer.add(services: appCoordinator)
    }
}

extension CoordinatorContainer: CoordinatorContainerProtocol {
    var mainTabBarCoordinator: IMainTabBarCoordinator {
        return serviceContainer.get(service: MainTabBarCoordinator.self)!
    }
    
    var appCoordinator: IAppCoordinator {
        return serviceContainer.get(service: AppCoordinator.self)!
    }
}
<file_sep>//
//  DIContainer.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

protocol DIContainerProtocol {
    var core: CoreContainerProtocol { get }
    var data: DataContainerProtocol { get }
    var dao: DaoContainerProtocol { get }
    var presentation: PresentationContainerProtocol { get }
    var parser: ParserContainerProtocol { get }
    var config: ConfigContainerProtocol { get }
    var coordinator: CoordinatorContainerProtocol { get }
}

class DIContainer: DIContainerProtocol {
    
    internal var core: CoreContainerProtocol
    internal var data: DataContainerProtocol
    internal var dao: DaoContainerProtocol
    internal var presentation: PresentationContainerProtocol
    internal var parser: ParserContainerProtocol
    internal var config: ConfigContainerProtocol
    var coordinator: CoordinatorContainerProtocol
    
    init() {
        self.parser = ParserContainer()
        self.config = ConfigContainer()
        self.dao = DaoContainer()
        self.core = CoreContainer(dao: self.dao)
        self.presentation = PresentationContainer()
        self.data = DataContainer(core: self.core,
                                  parser: self.parser)
        self.coordinator = CoordinatorContainer()
    }
}
<file_sep>//
//  ViperModuleInput.swift
//  Vanilla
//
//  Created by <NAME> on 11/05/2017.
//  Copyright © 2017 BrightBox. All rights reserved.
//

import Foundation

public protocol ViperModuleInputProtocol: class {
    var output: ViperModuleOutputProtocol! { get set }
}
<file_sep>import PlaygroundSupport
import UIKit

var str = "Hello, playground"
#colorLiteral(red: 0, green: 0.4784313725, blue: 1, alpha: 1)
//// Present the view controller in the Live View window

class LikeButton: UIButton {
    //// Color Declarations
    private let fillColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
    private let blueMain = UIColor(red: 0.294, green: 0.471, blue: 0.706, alpha: 1.000)

    
    
    private lazy var heartLayer: CAShapeLayer = {
        let layer = CAShapeLayer()
        
        func fastFloor(_ x: CGFloat) -> CGFloat { return floor(x) }
        
        //// Subframes
        let group: CGRect = CGRect(x: frame.minX + 72, y: frame.minY + fastFloor((frame.height - 56) * 0.10784 + 0.5), width: fastFloor((frame.width - 72) * 0.74149 + 0.5), height: frame.height - 56 - fastFloor((frame.height - 56) * 0.10784 + 0.5))
        
        let bezierPath = UIBezierPath()
        bezierPath.move(to: CGPoint(x: group.minX + 0.27104 * group.width, y: group.minY + 1.00000 * group.height))
        bezierPath.addLine(to: CGPoint(x: group.minX + 0.23309 * group.width, y: group.minY + 0.92797 * group.height))
        bezierPath.addCurve(to: CGPoint(x: group.minX + 0.00000 * group.width, y: group.minY + 0.30023 * group.height), controlPoint1: CGPoint(x: group.minX + 0.09215 * group.width, y: group.minY + 0.67688 * group.height), controlPoint2: CGPoint(x: group.minX + 0.00000 * group.width, y: group.minY + 0.50766 * group.height))
        bezierPath.addCurve(to: CGPoint(x: group.minX + 0.14907 * group.width, y: group.minY + 0.00000 * group.height), controlPoint1: CGPoint(x: group.minX + 0.00000 * group.width, y: group.minY + 0.13101 * group.height), controlPoint2: CGPoint(x: group.minX + 0.06505 * group.width, y: group.minY + 0.00000 * group.height))
        bezierPath.addCurve(to: CGPoint(x: group.minX + 0.27104 * group.width, y: group.minY + 0.11463 * group.height), controlPoint1: CGPoint(x: group.minX + 0.19515 * group.width, y: group.minY + 0.00000 * group.height), controlPoint2: CGPoint(x: group.minX + 0.24122 * group.width, y: group.minY + 0.04367 * group.height))
        bezierPath.addCurve(to: CGPoint(x: group.minX + 0.39301 * group.width, y: group.minY + 0.00000 * group.height), controlPoint1: CGPoint(x: group.minX + 0.30085 * group.width, y: group.minY + 0.04367 * group.height), controlPoint2: CGPoint(x: group.minX + 0.34693 * group.width, y: group.minY + 0.00000 * group.height))
        bezierPath.addCurve(to: CGPoint(x: group.minX + 0.54208 * group.width, y: group.minY + 0.30023 * group.height), controlPoint1: CGPoint(x: group.minX + 0.47703 * group.width, y: group.minY + 0.00000 * group.height), controlPoint2: CGPoint(x: group.minX + 0.54208 * group.width, y: group.minY + 0.13101 * group.height))
        bezierPath.addCurve(to: CGPoint(x: group.minX + 0.30898 * group.width, y: group.minY + 0.92797 * group.height), controlPoint1: CGPoint(x: group.minX + 0.54208 * group.width, y: group.minY + 0.50766 * group.height), controlPoint2: CGPoint(x: group.minX + 0.44992 * group.width, y: group.minY + 0.67688 * group.height))
        bezierPath.addLine(to: CGPoint(x: group.minX + 0.27104 * group.width, y: group.minY + 1.00000 * group.height))
        bezierPath.close()
        UIColor.white.setFill()
        bezierPath.fill()
        UIColor.lightGray.setStroke()
        bezierPath.lineWidth = 6.5
        bezierPath.miterLimit = 4
        bezierPath.stroke()

        layer.path = bezierPath.cgPath
        return layer
    }()
    
    override init(frame: CGRect) {
      super.init(frame: frame)
      configureLayers()
    }
    
    required init?(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      configureLayers()
    }
    
    private func configureLayers() {
        backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
        layer.addSublayer(heartLayer)
    }
}

let likeButton = LikeButton(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
PlaygroundPage.current.liveView = likeButton
<file_sep>//
//  ParseContainer.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

protocol ParserContainerProtocol {
    var feedParser: IFeedParser { get }
    var messagesParser: IMessagesParser { get }
}

class ParserContainer: ParserContainerProtocol {
    var feedParser: IFeedParser {
        return FeedParser()
    }
    
    var messagesParser: IMessagesParser {
        return MessagesParser()
    }
}
<file_sep>//
//  SignInViewController.swift
//  VKDemo
//
//  Created by <NAME> on 27/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit
import RxSwift
import RxCocoa
import WebKit
import RxWebKit

class SignInViewController: UIViewController {
    var viewModel: SignInViewModel!
    private let disposeBag = DisposeBag()
    @IBOutlet weak var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let url = URL(string: "https://oauth.vk.com/authorize?client_id=5513701&display=mobile&redirect_uri=https://oauth.vk.com/blank.html&scope=friends,photos,status,notes,messages,wall,groups,notifications,stats,messages&response_type=token&v=5.95&state=123456") else {
            return
        }
        
        webView.load(URLRequest(url: url))
        
        webView.rx
            .decidePolicyNavigationAction
            .asObservable()
            .do(onNext: { webView, action, handler in
                handler(.allow)
            }, onError: { _ in
                print("22")
            }, onCompleted: {
                print("11")
            })
            .filter({ arg -> Bool in
                let (_, action, _) = arg
                guard let url = action.request.url else {
                    return false
                }
                return url.absoluteString.contains("#")
            })
            .flatMap({ _, action, handler -> Observable<URL> in
                return Observable.just(action.request.url!)
            })
            .bind(to: viewModel.decidePolicyNavigationActionObserver)
            .disposed(by: disposeBag)
    }
}
<file_sep>//
//  ChatPresenter.swift
//  VKDemo
//
//  Created by AntonKrylovBrightBox on 10/06/2019.
//  Copyright © 2019 BrightBox. All rights reserved.
//

class ChatPresenter {
    private weak var view: ChatViewInput?
    private let router: ChatRouterInput
    
    private let messagesManager: IMessagesManager

    init(view: ChatViewInput,
         router: ChatRouterInput,
         messagesManager: IMessagesManager) {
        self.view = view
        self.router = router
        self.messagesManager = messagesManager
    }
}

extension ChatPresenter: ChatViewOutput {
    func viewLoaded() {
        
    }
}
<file_sep>//
//  PhotoDto.swift
//  VKDemo
//
//  Created by <NAME> on 12/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct PhotoDto: Codable {
    let id: Int
    let albumId: Int
    let ownerId: Int
    let userId: Int?
    let text: String
    let date: Int
    let sizes: [PhotoSizeCopy]
    
    private enum CodingKeys: String, CodingKey {
        case id
        case albumId = "album_id"
        case ownerId = "owner_id"
        case userId = "user_id"
        case text
        case date
        case sizes
    }
}

extension Array where Element == PhotoSizeCopy {
    func correctSize() -> PhotoSizeCopy? {
        return [self.first(where: { $0.type == "z" }),
                self.first(where: { $0.type == "y" }),
                self.first(where: { $0.type == "x" }),
                self.first(where: { $0.type == "w" })]
            .compactMap({$0})
            .first
    }
}


struct PhotoSizeCopy: Codable {
    let type: String
    let url: String
    let width: Int
    let height: Int
}

struct PhotosListDto: Codable {
    let count: Int
    let items: [ExtendedPhotoDto]
}

struct ExtendedPhotoDto: Codable {
    let id: Int
    let albumId: Int
    let ownerId: Int
    let userId: Int?
    let text: String
    let date: Int
    let sizes: [PhotoSizeCopy]
    let accessKey: String
    let likes: LikesCountDto
    let reposts: RepostCountDto
    let comments: CommentsCountDto
    let canComment: BoolInt
    let canRepost: BoolInt
    
    private enum CodingKeys: String, CodingKey {
        case id
        case albumId = "album_id"
        case ownerId = "owner_id"
        case userId = "user_id"
        case text
        case date
        case sizes
        case accessKey = "access_key"
        case likes
        case reposts
        case comments
        case canComment = "can_comment"
        case canRepost = "can_repost"
    }
}
<file_sep>//
//  ChatViewInput.swift
//  VKDemo
//
//  Created by AntonKrylovBrightBox on 10/06/2019.
//  Copyright © 2019 BrightBox. All rights reserved.
//

protocol ChatViewInput: AnyObject {
    func setup()
}
<file_sep>//
//  ServiceContainer.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

class ServiceContainer {
    private lazy var container = [String: Any]()

    func add(services: Any...) {
        for service in services {
            container["\(type(of: service))"] = service
        }
    }

    func get<T>(service: T.Type) -> T? {
        return container["\(T.self)"] as? T
    }
}
<file_sep>//
//  IUserSessionManager.swift
//  VKDemo
//
//  Created by <NAME> on 28/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//f

import Foundation

protocol IUserSessionManager {
    func clearSession()
    
    func getAccessToken() -> String?
    
    func saveAccessToken(_ accessToken: String?)
}
<file_sep>//
//  ConfigContainer.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

protocol ConfigContainerProtocol {
}

class ConfigContainer: ConfigContainerProtocol {
}
<file_sep>//
//  IMessagesManager.swift
//  VKDemo
//
//  Created by <NAME> on 01.11.2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

protocol IMessagesManager {
    func getConversations(success: @escaping ([FeedItem]) -> Void,
                          failure: @escaping FailureClosure)
}
<file_sep>//
//  AttachmentVideoCollectionViewCell.swift
//  VKDemo
//
//  Created by <NAME> on 28/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit

class AttachmentVideoCollectionViewCell: UICollectionViewCell {
    static let cellName: String = String(describing: AttachmentVideoCollectionViewCell.self)
    
    var videoDto: VideoDto?
    var photoUrlString: String?
    
    @IBOutlet weak var imageView: UIImageView!
    
    func setup(videoDto: VideoDto) {
        self.videoDto = videoDto
        
        guard let appropriatePhotoUrlString = [videoDto.photo1280,
                                         videoDto.photo800,
                                         videoDto.photo640,
                                         videoDto.photo320]
                                        .compactMap({$0})
                                        .first
            else {
                imageView.image = nil
                photoUrlString = nil
                return
        }
        
        photoUrlString = appropriatePhotoUrlString
        
        appropriatePhotoUrlString.getAsyncImageFromUrl { [weak self] image, url in
            guard let strongSelf = self,
                let photoUrlString = strongSelf.photoUrlString,
                photoUrlString == url,
                let image = image,
                let strongImageView = strongSelf.imageView  else {
                return
            }
            
            UIView.transition(with: strongImageView,
                              duration: 0.3,
                              options: .transitionCrossDissolve,
                              animations: {
                strongImageView.image = image
            }, completion: nil)
        }

    }
}
<file_sep>//
//  MappedResponse.swift
//  VKDemo
//
//  Created by <NAME> on 10/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct MappedResponse<T: Codable>: Codable {
    let response: T
}
<file_sep>//
//  ViperModuleInitializer.swift
//  Vanilla
//
//  Created by <NAME> on 12/7/17.
//  Copyright © 2017 BrightBox. All rights reserved.
//

import Foundation
import UIKit

//TODO: remove ViperModuleInitializer protocol after deleting storyboards
public protocol ViperModuleInitializer: class {
    var viewController: UIViewController? { get }
}

public protocol ViperInitializer: class {
    func createModule(values: [Any])
}

public extension ViperModuleInitializer where Self: NSObject {
    public static func assemble(values: Any...) -> Self {
        let instance = Self.init()
        if let instance = instance as? ViperInitializer {
            instance.createModule(values: values)
        } else {
            instance.awakeFromNib()
        }

        return instance
    }

    public var viewController: UIViewController? {
        return value(forKey: "viewController") as? UIViewController
    }
}
<file_sep>//
//  CoreContainer.swift
//  VKDemo
//
//  Created by <NAME> on 15/05/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

protocol CoreContainerProtocol {
    var sessionManager: UserSessionManager { get }
}

class CoreContainer: CoreContainerProtocol {
    private let serviceContainer = ServiceContainer()
    let dao: DaoContainerProtocol

    init(dao: DaoContainerProtocol) {
        self.dao = dao
        
        let sessionManager = UserSessionManager(sessionDao: dao.sessionDao)
        serviceContainer.add(services: sessionManager)
    }
    
    var sessionManager: UserSessionManager {
        return serviceContainer.get(service: UserSessionManager.self)!
    }
}
<file_sep>//
//  UIColor+Factory.swift
//  VKDemo
//
//  Created by <NAME> on 10/06/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit.UIColor

extension UIColor {
    static var blueMain: UIColor {
        return UIColor(named: "blueMain")!
    }
    
    static var redMain: UIColor {
        return UIColor(named: "redMain")!
    }
}
<file_sep>//
//  UIView+InCallStatusBar.swift
//  Vanilla
//
//  Created by <NAME> on 14.08.2018.
//  Copyright © 2018 BrightBox. All rights reserved.
//

import UIKit

extension UIView {
    func setInCallSafeFrame() {
        self.autoresizingMask = [.flexibleHeight, .flexibleTopMargin]
    }
}
<file_sep>//
//  LikesCountResponse.swift
//  VKDemo
//
//  Created by <NAME> on 07.11.2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation

struct LikesCountResponse: Codable {
    let likesCount: Int
    
    private enum CodingKeys: String, CodingKey {
        case likesCount = "likes"
    }
}
<file_sep>//
//  AttachmentsCollectionViewDataSource.swift
//  VKDemo
//
//  Created by <NAME> on 25/08/2019.
//  Copyright © 2019 <NAME>. All rights reserved.
//

import Foundation
import UIKit

class AttachmentsCollectionViewDataSource: NSObject {
    
    weak var output: FeedViewOutput?
    var attachments: [AttachmentDto]?
    
    weak var collectionView: UICollectionView!{
        didSet {
            initialSetup()
        }
    }
    
    func generateLayout() -> UICollectionViewLayout {
      // We have three row styles
      // Style 1: 'Full'
      // A full width photo
      // Style 2: 'Main with pair'
      // A 2/3 width photo with two 1/3 width photos stacked vertically
      // Style 3: 'Triplet'
      // Three 1/3 width photos stacked horizontally

      // Full
//      let fullPhotoItem = NSCollectionLayoutItem(
//        layoutSize: NSCollectionLayoutSize(
//          widthDimension: .fractionalWidth(1.0),
//          heightDimension: .fractionalWidth(3/2)))
//      fullPhotoItem.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
//
//      // Main with pair
//      let mainItem = NSCollectionLayoutItem(
//        layoutSize: NSCollectionLayoutSize(
//          widthDimension: .fractionalWidth(2/3),
//          heightDimension: .fractionalHeight(1.0)))
//      mainItem.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
//
//      let pairItem = NSCollectionLayoutItem(
//        layoutSize: NSCollectionLayoutSize(
//          widthDimension: .fractionalWidth(1.0),
//          heightDimension: .fractionalHeight(0.5)))
//      pairItem.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
//      let trailingGroup = NSCollectionLayoutGroup.vertical(
//        layoutSize: NSCollectionLayoutSize(
//          widthDimension: .fractionalWidth(1/3),
//          heightDimension: .fractionalHeight(1.0)),
//        subitem: pairItem,
//        count: 2)
//
//      let mainWithPairGroup = NSCollectionLayoutGroup.horizontal(
//        layoutSize: NSCollectionLayoutSize(
//          widthDimension: .fractionalWidth(1.0),
//          heightDimension: .fractionalWidth(4/9)),
//        subitems: [mainItem, trailingGroup])
//
//      // Triplet
//      let tripletItem1 = NSCollectionLayoutItem(
//        layoutSize: NSCollectionLayoutSize(
//            widthDimension: .fractionalWidth(1/3.35),
//          heightDimension: .fractionalHeight(1.0)))
//
//        let tripletItem2 = NSCollectionLayoutItem(
//        layoutSize: NSCollectionLayoutSize(
//            widthDimension: .fractionalWidth(1.35/3.35),
//          heightDimension: .fractionalHeight(1.0)))
//
//        let tripletItem3 = NSCollectionLayoutItem(
//        layoutSize: NSCollectionLayoutSize(
//            widthDimension: .fractionalWidth(1/3.35),
//          heightDimension: .fractionalHeight(1.0)))
//        tripletItem1.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
//        tripletItem2.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
//        tripletItem3.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
//
//      let tripletGroup = NSCollectionLayoutGroup.horizontal(
//        layoutSize: NSCollectionLayoutSize(
//          widthDimension: .fractionalWidth(1.0),
//          heightDimension: .fractionalWidth(2/3)),
//        subitems: [tripletItem1, tripletItem2, tripletItem3])
//
//      // Reversed main with pair
//      let mainWithPairReversedGroup = NSCollectionLayoutGroup.horizontal(
//        layoutSize: NSCollectionLayoutSize(
//          widthDimension: .fractionalWidth(1.0),
//          heightDimension: .fractionalWidth(4/9)),
//        subitems: [trailingGroup, mainItem])

        
        guard let photoAttachments = attachments?.filter({ $0.photo != nil }),
            photoAttachments.count > 0 else {
//            let layoutGroup = NSCollectionLayoutGroup(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
//                                                                                         heightDimension: .fractionalHeight(1.0)))
//            let section = NSCollectionLayoutSection(group: layoutGroup)
//            let layout = UICollectionViewCompositionalLayout(section: section)
            return UICollectionViewLayout()
        }

        
        var subgroups = [NSCollectionLayoutGroup]()
        var k = 0
        for _ in 1...3 {
            if k > photoAttachments.count - 1 { break }
            let j = Int.random(in: 1...4)
            let photo1 = (k < photoAttachments.count) ? photoAttachments[k].photo : photoAttachments.first?.photo!
            let photo2 = (k < photoAttachments.count - 1) ? photoAttachments[k+1].photo : photoAttachments.first?.photo!
            let photo3 = (k < photoAttachments.count - 2) ? photoAttachments[k+2].photo : photoAttachments.first?.photo!
            switch j {
            case 1:
                subgroups.append(generateSingleLayoutGroup(photoDto: photo1!))
                k += 1
            case 2:
                subgroups.append(generateMainWithPairLayoutGroup(photoDto1: photo1!, photoDto2: photo2!, photoDto3: photo3!))
                k += 3
            case 3:
                subgroups.append(generateTripletLayoutGroup(photoDto1: photo1!, photoDto2: photo2!, photoDto3: photo3!))
                k += 3
            case 4:
                break
            default:
                break
            }
        }
        
        let photo1 = (k < photoAttachments.count) ? photoAttachments[k].photo : photoAttachments.first?.photo!
        if subgroups.count == 0 { subgroups.append(generateSingleLayoutGroup(photoDto: photo1!)) }
        
        let nestedGroup = NSCollectionLayoutGroup.vertical(
            layoutSize: NSCollectionLayoutSize(
                widthDimension: .fractionalWidth(1.0),
                heightDimension: .fractionalHeight(1.0)),
            subitems: subgroups)
//            [fullPhotoItem, mainWithPairGroup, tripletGroup, mainWithPairReversedGroup])
        
        let section = NSCollectionLayoutSection(group: nestedGroup)
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }

    func generateSingleLayoutGroup(photoDto: PhotoDto) -> NSCollectionLayoutGroup {
        let width = collectionView.frame.width
        let photo = photoDto
        let photoSize = photo.sizes.correctSize()
        let photoRatio = CGFloat(photoSize?.height ?? 0) / CGFloat(photoSize?.width ?? 1)
        let photoHeight = photoRatio * width

        // Full
        let fullPhotoItem = NSCollectionLayoutItem(
          layoutSize: NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .fractionalWidth(3/2)))
        fullPhotoItem.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
        
        let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                                                                heightDimension: .fractionalWidth(photoRatio)),
                                                                                                subitem: fullPhotoItem,
                                                                                                count: 1)
        return layoutGroup
    }
    
    func generateMainWithPairLayoutGroup(photoDto1: PhotoDto, photoDto2: PhotoDto, photoDto3: PhotoDto) -> NSCollectionLayoutGroup {
        let width1 = collectionView.frame.width * 2.0 / 3.0
        let photo1 = photoDto1
        let photo1Size = photo1.sizes.correctSize()
        let photo1Ratio = CGFloat(photo1Size?.height ?? 0) / CGFloat(photo1Size?.width ?? 1)
        let photo1Height = photo1Ratio * width1
        
        let width2 = collectionView.frame.width / 3.0
        let photo2 = photoDto2
        let photo2Size = photo2.sizes.correctSize()
        let photo2Ratio = CGFloat(photo2Size?.height ?? 0) / CGFloat(photo2Size?.width ?? 1)
        let photo2Height = photo2Ratio * width2
        
        let width3 = collectionView.frame.width / 3.0
        let photo3 = photoDto3
        let photo3Size = photo2.sizes.correctSize()
        let photo3Ratio = CGFloat(photo3Size?.height ?? 0) / CGFloat(photo3Size?.width ?? 1)
        let photo3Height = photo3Ratio * width3
        
        // photo1Height < maxHeight
        // photo2Height 20% diff with photo3Height

        let mainItem = NSCollectionLayoutItem(
          layoutSize: NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(photo1Ratio),
            heightDimension: .fractionalHeight(1.0)))
        mainItem.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)

        let pairItem1 = NSCollectionLayoutItem(
          layoutSize: NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .fractionalHeight(photo2Ratio))) // меняем
        pairItem1.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
        
        let pairItem2 = NSCollectionLayoutItem(
          layoutSize: NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .fractionalHeight(photo3Ratio))) // меняем
        pairItem2.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
        
        let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0 - photo1Ratio),
            heightDimension: .fractionalHeight(1.0)), subitems: [pairItem1, pairItem2])

        let mainWithPairGroup = NSCollectionLayoutGroup.horizontal(
          layoutSize: NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .fractionalWidth(4/9)),
          subitems: [mainItem, trailingGroup])

        return mainWithPairGroup
    }
    
    func generateTripletLayoutGroup(photoDto1: PhotoDto, photoDto2: PhotoDto, photoDto3: PhotoDto) -> NSCollectionLayoutGroup {
        let width1 = collectionView.frame.width / 3.0
        let photo1 = photoDto1
        let photo1Size = photo1.sizes.correctSize()
        let photo1Ratio = CGFloat(photo1Size?.height ?? 0) / CGFloat(photo1Size?.width ?? 1)
        let photo1Height = photo1Ratio * width1
        
        let width2 = collectionView.frame.width / 3.0
        let photo2 = photoDto2
        let photo2Size = photo2.sizes.correctSize()
        let photo2Ratio = CGFloat(photo2Size?.height ?? 0) / CGFloat(photo2Size?.width ?? 1)
        let photo2Height = photo2Ratio * width2
        
        let width3 = collectionView.frame.width / 3.0
        let photo3 = photoDto3
        let photo3Size = photo3.sizes.correctSize()
        let photo3Ratio = CGFloat(photo3Size?.height ?? 0) / CGFloat(photo3Size?.width ?? 1)
        let photo3Height = photo3Ratio * width3
        
        // photo1Height < maxHeight photo2Height < maxHeight photo3Height < maxHeight
        // photo2Height 20% diff with photo3Height and photo1Height

        // Triplet
        let tripletItem1 = NSCollectionLayoutItem(
          layoutSize: NSCollectionLayoutSize(
              widthDimension: .fractionalHeight(photo1Ratio),
            heightDimension: .fractionalHeight(1.0)))

          let tripletItem2 = NSCollectionLayoutItem(
          layoutSize: NSCollectionLayoutSize(
              widthDimension: .fractionalHeight(photo2Ratio),
            heightDimension: .fractionalHeight(1.0)))
          
          let tripletItem3 = NSCollectionLayoutItem(
          layoutSize: NSCollectionLayoutSize(
              widthDimension: .fractionalHeight(photo3Ratio),
            heightDimension: .fractionalHeight(1.0)))
          tripletItem1.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
          tripletItem2.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
          tripletItem3.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)

        let tripletGroup = NSCollectionLayoutGroup.horizontal(
          layoutSize: NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .absolute(CGFloat(300))),
          subitems: [tripletItem1, tripletItem2, tripletItem3])
        
        return tripletGroup
        
        UIApplication().applicationState
    }
    
    func setup(attachments: [AttachmentDto]) {
        self.attachments = attachments
        collectionView.reloadData()
        collectionView.setCollectionViewLayout(generateLayout(), animated: false)
    }
    
    func collectionViewHeight(for attachments: [AttachmentDto]) -> CGFloat {
        if (attachments.count == 1),
            let photo = attachments.first?.photo,
            photo.sizes.count > 0 {
            let photoSizes = photo.sizes
            let photoSizesOrderArray = ["z", "y", "x", "w"]
            let properPhotoSize = photoSizes.correctSize()
            
//                photoSizes.sorted(by: {
//                guard let index0 = photoSizesOrderArray.index(of: $0.type) else { return false }
//                guard let index1 = photoSizesOrderArray.index(of: $1.type) else { return true }
//                return index0 <= index1
//                }).first
            
            let width = collectionView.frame.width
            let height = CGFloat(properPhotoSize?.height ?? 0) / CGFloat(properPhotoSize?.width ?? 1) * width
            
            return height
        }
            
            
        let height = CGFloat(120.0)
        let photosCount = attachments.filter({
            $0.type == .photo
            }).count
        
        return ceil(CGFloat(photosCount) / 2.0) * height
    }
    
    func createLayoutForPhotos(_ photos:[PhotoDto]) -> [CompositionalLayout] {
        for i in 4...1 {
            for j in 4...0 {
                for k in 4...0 {
                    guard i + j + k == photos.count,
                        (j != 0) || (k == 0) else {
                            continue
                            
                    }
                    
                    let row1Photos = Array(photos[0..<i])
                    let row2Photos = Array(photos[i..<j+i])
                    let row3Photos = Array(photos.dropFirst(j+i))
                    
                    createRowsLayout(row1: row1Photos,
                                     row2: row2Photos,
                                     row3: row3Photos)
                }
            }
        }
        
        return [CompositionalLayout]()
    }
    
    func createRowsLayout(row1: [PhotoDto], row2: [PhotoDto]?, row3: [PhotoDto]?) {
        
    }
}

struct CompositionalLayout {
    var rows: [RowLayout]
}

struct RowLayout {
    var cells: Any
}

// MARK: - Private functions
extension AttachmentsCollectionViewDataSource {
    private func initialSetup() {
        collectionView.delegate = self
        collectionView.dataSource = self
        
        collectionView.register(UINib(nibName: AttachmentVideoCollectionViewCell.cellName,
                                      bundle: nil),
                                forCellWithReuseIdentifier: AttachmentVideoCollectionViewCell.cellName)
        collectionView.register(UINib(nibName: AttachmentPhotoCollectionViewCell.cellName,
                                      bundle: nil),
                                forCellWithReuseIdentifier: AttachmentPhotoCollectionViewCell.cellName)
    }
}

// MARK: - UICollectionViewDelegate
extension AttachmentsCollectionViewDataSource: UICollectionViewDelegate {
    
}

// MARK: - UICollectionViewDataSource
extension AttachmentsCollectionViewDataSource: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        let photoAttachments = attachments?.filter({ $0.type == .photo }) ?? []
        
        var strings = [String]()
        for _ in 0..<15 {
            strings.append("")
        }
        
        for i in 0..<photoAttachments.count {
            guard let photoDto = photoAttachments[i].photo else {
                return 0
            }
            strings[0] += "\(i+1) фото\t\t"
            strings[1] += "\(photoDto.sizes.count) размеров\t"
            var j = 2
            photoDto.sizes.forEach {
                let wxh = "\($0.width)x\($0.height)"
                let aspRatio = ((CGFloat($0.width)/CGFloat($0.height))*100).rounded() / 100.0
                strings[j] += "\(wxh) \(aspRatio)\t\t"
                j += 1
            }
            let urlString = "\(photoDto.sizes.last!.url)\t"
            strings[j] += urlString
        }
        
        if photoAttachments.count == 10 {
            print("101010101010")
        }
        
        print("\n\n\n************Photo info*************")
        for i in 0..<15 {
            print(strings[i])
        }
        
        return photoAttachments.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let photoAttachments = attachments?.filter({ $0.type == .photo }) ?? []
        let attachment = photoAttachments[indexPath.row]
        
        switch attachment.type {
        case .photo:
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AttachmentPhotoCollectionViewCell.cellName, for: indexPath) as? AttachmentPhotoCollectionViewCell,
                let photoDto = attachment.photo else {
                return UICollectionViewCell()
            }
            cell.setup(photoDto: photoDto)
            return cell
        case .video:
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AttachmentVideoCollectionViewCell.cellName, for: indexPath) as? AttachmentVideoCollectionViewCell,
                let videoDto = attachment.video else {
                return UICollectionViewCell()
            }
            cell.setup(videoDto: videoDto)
            return cell
        case .market:
            return UICollectionViewCell()
        case .marketAlbum:
            return UICollectionViewCell()
        case .prettyCards:
            return UICollectionViewCell()
        case .podcast:
            return UICollectionViewCell()
        case .audio:
            return UICollectionViewCell()
        case .doc:
            return UICollectionViewCell()
        case .link:
            return UICollectionViewCell()
        case .note:
            return UICollectionViewCell()
        case .poll:
            return UICollectionViewCell()
        case .page:
            return UICollectionViewCell()
        case .album:
            return UICollectionViewCell()
        case .photosList:
            return UICollectionViewCell()
        }
    }
}
// MARK: - UICollectionViewDelegateFlowLayout
extension AttachmentsCollectionViewDataSource: UICollectionViewDelegateFlowLayout {
//    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
//
//        if (attachments?.count == 1),
//            let photo = attachments?.first?.photo,
//            photo.sizes.count > 0 {
//            let photoSizes = photo.sizes
//            let photoSizesOrderArray = ["z", "y", "x", "w"]
//            let properPhotoSize = photoSizes.sorted(by: {
//                guard let index0 = photoSizesOrderArray.index(of: $0.type) else { return false }
//                guard let index1 = photoSizesOrderArray.index(of: $1.type) else { return true }
//                return index0 <= index1
//                }).first
//            let width = collectionView.frame.width
//            let height = CGFloat(properPhotoSize?.height ?? 0) / CGFloat(properPhotoSize?.width ?? 1) * width
//
//            return CGSize(width: width, height: height)
//        }
//
//        let width = collectionView.bounds.width / 2.0 - 5.0
//        let height = CGFloat(120.0)
//
//        return CGSize(width: width, height: height)
//    }
}
