sodalive-ios/SodaLive/Sources/Content/Detail/ContentDetailPlayView.swift

192 lines
7.7 KiB
Swift

//
// ContentDetailPlayView.swift
// SodaLive
//
// Created by klaus on 2023/08/13.
//
import SwiftUI
import Kingfisher
import Sliders
struct ContentDetailPlayView: View {
let audioContent: GetAudioContentDetailResponse
let isAlertPreview: Bool
@Binding var isShowPreviewAlert: Bool
@StateObject var contentPlayManager = ContentPlayManager.shared
@State private var isRepeat = UserDefaults.bool(forKey: .isContentPlayLoop)
@State private var isEditing = false
@State private var progress: TimeInterval = 0
@State private var timer = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect()
var body: some View {
VStack(alignment: .leading, spacing: 8) {
ZStack {
KFImage(URL(string: audioContent.coverImageUrl))
.resizable()
.scaledToFill()
.frame(
width: screenSize().width - 26.7,
height: screenSize().width - 26.7,
alignment: .center
)
.cornerRadius(10.7, corners: [.topLeft, .topRight])
if let _ = audioContent.totalContentCount, let remainingContentCount = audioContent.remainingContentCount, remainingContentCount <= 0, audioContent.creator.creatorId != UserDefaults.int(forKey: .userId), !audioContent.existOrdered {
Text("Sold Out")
.font(.custom(Font.bold.rawValue, size: 36.7))
.foregroundColor(.white)
.frame(
width: screenSize().width - 26.7,
height: screenSize().width - 26.7,
alignment: .center
)
.background(Color.black.opacity(0.6))
} else if audioContent.releaseDate == nil && !isAlertPreview || (audioContent.isActivePreview && !audioContent.contentUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) {
Image(isPlaying() ? "btn_audio_content_pause" : isAlertPreview ? "btn_audio_content_preview_play" : "btn_audio_content_play")
.onTapGesture {
if isPlaying() {
contentPlayManager.pauseAudio()
} else {
contentPlayManager.startTimer = startTimer
contentPlayManager.stopTimer = stopTimer
contentPlayManager.playAudio(
contentId: audioContent.contentId,
title: audioContent.title,
nickname: audioContent.creator.nickname,
coverImage: audioContent.coverImageUrl,
contentUrl: audioContent.contentUrl,
isFree: audioContent.price <= 0,
isPreview: !audioContent.existOrdered && audioContent.price > 0
)
isShowPreviewAlert = true
}
}
} else if audioContent.releaseDate == nil {
Text("해당 콘텐츠는 크리에이터의 요청으로\n미리듣기를 제공하지 않습니다.")
.font(.custom(Font.medium.rawValue, size: 16.7))
.foregroundColor(Color.grayee)
.padding(13.3)
.background(Color.gray33.opacity(0.5))
.cornerRadius(46.7)
}
VStack(alignment: .leading, spacing: 13.3) {
Spacer()
ValueSlider(
value: audioContent.contentId == contentPlayManager.contentId ? $progress : .constant(0),
in: sliderRange(),
onEditingChanged: { editing in
isEditing = editing
if !editing {
contentPlayManager.setCurrentTime(progress)
}
}
)
.valueSliderStyle(
HorizontalValueSliderStyle(
track: HorizontalValueTrack(
view: Rectangle().foregroundColor(Color(hex: "9970ff")),
mask: Rectangle()
)
.background(Rectangle().foregroundColor(Color(hex: "979797").opacity(0.3)))
.frame(height: 5.3),
thumbSize: CGSizeZero,
options: .interactiveTrack
)
)
.frame(height: 5.3)
}
if contentPlayManager.isLoading {
LoadingView()
}
}
.frame(
width: screenSize().width - 26.7,
height: screenSize().width - 26.7
)
HStack(spacing: 0) {
Text("\(getProgress()) / \(getDuration())")
.font(.custom(Font.medium.rawValue, size: 14.7))
.foregroundColor(.white)
Spacer()
Image(
isRepeat ?
"btn_player_repeat" :
"btn_player_repeat_done"
)
.onTapGesture {
isRepeat = !UserDefaults.bool(forKey: .isContentPlayLoop)
UserDefaults.set(
isRepeat,
forKey: .isContentPlayLoop
)
}
}
.frame(width: screenSize().width - 40)
}
.onAppear {
if !isPlaying() {
stopTimer()
}
}
.onReceive(timer) { _ in
guard let player = contentPlayManager.player, !isEditing else { return }
self.progress = player.currentTime
}
}
private func isPlaying() -> Bool {
return contentPlayManager.contentId == audioContent.contentId && contentPlayManager.isPlaying
}
private func sliderRange() -> ClosedRange<Double> {
if audioContent.contentId == contentPlayManager.contentId {
return 0...contentPlayManager.duration
} else {
return 0...0
}
}
private func getProgress() -> String {
if audioContent.contentId == contentPlayManager.contentId {
return secondsToMinutesSeconds(seconds: Int(progress))
} else {
return secondsToMinutesSeconds(seconds: 0)
}
}
private func getDuration() -> String {
if audioContent.contentId == contentPlayManager.contentId {
return secondsToMinutesSeconds(seconds: Int(contentPlayManager.duration))
} else {
return audioContent.duration
}
}
private func secondsToMinutesSeconds(seconds: Int) -> String {
let hours = String(format: "%02d", seconds / 3600)
let minute = String(format: "%02d", (seconds % 3600) / 60)
let second = String(format: "%02d", seconds % 60)
return "\(hours):\(minute):\(second)"
}
private func startTimer() {
timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
}
private func stopTimer() {
timer.upstream.connect().cancel()
}
}