Parent directory

EntryView.swift

3932 bytes
  1import SwiftUI
  2
  3struct EntryView: View {
  4    let entry: LogEntry
  5
  6    @State private var showRomaji = false
  7    @State private var showEnglish = false
  8
  9    private var hasRomaji: Bool { !(entry.romaji ?? "").isEmpty }
 10    private var hasEnglish: Bool { !(entry.english ?? "").isEmpty }
 11
 12    var body: some View {
 13        VStack(alignment: .leading, spacing: 10) {
 14            if let action = entry.action {
 15                Text("> \(action)")
 16                    .font(.callout.monospaced())
 17                    .foregroundStyle(.secondary)
 18            }
 19            if let desc = entry.desc {
 20                Text(desc)
 21            }
 22            if entry.hasSpeech && (hasRomaji || hasEnglish) {
 23                speechCard
 24            }
 25            if let judge = entry.judge {
 26                if let said = judge.romaji, !said.isEmpty {
 27                    Text("said: \(said)")
 28                        .font(.callout)
 29                        .foregroundStyle(.secondary)
 30                }
 31                section("Judge") {
 32                    row("Score", "\(judge.score)/10")
 33                    if let feedback = judge.feedback, !feedback.isEmpty {
 34                        row("Feedback", feedback)
 35                    }
 36                }
 37            }
 38            if let ask = entry.ask {
 39                section("Ask") {
 40                    if let romaji = ask.romaji, !romaji.isEmpty {
 41                        row("Romaji", romaji)
 42                    }
 43                    if let translation = ask.translation, !translation.isEmpty {
 44                        row("Translation", translation)
 45                    }
 46                    if let breakdown = ask.breakdown, !breakdown.isEmpty {
 47                        row("Breakdown", breakdown)
 48                    }
 49                }
 50            }
 51        }
 52    }
 53
 54    private var speechCard: some View {
 55        GlassEffectContainer(spacing: 6) {
 56            VStack(alignment: .leading, spacing: 8) {
 57                if hasRomaji {
 58                    revealChip(title: "Romaji", revealed: $showRomaji, text: entry.romaji)
 59                }
 60                if hasEnglish {
 61                    revealChip(title: "Translation", revealed: $showEnglish, text: entry.english)
 62                }
 63            }
 64        }
 65    }
 66
 67    private func revealChip(title: String, revealed: Binding<Bool>, text: String?) -> some View {
 68        Button {
 69            withAnimation(.snappy) {
 70                revealed.wrappedValue.toggle()
 71            }
 72        } label: {
 73            HStack(alignment: .firstTextBaseline, spacing: 8) {
 74                if revealed.wrappedValue {
 75                    Text(text ?? "")
 76                } else {
 77                    Image(systemName: "eye.slash")
 78                        .font(.caption)
 79                    Text(title)
 80                        .font(.callout)
 81                }
 82                Spacer(minLength: 0)
 83            }
 84            .foregroundStyle(revealed.wrappedValue ? AnyShapeStyle(.primary) : AnyShapeStyle(.secondary))
 85            .frame(maxWidth: .infinity, alignment: .leading)
 86            .padding(.horizontal, 14)
 87            .padding(.vertical, 10)
 88            .glassEffect(in: .rect(cornerRadius: 12))
 89            .contentShape(.interaction, .rect(cornerRadius: 12))
 90        }
 91        .buttonStyle(.plain)
 92    }
 93
 94    private func section(_ title: String, @ViewBuilder rows: () -> some View) -> some View {
 95        VStack(alignment: .leading, spacing: 4) {
 96            Text(title)
 97                .font(.footnote.weight(.semibold))
 98                .foregroundStyle(.secondary)
 99            rows()
100        }
101    }
102
103    private func row(_ key: String, _ value: String) -> some View {
104        HStack(alignment: .firstTextBaseline, spacing: 12) {
105            Text(key)
106                .foregroundStyle(.secondary)
107                .frame(width: 84, alignment: .leading)
108            Text(value)
109        }
110        .font(.footnote)
111    }
112}