import SwiftUI struct MainHomeFollowingLiveSection: View { let onAirLives: [FollowingLiveResponse] let onTapLive: (Int) -> Void init( onAirLives: [FollowingLiveResponse], onTapLive: @escaping (Int) -> Void = { _ in } ) { self.onAirLives = onAirLives self.onTapLive = onTapLive } var body: some View { if !onAirLives.isEmpty { VStack(alignment: .leading, spacing: SodaSpacing.s14) { SectionTitle(title: I18n.HomeFollowing.onAirTitle) ScrollView(.horizontal, showsIndicators: false) { LazyHStack(alignment: .top, spacing: SodaSpacing.s8) { ForEach(onAirLives) { live in MainHomeFollowingLiveItemView(live: live) { onTapLive(live.liveId) } } } .padding(.leading, SodaSpacing.s14) } } } } } private struct MainHomeFollowingLiveItemView: View { let live: FollowingLiveResponse let action: () -> Void var body: some View { Button(action: action) { HStack(alignment: .center, spacing: SodaSpacing.s8) { DownsampledKFImage(url: URL(string: live.creatorProfileImageUrl), size: CGSize(width: 75, height: 75)) .clipShape(Circle()) VStack(alignment: .leading, spacing: SodaSpacing.s4) { HStack(alignment: .center, spacing: SodaSpacing.s8) { LiveTagView() Text(live.relativeStartedAtText()) .appFont(size: 14, weight: .regular) .foregroundColor(Color.gray500) .lineLimit(1) .fixedSize(horizontal: true, vertical: false) } Text(live.title) .appFont(size: 18, weight: .bold) .foregroundColor(.white) .lineLimit(1) .truncationMode(.tail) Text(live.creatorNickname) .appFont(size: 14, weight: .regular) .foregroundColor(Color.gray500) .lineLimit(1) .truncationMode(.tail) } .frame(width: 149, alignment: .leading) } .padding(.leading, SodaSpacing.s12) .padding(.trailing, 22) .padding(.vertical, SodaSpacing.s12) .background(Color.gray900) .clipShape(Capsule()) .overlay { Capsule() .stroke(Color(hex: "62CFFF"), lineWidth: 2) } } .buttonStyle(.plain) } } private struct LiveTagView: View { var body: some View { HStack(alignment: .center, spacing: SodaSpacing.s4) { Circle() .fill(Color.red400) .frame(width: 8, height: 8) Text(I18n.HomeFollowing.liveTag) .appFont(size: 12, weight: .regular) .foregroundColor(.white) .lineLimit(1) } .padding(.horizontal, SodaSpacing.s4) .padding(.vertical, 2) .frame(height: 18) .background(Color.black) .clipShape(Capsule()) } } private extension FollowingLiveResponse { func relativeStartedAtText(now: Date = Date()) -> String { DateParser.relativeTimeText(fromUTC: startedAtUtc, fallback: startedAtUtc, now: now) } } struct MainHomeFollowingLiveSection_Previews: PreviewProvider { private static let previewImageUrl = "https://placehold.co/500" static var previews: some View { MainHomeFollowingLiveSection( onAirLives: [ FollowingLiveResponse( liveId: 1, creatorProfileImageUrl: previewImageUrl, creatorNickname: "크리에이터", title: "오늘 밤 같이 이야기해요", startedAtUtc: "2026-07-01T00:30:00Z" ), FollowingLiveResponse( liveId: 2, creatorProfileImageUrl: previewImageUrl, creatorNickname: "소다", title: "신규 콘텐츠 제작 비하인드", startedAtUtc: "2026-07-01T00:10:00Z" ) ] ) .padding(.vertical, SodaSpacing.s20) .background(Color.black) .previewLayout(.sizeThatFits) } }