Skip to content

Commit

Permalink
Merge pull request #78 from boostcamp3-iOS/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
tak8997 authored Feb 18, 2019
2 parents 26b6889 + 8233117 commit a5159ea
Show file tree
Hide file tree
Showing 80 changed files with 4,185 additions and 333 deletions.
286 changes: 266 additions & 20 deletions DaumWebtoon.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions DaumWebtoon/APIS/BestPodCastsAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// BestPodCastsAPI.swift
// DaumWebtoon
//
// Created by Gaon Kim on 14/02/2019.
// Copyright © 2019 Gaon Kim. All rights reserved.
//

import Foundation

struct BestPodCastsAPI: RequestType {
typealias ResponseType = BestPodCasts
var data: RequestData
}
15 changes: 15 additions & 0 deletions DaumWebtoon/APIS/FetchGenresAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// FetchGenreAPI.swift
// DaumWebtoon
//
// Created by Tak on 15/02/2019.
// Copyright © 2019 Gaon Kim. All rights reserved.
//

import Foundation

struct FetchGenresAPI: RequestType {

typealias ResponseType = GenreDTO
var data: RequestData
}
16 changes: 16 additions & 0 deletions DaumWebtoon/APIS/FetchPodCastsAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// FetchPodCasts.swift
// DaumWebtoon
//
// Created by Tak on 12/02/2019.
// Copyright © 2019 Gaon Kim. All rights reserved.
//

import Foundation


struct FetchPodCastsAPI: RequestType {

typealias ResponseType = PodCast
var data: RequestData
}
15 changes: 15 additions & 0 deletions DaumWebtoon/APIS/SearchPodCastsAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// SearchPodCastsAPI.swift
// DaumWebtoon
//
// Created by Tak on 15/02/2019.
// Copyright © 2019 Gaon Kim. All rights reserved.
//

import Foundation

struct SearchPodCastsAPI: RequestType {

typealias ResponseType = PodCastSearchDTO
var data: RequestData
}
198 changes: 198 additions & 0 deletions DaumWebtoon/Controllers/Episode/EpisodeModalViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
//
// DetailWeatherViewController.swift
// DaumWebtoon
//
// Created by Tak on 06/02/2019.
// Copyright © 2019 Gaon Kim. All rights reserved.
//

import UIKit
import AVFoundation

protocol EpisodeModalViewDelegate: class {
func playPauseAudio(state: Bool)
func showHeaderImageView()
}

class EpisodeModalViewController: UIViewController {

@IBOutlet weak var episodeImage: UIImageView!
@IBOutlet weak var episodeProgress: UISlider!
@IBOutlet weak var episodeTitle: UILabel!
@IBOutlet weak var episodeTotalTime: UILabel!
@IBOutlet weak var episodeUpdateTime: UILabel!
@IBOutlet weak var favoriteButton: UIButton!

weak var delegate: EpisodeModalViewDelegate?

var episode: Episode?

private let audioSession = AVAudioSession.sharedInstance()
private let dbService = DatabaseService()
private let audioService = FetchAudioService.shared

private var audioUrl: String?
private var buttonSelected = false
private var audioTimer : Timer?

override func viewDidLoad() {
super.viewDidLoad()

setupAudioSession()
setupFavoriteViewState()

initializeEpisode()
initializeViews()

addRecentEpisode()
}

private func initializeViews() {
let gestureRecognizer = UIPanGestureRecognizer(target: self,
action: #selector(handlePanGesture(_:)))
view.addGestureRecognizer(gestureRecognizer)
}

private func initializeEpisode() {
guard let episode = self.episode else { return }

let (h,m,s) = episode.duration.secondsToHoursMinutesSeconds()

audioUrl = episode.audio
episodeTitle.text = episode.title
episodeTotalTime.text = "\(h):\(m):\(s)"

audioService.execute(audioUrl: episode.audio) { [weak self] (status) in
guard let self = self else { return }

if status == AudioFetchStatus.success {
self.episodeProgress.maximumValue = self.audioService.getMaximumValue()
self.episodeProgress.minimumValue = 0
self.episodeProgress.value = Float(self.audioService.getCurrentValue())
}
}

FetchImageService.shared.execute(imageUrl: episode.image) { [weak self] (image) in
guard let self = self else { return }
self.episodeImage.image = image
}
}

private func addRecentEpisode() {
guard let episode = self.episode else { return }
dbService.addRecentEpisode(with: episode)
}

// MARK :- private methods
private func setupFavoriteViewState() {
guard let episode = self.episode else { return }
let isFavorite = dbService.isFavoriteEpisode(of: episode)
favoriteButton.isSelected = isFavorite
}

private func setupAudioSession() {
do {
try audioSession.setCategory(AVAudioSession.Category.playback,
mode: .default,
policy: .longForm,
options: [])
try audioSession.setActive(true, options: [])
} catch let error {
fatalError("*** Unable to set up the audio session: \(error.localizedDescription) ***")
}
}

private func makeAndFireTimer() {
self.audioTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { [weak self] (timer : Timer) in
guard
let self = self,
self.episodeProgress.isTracking == false else { return }

self.updateEpisodeTime(time: self.audioService.getCurrentValue())
self.episodeProgress.value = Float(self.audioService.getCurrentValue())
})
}

private func updateEpisodeTime(time: TimeInterval?) {
guard let time = time else { return }
episodeUpdateTime.text = time.stringFromTimeInterval()
}

private func invalidateTimer() {
audioTimer?.invalidate()
audioTimer = nil
}

private func playAudio() {
audioService.play()
}

private func pauseAudio() {
audioService.pause()
}

private func dismissModal() {
delegate?.showHeaderImageView()
dismiss(animated: true, completion: nil)
}

// MARK :- event handling
@objc func handlePanGesture(_ recognizer: UIPanGestureRecognizer) {
let touchPoint = recognizer.location(in: view?.window)
let translation = recognizer.translation(in: view)

var initialTouchPoint = CGPoint.zero
switch recognizer.state {
case .began:
initialTouchPoint = touchPoint
case .changed:
if touchPoint.y > initialTouchPoint.y {
view.center = CGPoint(x: view.center.x, y: view.center.y + translation.y)
recognizer.setTranslation(CGPoint.zero, in: view)
}
case .ended, .cancelled:
if view.frame.origin.y > view.frame.size.height / 2 {
dismissModal()
} else {
UIView.animate(withDuration: 0.2, animations: {
self.view.frame = CGRect(x: 0,
y: 0,
width: self.view.frame.size.width,
height: self.view.frame.size.height)
})
}
case .failed, .possible:
break
}
}

@IBAction func playPauseTapped(_ sender: UIButton) {
if sender.isSelected {
pauseAudio()
invalidateTimer()
} else {
playAudio()
makeAndFireTimer()
}

sender.isSelected = !sender.isSelected
buttonSelected = !sender.isSelected
}

@IBAction func likeTapped(_ sender: UIButton) {
guard let episode = episode else { return }
dbService.manageFavoriteEpisode(with: episode, state: sender.isSelected)
sender.isSelected = !sender.isSelected
}

@IBAction func shareTapped(_ sender: UIButton) {
let activityViewController = UIActivityViewController(activityItems: [episode?.audio ?? "", episode?.channelTitle ?? ""], applicationActivities: nil)
activityViewController.excludedActivityTypes = [.airDrop]
present(activityViewController, animated: true)
sender.isSelected = !sender.isHighlighted
}

@IBAction func downTapped(_ sender: UIButton) {
dismissModal()
}
}
99 changes: 99 additions & 0 deletions DaumWebtoon/Controllers/Main/ChannelTableViewCell.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// ChannelTableViewCell.swift
// DaumWebtoon
//
// Created by Gaon Kim on 14/02/2019.
// Copyright © 2019 Gaon Kim. All rights reserved.
//

import UIKit

class ChannelTableViewCell: UITableViewCell {
var thumbnailImageView = UIImageView()
private lazy var titleLabel = UILabel()
private lazy var publisherLabel = UILabel()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addThumbnailImageView()
addTitleLabel()
addPublisherLabel()
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: Thumnail Image View Methods
func addThumbnailImageView() {
addSubview(thumbnailImageView)
setThumbnailImageViewLayout()
setThumbnailImageViewProperties()
}

func setThumbnailImageViewLayout() {
thumbnailImageView.translatesAutoresizingMaskIntoConstraints = false
thumbnailImageView.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
thumbnailImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10).isActive = true
thumbnailImageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive = true
thumbnailImageView.heightAnchor.constraint(equalToConstant: 80).isActive = true
thumbnailImageView.widthAnchor.constraint(equalToConstant: 80).isActive = true
}

func setThumbnailImageViewProperties() {
thumbnailImageView.contentMode = .scaleAspectFill
thumbnailImageView.clipsToBounds = true
}

// MARK: Title Label Methods
func addTitleLabel() {
addSubview(titleLabel)
setTitleLabelLayout()
setTitleLabelProperties()
}

func setTitleLabelLayout() {
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
titleLabel.trailingAnchor.constraint(equalTo: thumbnailImageView.leadingAnchor, constant: -20).isActive = true
titleLabel.heightAnchor.constraint(lessThanOrEqualToConstant: 60).isActive = true
}

func setTitleLabelProperties() {
titleLabel.numberOfLines = 0
titleLabel.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
}

// MARK: Publisher Label Methods
func addPublisherLabel() {
addSubview(publisherLabel)
setPublisherLabelLayout()
setPublisherLabelProperties()
}

func setPublisherLabelLayout() {
publisherLabel.translatesAutoresizingMaskIntoConstraints = false
publisherLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 3).isActive = true
publisherLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor).isActive = true
publisherLabel.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor).isActive = true
}

func setPublisherLabelProperties() {
publisherLabel.font = UIFont.monospacedDigitSystemFont(ofSize: 12, weight: .light)
}

// MARK: Data
func setData(channel: Channel) {
titleLabel.text = channel.title
publisherLabel.text = channel.publisher
fetchImage(imageUrl: channel.image)
}

func fetchImage(imageUrl: String) {
FetchImageService.shared.execute(imageUrl: imageUrl) { [weak self] image in
guard let self = self else { return }
self.thumbnailImageView.image = image
}
}
}
Loading

0 comments on commit a5159ea

Please sign in to comment.