Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/random music #13

Merged
merged 11 commits into from
Apr 27, 2022
13 changes: 13 additions & 0 deletions LiarGame/Sources/Model/GameMode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// GameMode.swift
// LiarGame
//
// Created by JK on 2022/04/26.
//

import Foundation

enum GameMode {
case liarGame
case randomMusicQuiz
}
9 changes: 5 additions & 4 deletions LiarGame/Sources/Model/Music.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

import Foundation

struct Music: Codable {
struct Music: Codable, Equatable {
let title: String
let artist: String
/// youtube video ID
let id: String
let startedAt: String
let startedAt: Float

/** Music 생성자

Expand All @@ -27,12 +27,13 @@ struct Music: Codable {
길이가 맞지 않을 경우 `return nil`
*/
init?(from array: [String]) {
guard array.count == 4 else { return nil }
guard array.count == 4,
let startedAt = Float(array[3]) else { return nil }

self.title = array[0]
self.artist = array[1]
self.id = array[2]
self.startedAt = array[3]
self.startedAt = startedAt
}
}

7 changes: 3 additions & 4 deletions LiarGame/Sources/Reactor/HomeReactor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,17 @@ import RxCocoa

final class HomeReactor: Reactor{
enum Action{
case updateMode(String?)
case updateMode(GameMode)
}
enum Mutation{
case setMode(String?)
case setMode(GameMode)
}
struct State{
var mode: String?
var mode: GameMode?
}

let initialState = State()


func mutate(action: Action) -> Observable<Mutation> {
switch action {
case let .updateMode(mode):
Expand Down
154 changes: 150 additions & 4 deletions LiarGame/Sources/Reactor/RandomMusicQuizReactor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,162 @@ import Foundation
import ReactorKit

final class RandomMusicQuizReactor: Reactor {
init(repository: RandomMusicRepository) {
self.repository = repository
}

private var disposeBag = DisposeBag()

var scheduler = SerialDispatchQueueScheduler(internalSerialQueueName: "random.music.quiz")
var initialState = State()
private let repository: RandomMusicRepository
private var playerState: PlayerState = .unknwon
private var second: PlaySecond?

enum PlaySecond: Int {
case three = 3
case five = 5
case ten = 10
}

enum PlayerState {
/// 비디오 로드 후 대기 중
case pending
/// 비디오 로드 완료
case ready
/// 비디오 재생 시 버퍼링
case buffering
/// 비디오 재생 중
case playing
/// 재생정지(stopVideo) 호출 시 cued
case cued
case unknwon
}

enum Action {
case updateMusicList
case playMusicButtonTapped(second: PlaySecond)
case didPlayToggleButtonTapped
case didAnswerButtonTapped
case shuffle
case playerState(PlayerState)
case needCurrentVersion
}

enum Mutation {
case updatePlayStopState(Bool)
case updateCurrentVersion(String)
case updateCurrentMusic(Music?)
case updateAnswer((String, String)?)
case updateLoading(Bool)
case ignore
}

struct State {
var isPlaying: Bool = false
var isLoading: Bool = false
var currentVersion: String = ""
var answer: (title: String, artist: String)?
var currentMusic: Music?
}

enum Action { }
enum Mutation { }
struct State { }
func mutate(action: Action) -> Observable<Mutation> {

switch action {
case .updateMusicList:
guard playerState != .pending else { return .empty() }
playerState = .pending
return .concat([
.just(.updateLoading(true)),
.just(.updatePlayStopState(false)),
.just(.updateAnswer(nil)),
repository.getNewestVersion()
.asObservable()
.map { _ in Mutation.ignore },
.just(.updateCurrentMusic(shuffleMusic())),
.just(.updateCurrentVersion(repository.currentVersion))
])
.timeout(.seconds(10), other: Observable.just(Mutation.updateLoading(false)), scheduler: scheduler)
2jae6 marked this conversation as resolved.
Show resolved Hide resolved

case let .playMusicButtonTapped(second):
self.second = second
return .concat([
.just(.updatePlayStopState(true)),
])

case .didPlayToggleButtonTapped:
return .just(.updatePlayStopState(!currentState.isPlaying))

case .didAnswerButtonTapped:
return .just(.updateAnswer(currentAnswer()))

case .shuffle:
guard playerState != .pending else { return .empty() }
playerState = .pending
return .concat(
.just(.updateLoading(true)),
.just(.updatePlayStopState(false)),
.just(.updateAnswer(nil)),
.just(.updateCurrentMusic(shuffleMusic()))
)

case .needCurrentVersion:
return .just(.updateCurrentVersion(repository.currentVersion))

case let .playerState(state):
return playerStateHandler(state)
}
}

func reduce(state: State, mutation: Mutation) -> State {
var state = state
switch mutation {
case let .updatePlayStopState(boolean):
state.isPlaying = boolean
case let .updateCurrentVersion(version):
state.currentVersion = version
case let .updateCurrentMusic(music):
state.currentMusic = music
case let .updateAnswer(info):
state.answer = info
case let .updateLoading(boolean):
state.isLoading = boolean
case .ignore: break
}
return state
}

private func currentAnswer() -> (title: String, artist: String)? {
if let currentMusic = currentState.currentMusic {
return (title: currentMusic.title, artist: currentMusic.artist)
} else {
return nil
}
}

private func shuffleMusic() -> Music? {
guard repository.musicList.count > 0 else { return nil }
let size = repository.musicList.count
let randomNumber: Int = Int(arc4random()) % size

return repository.musicList[randomNumber]
}

// `YTPlayerView.playVideo()` 호출 시점과 실제 재생 시점이 다름
// `YTPlayerView` 의 state 를 확인해서 재생 타이머를 수행
private func playerStateHandler(_ state: PlayerState) -> Observable<Mutation> {
self.playerState = state
guard playerState != .pending else { return .empty() }

switch playerState {
case .playing:
return .just(.updatePlayStopState(false))
case .ready:
return .just(.updateLoading(false))
case .cued:
self.second = nil
fallthrough
default:
return .empty()
}
}
}
10 changes: 4 additions & 6 deletions LiarGame/Sources/Repository/RandomMusicRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final class RandomMusicRepository: RandomMusicRepositoryType {
Self._currentVersion
}

@UserDefault(key: "RandomMusicVersion", defaultValue: "")
@UserDefault(key: "RandomMusicVersion", defaultValue: "unknwon")
private static var _currentVersion: String

/** 저장되어있는 음악 목록을 가져옵니다.
Expand Down Expand Up @@ -61,7 +61,7 @@ final class RandomMusicRepository: RandomMusicRepositoryType {
func getNewestVersion() -> Single<[Music]> {
_newsetVersion
.flatMap { _version -> Single<[Music]> in
let arr = _version.components(separatedBy: ",")
let arr = _version.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: ",")
let version = arr[0]
let length = arr[1]
guard version != self.currentVersion else { return .just(try self.readMuicList()) }
Expand Down Expand Up @@ -141,17 +141,15 @@ final class RandomMusicRepository: RandomMusicRepositoryType {
}

/// 음악 데이터 읽어오기
// TODO: - private 으로 변경///
func readMuicList() throws -> [Music] {
private func readMuicList() throws -> [Music] {
let path = try musicDataPath()
let data = try Data(contentsOf: path)

return try JSONDecoder().decode([Music].self, from: data)
}

/// 음악 데이터를 저장하는 경로
// TODO: - private 으로 변경///
func musicDataPath() throws -> URL {
private func musicDataPath() throws -> URL {
guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
throw RandomMusicRepositoryError.fileAccessFailed
}
Expand Down
32 changes: 32 additions & 0 deletions LiarGame/Sources/Utils/Flex+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// Flex+Extensions.swift
// LiarGame
//
// Created by JK on 2022/04/26.
//

import Foundation
import CoreGraphics
import FlexLayout

extension Flex {
@discardableResult
2jae6 marked this conversation as resolved.
Show resolved Hide resolved
func horizontallySpacing(_ value: CGFloat?) -> Flex {
guard let view = view, view.subviews.count > 1 else { return self }
for (idx, subview) in view.subviews.enumerated() {
if idx == 0 { continue }
subview.flex.marginLeft(value ?? 0)
}
return self
}

@discardableResult
func verticallySpacing(_ value: CGFloat?) -> Flex {
guard let view = view, view.subviews.count > 1 else { return self }
for (idx, subview) in view.subviews.enumerated() {
if idx == 0 { continue }
subview.flex.marginTop(value ?? 0)
}
return self
}
}
21 changes: 21 additions & 0 deletions LiarGame/Sources/Utils/UIColor+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// UIColor+Extensions.swift
// LiarGame
//
// Created by JK on 2022/04/26.
//

import UIKit

extension UIColor {
convenience init(hexString : String) {
if let rgbValue = UInt(hexString, radix: 16) {
let red = CGFloat((rgbValue >> 16) & 0xff) / 255
let green = CGFloat((rgbValue >> 8) & 0xff) / 255
let blue = CGFloat((rgbValue ) & 0xff) / 255
self.init(red: red, green: green, blue: blue, alpha: 1.0)
} else {
self.init(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
}
}
}
Loading