Skip to content

Commit

Permalink
✨ [feat] MusicPlayerView를 생성 및 ViewModel도 구현하여 바인딩 처리 #20
Browse files Browse the repository at this point in the history
- 지역화 추가
  • Loading branch information
leeari95 committed Jul 23, 2022
1 parent 60d4297 commit 3bf262d
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
9A1DB1D2284069DD00689F11 /* UIImageView+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1DB1D1284069DD00689F11 /* UIImageView+extension.swift */; };
9A1DB1D7284081E400689F11 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 9A1DB1D6284081E400689F11 /* Alamofire */; };
9A1DB1D92840857300689F11 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1DB1D82840857300689F11 /* APIError.swift */; };
9A30FC02288C517600F55B8D /* MusicPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A30FC01288C517600F55B8D /* MusicPlayerView.swift */; };
9A30FC04288C57A400F55B8D /* MusicPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A30FC03288C57A400F55B8D /* MusicPlayerViewModel.swift */; };
9A3CFB2928805C4400B0FFCB /* MiscellaneousResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3CFB2828805C4400B0FFCB /* MiscellaneousResponseDTO.swift */; };
9A3CFB2B28805F4000B0FFCB /* MiscellaneousRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3CFB2A28805F4000B0FFCB /* MiscellaneousRequest.swift */; };
9A3CFB2D2880637100B0FFCB /* WallMountedResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3CFB2C2880637100B0FFCB /* WallMountedResponseDTO.swift */; };
Expand Down Expand Up @@ -208,6 +210,8 @@
/* Begin PBXFileReference section */
9A1DB1D1284069DD00689F11 /* UIImageView+extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageView+extension.swift"; sourceTree = "<group>"; };
9A1DB1D82840857300689F11 /* APIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = "<group>"; };
9A30FC01288C517600F55B8D /* MusicPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicPlayerView.swift; sourceTree = "<group>"; };
9A30FC03288C57A400F55B8D /* MusicPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicPlayerViewModel.swift; sourceTree = "<group>"; };
9A3CFB2828805C4400B0FFCB /* MiscellaneousResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiscellaneousResponseDTO.swift; sourceTree = "<group>"; };
9A3CFB2A28805F4000B0FFCB /* MiscellaneousRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiscellaneousRequest.swift; sourceTree = "<group>"; };
9A3CFB2C2880637100B0FFCB /* WallMountedResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallMountedResponseDTO.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -685,6 +689,7 @@
9A99AA51285C32C300629C4A /* PreferencesView.swift */,
9A6DC208287C7B42004EEC41 /* AppSettingView.swift */,
9A99AA5928602F4A00629C4A /* CustomTaskView.swift */,
9A30FC01288C517600F55B8D /* MusicPlayerView.swift */,
);
path = Views;
sourceTree = "<group>";
Expand All @@ -711,6 +716,7 @@
9A99AA792865CA9300629C4A /* CustomTaskViewModel.swift */,
9A6A2ED6287BE71A007D2EEA /* CollectionProgressSectionViewModel.swift */,
9A6DC20A287D0AC5004EEC41 /* AppSettingsViewModel.swift */,
9A30FC03288C57A400F55B8D /* MusicPlayerViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
Expand Down Expand Up @@ -1170,6 +1176,7 @@
9A7ACEE2287EC188001D47CB /* VillagerDetailViewModel.swift in Sources */,
9A99AA082858248600629C4A /* AppCoordinator.swift in Sources */,
9A6DE3532888E2E300EB2F3F /* PlayerViewController.swift in Sources */,
9A30FC04288C57A400F55B8D /* MusicPlayerViewModel.swift in Sources */,
9AF0421D28530DD200C51449 /* VillagersLikeStorage.swift in Sources */,
9A99AA54285C341E00629C4A /* InfoContentView.swift in Sources */,
9A99AA6D2861945B00629C4A /* PreferencesViewModel.swift in Sources */,
Expand Down Expand Up @@ -1274,6 +1281,7 @@
9A6891B1283371A60061AAF1 /* FishRequest.swift in Sources */,
9AF0418A28503F7900C51449 /* Item.swift in Sources */,
9A99AA2028587A7500629C4A /* SectionHeaderView.swift in Sources */,
9A30FC02288C517600F55B8D /* MusicPlayerView.swift in Sources */,
9A7ACF27287F9F00001D47CB /* HousewaresRequest.swift in Sources */,
9AF042042852F2F500C51449 /* Keyword.swift in Sources */,
9A99AA0C285824F000629C4A /* UIColor+extension.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "TodaySong.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// MusicPlayerViewModel.swift
// Animal-Crossing-Wiki
//
// Created by Ari on 2022/07/24.
//

import Foundation
import RxSwift

final class MusicPlayerViewModel {

private let coordinator: DashboardCoordinator

init(coordinator: DashboardCoordinator) {
self.coordinator = coordinator
}

struct Input {
let didTapPlayButton: Observable<Void>
let didTapNextButton: Observable<Void>
let didTapPrevButton: Observable<Void>
let didTapShuffle: Observable<Void>
let didTapRepeat: Observable<Void>
}

struct Output {
let currentSong: Observable<Item?>
let isPlaying: Observable<Bool?>
let songProgress: Observable<Float>
let currentTime: Observable<String>
let fullTime: Observable<String>
}

func transform(input: Input, disposeBag: DisposeBag) -> Output {
input.didTapPlayButton.subscribe(onNext: { _ in
let appCoordinator = self.coordinator.parentCoordinator as? AppCoordinator
appCoordinator?.showPlayerViewController()
MusicPlayerManager.shared.togglePlaying()
}).disposed(by: disposeBag)

input.didTapNextButton.subscribe(onNext: { _ in
MusicPlayerManager.shared.next()
}).disposed(by: disposeBag)

input.didTapPrevButton
.subscribe(onNext: { _ in
MusicPlayerManager.shared.prev()
}).disposed(by: disposeBag)

input.didTapShuffle
.subscribe(onNext: { _ in
MusicPlayerManager.shared.updatePlayerMode(to: .shuffle)
}).disposed(by: disposeBag)

input.didTapRepeat
.subscribe(onNext: { _ in
MusicPlayerManager.shared.updatePlayerMode(to: .fullRepeat)
}).disposed(by: disposeBag)

return Output(
currentSong: MusicPlayerManager.shared.currentMusic,
isPlaying: MusicPlayerManager.shared.isNowPlaying,
songProgress: MusicPlayerManager.shared.songProgress,
currentTime: MusicPlayerManager.shared.currentTime,
fullTime: MusicPlayerManager.shared.fullTime
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
//
// MusicPlayerView.swift
// Animal-Crossing-Wiki
//
// Created by Ari on 2022/07/24.
//

import UIKit
import RxSwift

class MusicPlayerView: UIView {

private let disposeBag = DisposeBag()

private lazy var backgroundStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .center
stackView.distribution = .equalSpacing
stackView.spacing = 10
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = UIEdgeInsets(top: 30, left: .zero, bottom: 30, right: .zero)
return stackView
}()

private lazy var songStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .leading
stackView.distribution = .fill
stackView.spacing = 12
stackView.addArrangedSubviews(coverImageView, songInfoStackView)
return stackView
}()

private lazy var songInfoStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .leading
stackView.distribution = .fill
stackView.spacing = 4
stackView.addArrangedSubviews(titleLabel, artistLabel)
return stackView
}()

private lazy var playTimeStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .fill
stackView.distribution = .fill
stackView.spacing = 5
stackView.addArrangedSubviews(durationBar, songProgressStackView)
return stackView
}()

private lazy var songProgressStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fill
stackView.addArrangedSubviews(timeElaspedLabel, durationLabel)
return stackView
}()

private lazy var buttonStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fill
stackView.spacing = 30
stackView.addArrangedSubviews(shuffleButton, previousButton, playButton, nextButton, repeatButton)
return stackView
}()

private lazy var coverImageView: UIImageView = {
let imageView = UIImageView()
imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
imageView.image = UIImage(named: "TodaySong")
return imageView
}()

private lazy var titleLabel: UILabel = {
let label = UILabel(text: "What is today's song?".localized, font: .preferredFont(forTextStyle: .headline), color: .acText)
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
label.numberOfLines = 0
return label
}()

private lazy var artistLabel: UILabel = {
let label = UILabel(
text: "Click the play button.".localized,
font: .preferredFont(for: .footnote, weight: .semibold),
color: .acText.withAlphaComponent(0.5)
)
return label
}()

private lazy var durationBar: ProgressBar = {
let progressBar = ProgressBar(height: 3)
progressBar.tintColor = .acHeaderBackground
return progressBar
}()

private lazy var timeElaspedLabel: UILabel = {
let label = UILabel(text: "0:00", font: .preferredFont(forTextStyle: .footnote), color: .acText)
label.textAlignment = .left
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return label
}()

private lazy var durationLabel: UILabel = {
let label = UILabel(text: "0:00", font: .preferredFont(forTextStyle: .footnote), color: .acText)
label.textAlignment = .right
return label
}()

lazy var previousButton: UIButton = {
let button = UIButton()
let config = UIImage.SymbolConfiguration(font: UIFont.preferredFont(for: .title3, weight: .bold))
button.setImage(UIImage(systemName: "backward.fill")?.withConfiguration(config), for: .normal)
button.tintColor = .acText
return button
}()

lazy var playButton: UIButton = {
let button = UIButton()
let config = UIImage.SymbolConfiguration(font: UIFont.preferredFont(for: .title1, weight: .bold))
button.setImage(UIImage(systemName: "play.fill")?.withConfiguration(config), for: .normal)
button.tintColor = .acText
return button
}()

lazy var nextButton: UIButton = {
let button = UIButton()
let config = UIImage.SymbolConfiguration(font: UIFont.preferredFont(for: .title3, weight: .bold))
button.setImage(UIImage(systemName: "forward.fill")?.withConfiguration(config), for: .normal)
button.tintColor = .acText
return button
}()

lazy var shuffleButton: UIButton = {
let button = UIButton()
let config = UIImage.SymbolConfiguration(font: UIFont.preferredFont(for: .body, weight: .semibold))
button.setImage(UIImage(systemName: "shuffle")?.withConfiguration(config), for: .normal)
button.tintColor = .acText
return button
}()

lazy var repeatButton: UIButton = {
let button = UIButton()
let config = UIImage.SymbolConfiguration(font: UIFont.preferredFont(for: .body, weight: .bold))
button.setImage(UIImage(systemName: "repeat")?.withConfiguration(config), for: .normal)
button.tintColor = .acText
return button
}()

convenience init(viewModel: MusicPlayerViewModel) {
self.init(frame: .zero)
backgroundColor = .clear
configure()
bind(to: viewModel)
}

private func configure() {
addSubviews(backgroundStackView)
backgroundStackView.addArrangedSubviews(
songStackView, playTimeStackView, buttonStackView
)
NSLayoutConstraint.activate([
backgroundStackView.topAnchor.constraint(equalTo: topAnchor),
backgroundStackView.heightAnchor.constraint(equalTo: heightAnchor),
backgroundStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
backgroundStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
songStackView.widthAnchor.constraint(equalTo: backgroundStackView.widthAnchor, constant: -20),
durationBar.widthAnchor.constraint(equalTo: widthAnchor, constant: -20),
songProgressStackView.widthAnchor.constraint(equalTo: durationBar.widthAnchor),
buttonStackView.heightAnchor.constraint(equalToConstant: 37)
])
}

private func bind(to viewModel: MusicPlayerViewModel) {
let input = MusicPlayerViewModel.Input(
didTapPlayButton: playButton.rx.tap.asObservable(),
didTapNextButton: nextButton.rx.tap.asObservable(),
didTapPrevButton: previousButton.rx.tap.asObservable(),
didTapShuffle: shuffleButton.rx.tap.asObservable(),
didTapRepeat: repeatButton.rx.tap.asObservable()
)

let output = viewModel.transform(input: input, disposeBag: disposeBag)
output.isPlaying
.compactMap { $0 }
.withUnretained(self)
.subscribe(onNext: { owner, isPlaying in
let config = UIImage.SymbolConfiguration(font: UIFont.preferredFont(for: .largeTitle, weight: .bold))
owner.playButton.setImage(
UIImage(systemName: isPlaying ? "pause.fill" : "play.fill")?.withConfiguration(config),
for: .normal
)
}).disposed(by: disposeBag)

output.currentSong
.compactMap { $0 }
.withUnretained(self)
.subscribe(onNext: { owner, song in
if owner.artistLabel.text != "K.K Slider" {
owner.artistLabel.text = "K.K Slider"
}
owner.titleLabel.text = song.translations.localizedName()
owner.coverImageView.setImage(with: song.image ?? "")
}).disposed(by: disposeBag)

output.songProgress
.withUnretained(self)
.subscribe(onNext: { owner, value in
owner.durationBar.setProgress(value, animated: false)
}).disposed(by: disposeBag)

output.currentTime
.withUnretained(self)
.subscribe(onNext: { owner, value in
owner.timeElaspedLabel.text = value
}).disposed(by: disposeBag)

output.fullTime
.filter { self.durationLabel.text != $0 }
.withUnretained(self)
.subscribe(onNext: { owner, value in
owner.durationLabel.text = value
}).disposed(by: disposeBag)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
"My Villagers" = "My Villagers";
"Collection Progress" = "Collection Progress";

// MARK: - MusicPlayerSection
"What is today's song?" = "What is today's song?";
"Click the play button." = "Click the play button.";

// MARK: - UserInfoView
"Please set a name." = "Please set a name.";
"Please set a Island Name." = "Please set a Island Name.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
"My Villagers" = "마을 주민";
"Collection Progress" = "수집 현황";

// MARK: - MusicPlayerSection
"What is today's song?" = "오늘의 노래는 뭘까요?";
"Click the play button." = "재생 버튼을 눌러보세요.";

// MARK: - UserInfoView
"Please set a name." = "이름을 설정해주세요.";
"Please set a Island Name." = "섬 이름을 설정해주세요.";
Expand Down

0 comments on commit 3bf262d

Please sign in to comment.