Created
October 7, 2020 11:34
-
-
Save zykis/e234764c1a109f27c4215e114800ee4e to your computer and use it in GitHub Desktop.
Code sample
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Alamofire | |
let host = "https://api-dev.talala.la/api/v2" | |
protocol APIClientProtocol { | |
var authentication: APIClientAuthServiceProtocol { get } | |
var user: APIClientUserServiceProtocol { get } | |
var media: APIClientMediaServiceProtocol { get } | |
var challenge: APIClientChallengeServiceProtocol { get } | |
var helloService: HelloServiceProtocol { get } | |
func update(token: HelloModel) | |
func update(token: TokenModel) | |
func clearTokenInfo() | |
} | |
class APIClient: APIClientProtocol { | |
var authentication: APIClientAuthServiceProtocol | |
var user: APIClientUserServiceProtocol | |
var media: APIClientMediaServiceProtocol | |
var challenge: APIClientChallengeServiceProtocol | |
let helloService: HelloServiceProtocol | |
static let shared = APIClient() | |
var session: Session | |
private let logger: LoggerProtocol? | |
private let tokenService = TokenService() | |
private init() { | |
logger = Logger() | |
let config = URLSessionConfiguration.default | |
config.requestCachePolicy = .reloadIgnoringLocalCacheData | |
let interceptor = APIInterceptor(tokenService: tokenService, host: host, logger: logger) | |
session = Session(configuration: config, interceptor: interceptor) | |
helloService = HelloService(host: host, session: session, tokenService: tokenService) | |
interceptor.helloService = helloService | |
authentication = APIClientAuthService(host: host, | |
logger: logger, | |
session: session, | |
tokenService: tokenService, | |
helloService: helloService) | |
user = APIClientUserService(host: host, | |
logger: logger, | |
session: session, | |
tokenService: tokenService) | |
media = APIClientMediaService(host: host, logger: logger, session: session) | |
challenge = APIClientChallengeService(host: host, logger: logger, session: session) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. | |
// | |
import Foundation | |
protocol ChallengeViewProtocol: class { | |
var sortColumn: MediaSortColumn { get set } | |
var sortOrder: MediaSortOrder { get set } | |
func challengeFetchFailed(error: Error) | |
func update() | |
} | |
enum MediaSortColumn: Int { | |
case time = 0 | |
case likes | |
} | |
enum MediaSortOrder: Int { | |
case ascending = 0 | |
case descending | |
} | |
protocol ChallengePresenterProtocol: class { | |
var view: ChallengeViewProtocol? { get set } | |
var router: ChallengeRouterProtocol { get } | |
// In | |
func fetchChallenge(sortColumn: MediaSortColumn?, sortOrder: MediaSortOrder?) | |
func title() -> String? | |
func description() -> String? | |
func avatarURL() -> URL? | |
func username() -> String? | |
func reward() -> String? | |
func likes() -> String? | |
func dislikes() -> String? | |
func views() -> String? | |
func medias() -> String? | |
func reposts() -> String? | |
func start() -> String? | |
func end() -> String? | |
func categories() -> String? | |
func showStatus() -> Bool | |
func statusImageName() -> String? | |
func statusTitle() -> String? | |
func showFollowButton() -> Bool | |
func isFollowing() -> Bool? | |
func medias() -> [Media] | |
func userMediaID() -> String? | |
func winnerMediaID() -> String? | |
func challengeID() -> String? | |
func latitude() -> Double? | |
func longitude() -> Double? | |
// Out | |
func followChallenge() | |
func likeChallenge() | |
func dislikeChallenge() | |
func didTapMedia(index: Int) | |
func onProfilePressed() | |
} | |
class ChallengePresenter: ChallengePresenterProtocol { | |
struct Dependencies { | |
var challengeID: String? | |
var apiClient: APIClientChallengeServiceProtocol | |
} | |
weak var view: ChallengeViewProtocol? | |
var router: ChallengeRouterProtocol | |
var dependencies: Dependencies | |
var challenge: Challenge? | |
init(router: ChallengeRouterProtocol, dependencies: ChallengePresenter.Dependencies) { | |
self.router = router | |
self.dependencies = dependencies | |
} | |
func fetchChallenge(sortColumn: MediaSortColumn?, sortOrder: MediaSortOrder?) { | |
guard let challengeId = dependencies.challengeID else { | |
return | |
} | |
APIManager.shared.fetchChallenge(challengeID: challengeId, | |
mediaSortType: sortColumn, | |
mediaSortOrder: sortOrder) { (challenge, error) in | |
guard let challenge = challenge else { | |
if let error = error { | |
self.view?.challengeFetchFailed(error: error) | |
} | |
return | |
} | |
self.challenge = challenge | |
self.view?.update() | |
} | |
} | |
func title() -> String? { | |
return challenge?.title | |
} | |
func description() -> String? { | |
return challenge?.description | |
} | |
func avatarURL() -> URL? { | |
if let avatarUrl = challenge?.owner?.avatarUrl { | |
return URL(string: avatarUrl) | |
} | |
return nil | |
} | |
func username() -> String? { | |
if let owner = challenge?.owner { | |
return "@\(owner.username)" | |
} | |
return nil | |
} | |
func reward() -> String? { | |
if let challenge = challenge { | |
return "\(Int(challenge.reward / 100.0))$" | |
} | |
return nil | |
} | |
func likes() -> String? { | |
if let challenge = challenge { | |
return "\(challenge.likes)" | |
} | |
return nil | |
} | |
func dislikes() -> String? { | |
if let challenge = challenge { | |
return "\(challenge.dislikes)" | |
} | |
return nil | |
} | |
func views() -> String? { | |
if let challenge = challenge { | |
return "\(challenge.watch)" | |
} | |
return nil | |
} | |
func medias() -> String? { | |
if let challenge = challenge { | |
return "\(challenge.medias.count)" | |
} | |
return nil | |
} | |
func reposts() -> String? { | |
if let challenge = challenge { | |
return "\(challenge.reposts)" | |
} | |
return nil | |
} | |
func statusImageName() -> String? { | |
return "challenge-status-completed" | |
} | |
func statusTitle() -> String? { | |
guard let challenge = challenge else { | |
return nil | |
} | |
if Date() > challenge.endDate { | |
return "Successfully completed" | |
} | |
return challenge.endDate.extendedRepresentationSinceNow() | |
} | |
func start() -> String? { | |
if let challenge = challenge { | |
return challenge.startDate.readableRepresentation() | |
} | |
return nil | |
} | |
func end() -> String? { | |
if let challenge = challenge { | |
return challenge.endDate.readableRepresentation() | |
} | |
return nil | |
} | |
func categories() -> String? { | |
if let challenge = challenge { | |
let categories = challenge.tags.compactMap { $0.name }.joined(separator: " ") | |
return categories | |
} | |
return nil | |
} | |
func showStatus() -> Bool { | |
if let challenge = challenge, challenge.userMediaID != nil { | |
return true | |
} | |
return false | |
} | |
func showFollowButton() -> Bool { | |
if let challenge = challenge, challenge.owner != nil { | |
return true | |
} | |
return true | |
} | |
func isFollowing() -> Bool? { | |
if let challenge = challenge { | |
return challenge.isFollowed | |
} | |
return false | |
} | |
func medias() -> [Media] { | |
if let challenge = challenge { | |
return challenge.medias | |
} | |
return [] | |
} | |
func userMediaID() -> String? { | |
return challenge?.userMediaID | |
} | |
func winnerMediaID() -> String? { | |
return challenge?.winnerMediaID | |
} | |
func challengeID() -> String? { | |
return challenge?.id | |
} | |
func latitude() -> Double? { | |
return challenge?.latitude | |
} | |
func longitude() -> Double? { | |
return challenge?.longitude | |
} | |
func followChallenge() { | |
guard let challengeId = dependencies.challengeID else { return } | |
dependencies.apiClient.followChallenge(challengeId: challengeId) { [weak self] response in | |
switch response { | |
case .success(_): | |
self?.fetchChallenge(sortColumn: self?.view?.sortColumn, sortOrder: self?.view?.sortOrder) | |
case .failure(let error): | |
print(error.localizedDescription) | |
} | |
} | |
} | |
func likeChallenge() { | |
guard let challengeId = dependencies.challengeID else { return } | |
dependencies.apiClient.likeChallenge(challengeId: challengeId) { [weak self] result in | |
switch result { | |
case .success(_): | |
#warning("Need create new method like as \"fetchChallenge\", there will getting current result and work with him instead new request") | |
self?.fetchChallenge(sortColumn: self?.view?.sortColumn, sortOrder: self?.view?.sortOrder) | |
case .failure(let error): | |
#warning("make code simplier") | |
if case let .api(errors) = error, errors.filter({ $0.key == .authRequired }).first != nil { | |
guard let view = self?.router.view else { | |
return | |
} | |
self?.router.presentSignIn(from: view) { [weak self] _ in | |
self?.likeChallenge() | |
} | |
return | |
} | |
} | |
} | |
} | |
func dislikeChallenge() { | |
guard let challengeId = dependencies.challengeID else { return } | |
dependencies.apiClient.dislikeChallenge(challengeId: challengeId) { [weak self] response in | |
switch response { | |
case .success(_): | |
self?.fetchChallenge(sortColumn: self?.view?.sortColumn, sortOrder: self?.view?.sortOrder) | |
case .failure(let error): | |
print(error.localizedDescription) | |
} | |
} | |
} | |
func didTapMedia(index: Int) { | |
guard let mediaIDs = challenge?.medias.compactMap({ $0.id }) else { return } | |
guard let view = router.view else { return } | |
router.goToMedia(from: view, index: index, mediaIDs: mediaIDs) | |
} | |
func onProfilePressed() { | |
#warning("Исправить после перехода на api v2") | |
router.goToSomeProfile(userId: "") | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
typealias CommentID = String | |
struct CommentModel { | |
let message: String | |
var counters: MediaModelCounters? | |
var isLiked: Bool? | |
var author: UserModel? | |
} | |
extension CommentModel: Codable { | |
private enum CodingKeys: String, CodingKey { | |
case message = "comment_message" | |
case counters | |
case isLiked = "is_liked" | |
case author | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
protocol ChallengeRouterProtocol: class, GoToMediaRouterProtocol, GoToProfileProtocol, SignInRouterProtocol { | |
var view: UIViewController? { get } | |
func navigationBar(hidden: Bool, animated: Bool) | |
func goBackToRoot(animated: Bool) | |
func goToSomeProfile(userId: String) | |
} | |
class ChallengeBuilder { | |
static func create(challengeId: String?) -> UIViewController { | |
let router = ChallengeRouter() | |
let dependencies = ChallengePresenter.Dependencies( | |
challengeID: challengeId, | |
apiClient: APIClient.shared.challenge | |
) | |
let presenter = ChallengePresenter( | |
router: router, | |
dependencies: dependencies) | |
let controller = ChallengeViewController(presenter: presenter, banubaService: Services.banubaEditor()) | |
router.view = controller | |
return controller | |
} | |
} | |
class ChallengeRouter: ChallengeRouterProtocol { | |
weak var view: UIViewController? | |
func goBackToRoot(animated: Bool) { | |
view?.navigationController?.popToRootViewController(animated: animated) | |
} | |
func navigationBar(hidden: Bool, animated: Bool) { | |
view?.navigationController?.setNavigationBarHidden(hidden, animated: animated) | |
} | |
func goToSomeProfile(userId: String) { | |
visitProfile(from: view, userId: userId) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// WatchMediaView.swift | |
// Talala | |
// | |
// Created by Artem Zaytsev on 24.09.2020. | |
// Copyright © 2020 Артём Зайцев. All rights reserved. | |
// | |
import UIKit | |
import AVFoundation | |
import AVKit | |
class WatchMediaView: UIView { | |
private struct UI { | |
static let likedImage: UIImage = UIImage(named: "liked")!.withRenderingMode(.alwaysOriginal) | |
static let unlikedImage: UIImage = UIImage(named: "like")!.withRenderingMode(.alwaysOriginal) | |
static let playIconSize: CGFloat = 70 | |
} | |
let presenter: WatchMediaPresenterProtocol | |
init(presenter: WatchMediaPresenterProtocol, isCustom: Bool, forcePlay: Bool) { | |
self.presenter = presenter | |
self.player = AVPlayer() | |
self.playerVC = AVPlayerViewController() | |
self.isCustom = isCustom | |
self.forcePlay = forcePlay | |
super.init(frame: .null) | |
self.presenter.view = self | |
commonInit() | |
} | |
required init?(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
deinit { | |
playerStatusObserver?.invalidate() | |
} | |
private let isCustom: Bool | |
private let forcePlay: Bool | |
private let player: AVPlayer | |
private let playerVC: AVPlayerViewController | |
private var playerItemLoopObserver: NSObjectProtocol? | |
private var playerItemStatusObserver: NSObjectProtocol? | |
private var playerStatusObserver: NSKeyValueObservation? | |
private let previewImageView = UIImageView.new { | |
$0.contentMode = .scaleAspectFill | |
} | |
private let playImageView = UIImageView.new { | |
$0.image = UIImage(named: "watch-media-play-icon")?.withRenderingMode(.alwaysTemplate) | |
$0.tintColor = .white | |
$0.alpha = 0 | |
} | |
private let leftStackView = UIStackView.new { | |
$0.spacing = 10.0 | |
$0.axis = .vertical | |
$0.distribution = .equalSpacing | |
$0.alignment = .leading | |
} | |
private let rightStackView = UIStackView.new { | |
$0.axis = .vertical | |
$0.alignment = .trailing | |
$0.distribution = .equalSpacing | |
$0.spacing = 16 | |
} | |
private let likeStackView = UIStackView.new { | |
$0.axis = .vertical | |
$0.spacing = 5 | |
$0.alignment = .center | |
} | |
private lazy var likeButton = VinciAnimatableButton.new { | |
$0.setImage(UI.unlikedImage, for: .normal) | |
$0.setImage(UI.unlikedImage, for: .highlighted) | |
$0.tintAdjustmentMode = .normal | |
$0.addTarget(self, action: #selector(likePressed), for: .touchUpInside) | |
} | |
private let likeLabel = UILabel.new { | |
$0.textColor = .white | |
$0.font = .boldSystemFont(ofSize: 14.0) | |
$0.text = "0" | |
} | |
private let commentStackView = UIStackView.new { | |
$0.axis = .vertical | |
$0.spacing = 5 | |
$0.alignment = .center | |
} | |
private lazy var commentsButton = VinciAnimatableButton.new { | |
$0.setImage(UIImage(named: "icon_comments_white_60"), for: .normal) | |
$0.setImage(UIImage(named: "icon_comments_white_60"), for: .highlighted) | |
$0.tintAdjustmentMode = .normal | |
} | |
private let commentsLabel = UILabel.new { | |
$0.textColor = .white | |
$0.font = .boldSystemFont(ofSize: 14.0) | |
$0.text = "0" | |
} | |
private let shareStackView = UIStackView.new { | |
$0.axis = .vertical | |
$0.spacing = 5 | |
$0.alignment = .center | |
} | |
private lazy var shareButton = VinciAnimatableButton.new { | |
$0.setImage(UIImage(named: "share"), for: .normal) | |
$0.setImage(UIImage(named: "share"), for: .highlighted) | |
$0.tintAdjustmentMode = .normal | |
$0.addTarget(self, action: #selector(sharePressed), for: .touchUpInside) | |
} | |
private let shareLabel = UILabel.new { | |
$0.textColor = .white | |
$0.font = .boldSystemFont(ofSize: 14.0) | |
$0.text = "0" | |
} | |
private let descriptionTextView = UITextView.new { | |
$0.text = "Description" | |
$0.textColor = .white | |
$0.textAlignment = .left | |
$0.font = .systemFont(ofSize: 15.0, weight: .regular) | |
$0.isEditable = false | |
$0.isSelectable = false | |
$0.isUserInteractionEnabled = false | |
$0.backgroundColor = .clear | |
$0.textContainer.lineBreakMode = .byTruncatingTail | |
$0.textContainer.lineFragmentPadding = 0.0 | |
$0.contentInset.left = 0.0 | |
$0.textContainerInset.left = 0.0 | |
} | |
private let topGradient = GradientView.new { | |
$0.backgroundColor = .clear | |
$0.direction = "vertical" | |
$0.colors = "#000000AA, #00000000" | |
$0.locations = "0.0, 1.0" | |
} | |
private let descriptionGradient = GradientView.new { | |
$0.backgroundColor = .clear | |
$0.direction = "vertical" | |
$0.colors = "#00000000, #000000AA" | |
$0.locations = "0.0, 1.0" | |
} | |
private let rightButtonsGradient = UIView.new { | |
$0.backgroundColor = .clear | |
} | |
private lazy var radialGradient = CAGradientLayer.new { | |
$0.type = .radial | |
$0.colors = [ UIColor.black.withAlphaComponent(0.1), | |
UIColor.black.withAlphaComponent(0.0)] | |
$0.locations = [ 0, 1 ] | |
$0.startPoint = CGPoint(x: 0.5, y: 0.5) | |
$0.endPoint = CGPoint(x: 1, y: 1) | |
rightButtonsGradient.layer.addSublayer($0) | |
} | |
private lazy var profileStackView = UIStackView.new { | |
$0.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(profilePressed))) | |
$0.axis = .horizontal | |
$0.spacing = 8.0 | |
$0.alignment = .center | |
} | |
private let profileImageView = UIImageView.new { | |
$0.heightAnchor.constraint(equalToConstant: 24.0).isActive = true | |
$0.widthAnchor.constraint(equalToConstant: 24.0).isActive = true | |
$0.layer.cornerRadius = 12.0 | |
$0.layer.masksToBounds = true | |
$0.backgroundColor = .white | |
} | |
private let profileVerticalStackView = UIStackView.new { | |
$0.axis = .vertical | |
$0.spacing = 0.0 | |
$0.alignment = .leading | |
} | |
private let profileLabel = UILabel.new { | |
$0.font = .boldSystemFont(ofSize: 11.0) | |
$0.text = "Smithsonian institution" | |
$0.textColor = .white | |
} | |
private let postDateLabel = UILabel.new { | |
$0.font = .systemFont(ofSize: 11.0) | |
$0.text = "20 November 2020" | |
$0.textColor = .white | |
} | |
// WORKAROUND: we need a pace between elements look the same | |
// So instead of button, we should use StackView with invisible title | |
private let moveToChallengeStackView = UIStackView.new { | |
$0.axis = .vertical | |
$0.spacing = 0 | |
$0.alignment = .center | |
} | |
private let moveToChallengeLabel = UILabel.new { | |
$0.textColor = .clear | |
$0.font = .systemFont(ofSize: 17.0, weight: .light) | |
$0.text = "0" | |
$0.heightAnchor.constraint(equalToConstant: 8.0).isActive = true | |
} | |
private lazy var moveToChallengeButton = UIButton.new { | |
$0.setImage(UIImage(named: "arrow"), for: .normal) | |
$0.addTarget(self, action: #selector(moveToChallengePressed), for: .touchUpInside) | |
} | |
} | |
// MARK: User input | |
extension WatchMediaView { | |
@objc | |
func profilePressed() { | |
pause() | |
presenter.handleProfilePressed() | |
} | |
@objc | |
func commentsPressed() { | |
} | |
@objc | |
func sharePressed() { | |
presenter.handleSharePressed() | |
} | |
@objc | |
func likePressed() { | |
presenter.handleLikePressed() | |
} | |
@objc | |
func moveToChallengePressed() { | |
} | |
@objc | |
func handleTap() { | |
let isPlaying = player.rate != 0.0 | |
isPlaying ? pause() : play() | |
} | |
} | |
// MARK: Initialization | |
extension WatchMediaView { | |
private func commonInit() { | |
player.automaticallyWaitsToMinimizeStalling = true | |
playerItemLoopObserver = NotificationCenter.default.addObserver( | |
forName: .AVPlayerItemDidPlayToEndTime, | |
object: player.currentItem, | |
queue: .main) { [weak self] notification in | |
if self?.player.currentItem === notification.object as? AVPlayerItem { | |
self?.player.seek(to: CMTime.zero) | |
self?.player.play() | |
} | |
} | |
playerStatusObserver = player.observe(\.timeControlStatus) { [weak self] (object, _) in | |
if object.timeControlStatus == .playing { | |
self?.previewImageView.isHidden = true | |
} | |
} | |
playerVC.player = player | |
playerVC.showsPlaybackControls = false | |
playerVC.videoGravity = .resizeAspectFill | |
addSubviews() | |
setupConstraints() | |
presenter.updateMediaMetadata() | |
presenter.getMediaVideo() | |
} | |
private func addSubviews() { | |
addSubview(playerVC.view) | |
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap))) | |
addSubview(descriptionGradient) | |
addSubview(topGradient) | |
addSubview(rightButtonsGradient) | |
addSubview(previewImageView) | |
addSubview(leftStackView) | |
addSubview(rightStackView) | |
addSubview(playImageView) | |
leftStackView.addArrangedSubview(descriptionTextView) | |
leftStackView.addArrangedSubview(profileStackView) | |
profileStackView.addArrangedSubview(profileImageView) | |
profileStackView.addArrangedSubview(profileVerticalStackView) | |
profileVerticalStackView.addArrangedSubview(profileLabel) | |
profileVerticalStackView.addArrangedSubview(postDateLabel) | |
rightStackView.addArrangedSubview(moveToChallengeStackView) | |
rightStackView.addArrangedSubview(likeStackView) | |
rightStackView.addArrangedSubview(commentStackView) | |
rightStackView.addArrangedSubview(shareStackView) | |
moveToChallengeStackView.addArrangedSubview(moveToChallengeButton) | |
moveToChallengeStackView.addArrangedSubview(moveToChallengeLabel) | |
likeStackView.addArrangedSubview(likeButton) | |
likeStackView.addArrangedSubview(likeLabel) | |
commentStackView.addArrangedSubview(commentsButton) | |
commentStackView.addArrangedSubview(commentsLabel) | |
shareStackView.addArrangedSubview(shareButton) | |
shareStackView.addArrangedSubview(shareLabel) | |
} | |
private func setupConstraints() { | |
playImageView.widthAnchor.constraint(equalToConstant: UI.playIconSize).isActive = true | |
playImageView.heightAnchor.constraint(equalTo: playImageView.widthAnchor, multiplier: 1.0).isActive = true | |
playImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true | |
playImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true | |
descriptionGradient.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true | |
descriptionGradient.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true | |
descriptionGradient.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true | |
descriptionGradient.topAnchor.constraint(equalTo: leftStackView.topAnchor, constant: -20.0).isActive = true | |
topGradient.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true | |
topGradient.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true | |
topGradient.topAnchor.constraint(equalTo: topAnchor).isActive = true | |
topGradient.heightAnchor.constraint(equalToConstant: 130.0).isActive = true | |
rightButtonsGradient.leadingAnchor.constraint(equalTo: rightStackView.leadingAnchor, | |
constant: -20.0).isActive = true | |
rightButtonsGradient.topAnchor.constraint(equalTo: rightStackView.topAnchor, constant: -20.0).isActive = true | |
rightButtonsGradient.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true | |
rightButtonsGradient.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true | |
playerVC.view.frame = bounds | |
descriptionTextView.widthAnchor.constraint(equalToConstant: bounds.width * 0.6).isActive = true | |
likeButton.widthAnchor.constraint(equalToConstant: 40.0).isActive = true | |
likeButton.heightAnchor.constraint(equalTo: likeButton.widthAnchor).isActive = true | |
commentsButton.widthAnchor.constraint(equalToConstant: 40.0).isActive = true | |
commentsButton.heightAnchor.constraint(equalTo: commentsButton.widthAnchor).isActive = true | |
leftStackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, | |
constant: 17.0).isActive = true | |
leftStackView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, | |
constant: isCustom ? -25.0 : -10.0).isActive = true | |
leftStackView.trailingAnchor.constraint(equalTo: rightStackView.leadingAnchor).isActive = true | |
leftStackView.bottomAnchor.constraint(equalTo: rightStackView.bottomAnchor).isActive = true | |
rightStackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, | |
constant: -20.0).isActive = true | |
leadingAnchor.constraint(equalTo: previewImageView.leadingAnchor).isActive = true | |
trailingAnchor.constraint(equalTo: previewImageView.trailingAnchor).isActive = true | |
topAnchor.constraint(equalTo: previewImageView.topAnchor).isActive = true | |
bottomAnchor.constraint(equalTo: previewImageView.bottomAnchor).isActive = true | |
} | |
} | |
// MARK: WatchMediaViewProtocol | |
extension WatchMediaView: WatchMediaViewProtocol { | |
func isPlaying() -> Bool { | |
return player.rate != 0.0 | |
} | |
func play() { | |
playImageView.alpha = 0 | |
player.play() | |
} | |
func pause() { | |
playImageView.alpha = 0.54 | |
player.pause() | |
} | |
func rewind() { | |
player.seek(to: .zero) | |
} | |
func updateMetadata(media: MediaModel, isLiked: Bool, likes: Int) { | |
likeLabel.text = likes.kmb | |
likeButton.setImage(isLiked ? UI.likedImage : UI.unlikedImage, for: .normal) | |
likeButton.setImage(isLiked ? UI.likedImage : UI.unlikedImage, for: .highlighted) | |
commentsLabel.text = media.counters.comments?.kmb | |
descriptionTextView.text = media.description | |
postDateLabel.text = media.createdAt.readableRepresentation2() | |
if let username = media.author.userName { | |
profileLabel.text = "@\(username)" | |
} | |
if let urlString = media.author.userAvatarUrl, let avatarUrl = URL(string: urlString) { | |
profileImageView.af.setImage(withURL: avatarUrl) | |
} | |
} | |
func updateVideo(videoUrl: URL) { | |
let asset = AVAsset(url: videoUrl) | |
let item = AVPlayerItem(asset: asset) | |
playerItemStatusObserver = item.observe(\.status) { [weak self] (object, _) in | |
guard let self = self else { return } | |
if object.status == AVPlayerItem.Status.readyToPlay, self.forcePlay { | |
self.play() | |
} else if let error = object.error { | |
print(error.localizedDescription) | |
} | |
} | |
player.replaceCurrentItem(with: item) | |
} | |
func updatePreview(previewUrl: URL) { | |
previewImageView.af.cancelImageRequest() | |
previewImageView.af.setImage(withURL: previewUrl) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment