2024-MacC-M14-Medio / SqoopDesignSystem / Sources / SQComponents / SQViews / SQThumbnailBox.swift
SQThumbnailBox.swift
Raw
//
//  SQThumbnailBox.swift
//  Sqoop
//
//  Created by Greem on 10/11/24.
//

import SwiftUI
import SqoopDesignSystem

public extension SQThumbnailBox {
    
    ///  URL     ,      .
    enum ThumbnailInfoType: Hashable {
        case length(Int)
        case songs(Int)
    }
}

// MARK: - SQThumbnailBox

///  URL     .
public struct SQThumbnailBox: View {
    
    public let playlistName: String
    public let channelName: String
    public let thumbnailInfoType: ThumbnailInfoType
    public let originalUrlString: String
    public let thumbnailImageUrlString: String
    public let thumbnailBoxTapAction: () -> Void
    
    public init(
        playlistName: String,
        channelName: String,
        thumbnailInfoType: ThumbnailInfoType,
        originalUrlString: String,
        thumbnailImageUrlString: String,
        thumbnailBoxTapAction: @escaping () -> Void
    ) {
        self.playlistName = playlistName
        self.channelName = channelName
        self.thumbnailInfoType = thumbnailInfoType
        self.originalUrlString = originalUrlString
        self.thumbnailImageUrlString = thumbnailImageUrlString
        self.thumbnailBoxTapAction = thumbnailBoxTapAction
    }
    
    public var body: some View {
        Button {
            thumbnailBoxTapAction()
        } label: {
            VStack(alignment: .leading, spacing: 0) {
                ThumbnailImage(
                    originalUrlString: originalUrlString,
                    thumbnailImageUrlString: thumbnailImageUrlString
                )
                .padding(.top, 20)
                
                Text(playlistName)
                    .padding(.top, 16)
                    .font(.headline03(.semiBold))
                    .foregroundStyle(Color.textPrimary)
                    .lineLimit(2)
                    .multilineTextAlignment(.leading)
                
                Spacer()
                
                HStack(spacing: 10) {
                    ChannelNameLabel(channelName: channelName)
                    ThumbnailInfoLabel(thumbnailInfo: thumbnailInfoType)
                }
                .padding(.bottom, 20)
            }
            .modifier(ThumbnailBoxViewModifier())
        }
    }
}

// MARK: - ThumbnailImage

private struct ThumbnailImage: View {
    
    let originalUrlString: String
    let thumbnailImageUrlString: String
    
    private let imageHeight: CGFloat = 171
    
    private var thumbnailImageUrl: URL? {
        URL(string: thumbnailImageUrlString)
    }
    
    var body: some View {
        AsyncImage(url: thumbnailImageUrl) { phase in
            if let image = phase.image {
                image
                    .resizable().aspectRatio(16/9, contentMode: .fit)
                    .clipShape(RoundedRectangle(cornerRadius: 4))
            } else {
                Image(.emptyThumbnail)
                    .resizable().aspectRatio(16/9, contentMode: .fit)
                    .clipShape(RoundedRectangle(cornerRadius: 4))
            }
        }
    }
}


// MARK: - SQThumbnailBox  Label

fileprivate extension SQThumbnailBox {
    
    private struct CoreInfoLabel: View {
        
        let symbol: SFSymbol
        let infoString: String
        
        var body: some View {
            HStack(spacing: 6) {
                Image(symbol: symbol)
                    .resizable()
                    .scaledToFill()
                    .frame(width: 20, height: 20)
                
                Text(infoString)
                    .fontWithLineHeight(.body01(weight: .regular))
                    .lineLimit(1)
            }
            .foregroundStyle(Color.textSecondary)
        }
    }
    
    struct ChannelNameLabel: View {
        let channelName: String
        var body: some View {
            CoreInfoLabel(
                symbol: .channelName,
                infoString: channelName
            )
        }
    }
    
    // MARK: - DurationTimeLabel
    
    struct ThumbnailInfoLabel: View {
        var thumbnailInfo: ThumbnailInfoType
        var body: some View {
            CoreInfoLabel(
                symbol: infoSymbol,
                infoString: infoString
            )
        }
        private var infoSymbol: SFSymbol {
            switch thumbnailInfo {
            case .length: .videoLength
            case .songs: .songCount
            }
        }
        private var infoString: String {
            switch thumbnailInfo {
            case .length(let len): durationTimeFormat(durationTime: len)
            case .songs(let songs): "Total %d".localized(with: [songs])
            }
        }
        
        private func durationTimeFormat(durationTime: Int)-> String {
            let hours = durationTime / 3600
            let minutes = (durationTime % 3600) / 60
            let seconds = durationTime % 60
            return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
        }
    }
}

// MARK: - ThumbnailBoxGradient

private struct ThumbnailBoxViewModifier: ViewModifier {
    
    func body(content: Content) -> some View {
        content
            .padding(.horizontal, 20)
            .background(Color(hex: "#8F8F8F").opacity(0.16))
            .background(
                GeometryReader { proxy in
                    RadialGradient(
                        stops: .thumbnailBox,
                        center: UnitPoint(x: 0.2, y: 0.32),
                        startRadius: 0,
                        endRadius: proxy.size.width
                    )
                }
            )
            .background(StrokeBorder())
            .clipShape(RoundedRectangle(cornerRadius: 8))
            .frame(minWidth: 343, maxWidth: 408, minHeight: 314, maxHeight: 373)
            .aspectRatio(343/314, contentMode: .fit)
    }
    
    struct StrokeBorder: View {
        var body: some View {
            GeometryReader { proxy in
                RoundedRectangle(cornerRadius: 8)
                    .strokeBorder(RadialGradient(stops: [
                        .init(color: .init(hex: "#000000").opacity(0.3), location: 0),
                        .init(color: .init(hex: "#0ED0DB"), location: 0.5),
                        .init(color: .init(hex: "#656565").opacity(0.3), location: 1)
                    ], center: {
                        var center: UnitPoint = .topLeading
                        center.x += 0.19; center.y += 0.32
                        return center
                    }(), startRadius: 0, endRadius: proxy.size.width), lineWidth: 1.5)
            }
        }
    }
}

// MARK: - Preview

#Preview {
    ZStack {
        Color.black.ignoresSafeArea()
        
        SQThumbnailBox(
            playlistName: "플레이리스트 제목 입력",
            channelName: "한톨TV",
            thumbnailInfoType: .songs(32),
            originalUrlString: "https://www.youtube.com/watch?v=fTwVEu6itik",
            thumbnailImageUrlString: ""
        ) {}
        .padding(.horizontal, 16)
    }
    .loadSqoopFontSystem()
}