| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- import SwiftUI
- // MARK: - Title Goal Picker
- struct TitleGoalPicker: View {
- @Binding var selectedGoal: TitleGoal
- var body: some View {
- LazyVGrid(
- columns: [GridItem(.flexible()), GridItem(.flexible())],
- spacing: 8
- ) {
- ForEach(TitleGoal.allCases) { goal in
- Button {
- selectedGoal = goal
- } label: {
- VStack(alignment: .leading, spacing: 2) {
- Text(goal.title)
- .font(.system(size: 11, weight: .semibold))
- .foregroundStyle(AppTheme.textPrimary)
- Text(goal.subtitle)
- .font(.system(size: 9))
- .foregroundStyle(AppTheme.textTertiary)
- .lineLimit(1)
- }
- .frame(maxWidth: .infinity, alignment: .leading)
- .padding(.horizontal, 10)
- .padding(.vertical, 8)
- .background(
- RoundedRectangle(cornerRadius: 8)
- .fill(
- selectedGoal == goal
- ? AppTheme.accentBlue.opacity(0.15)
- : AppTheme.panelBackground
- )
- )
- .overlay(
- RoundedRectangle(cornerRadius: 8)
- .stroke(
- selectedGoal == goal
- ? AppTheme.accentBlue.opacity(0.5)
- : AppTheme.cardBorder,
- lineWidth: 1
- )
- )
- }
- .buttonStyle(AppPlainButtonStyle())
- .hoverOverlay(cornerRadius: 8)
- }
- }
- }
- }
- // MARK: - Variant Count Picker
- struct TitleVariantCountPicker: View {
- @Binding var selectedCount: TitleVariantCount
- var body: some View {
- HStack(spacing: 8) {
- ForEach(TitleVariantCount.allCases) { count in
- Button {
- selectedCount = count
- } label: {
- Text(count.label)
- .font(.system(size: 10, weight: .semibold))
- .foregroundStyle(selectedCount == count ? .white : AppTheme.textSecondary)
- .frame(maxWidth: .infinity)
- .padding(.vertical, 7)
- .background(
- RoundedRectangle(cornerRadius: 7)
- .fill(
- selectedCount == count
- ? AppTheme.accentBlue
- : AppTheme.panelBackground
- )
- )
- .overlay(
- RoundedRectangle(cornerRadius: 7)
- .stroke(AppTheme.cardBorder, lineWidth: 1)
- )
- }
- .buttonStyle(AppPlainButtonStyle())
- .hoverOverlay(cornerRadius: 8)
- }
- }
- }
- }
- // MARK: - Title Analysis Card
- struct TitleAnalysisCard: View {
- let analysis: TitleAnalysis
- var body: some View {
- VStack(alignment: .leading, spacing: 14) {
- HStack {
- VStack(alignment: .leading, spacing: 2) {
- Text("Title Analysis")
- .font(.system(size: 12, weight: .semibold))
- .foregroundStyle(AppTheme.textPrimary)
- Text("How your current title performs")
- .font(.system(size: 10))
- .foregroundStyle(AppTheme.textSecondary)
- }
- Spacer()
- TitleScoreBadge(score: analysis.overallScore, size: .large)
- }
- HStack(spacing: 12) {
- scoreMetric(label: "Length", score: analysis.lengthScore)
- scoreMetric(label: "Engagement", score: analysis.engagementScore)
- scoreMetric(label: "Clarity", score: analysis.clarityScore)
- }
- if !analysis.suggestions.isEmpty {
- VStack(alignment: .leading, spacing: 6) {
- Text("Suggestions")
- .font(.system(size: 10, weight: .medium))
- .foregroundStyle(AppTheme.textTertiary)
- ForEach(analysis.suggestions, id: \.self) { suggestion in
- HStack(alignment: .top, spacing: 6) {
- Image(systemName: "lightbulb.fill")
- .font(.system(size: 9))
- .foregroundStyle(AppTheme.accentYellow)
- .padding(.top, 2)
- Text(suggestion)
- .font(.system(size: 10))
- .foregroundStyle(AppTheme.textSecondary)
- .fixedSize(horizontal: false, vertical: true)
- }
- }
- }
- }
- }
- .padding(14)
- .frame(maxWidth: .infinity, alignment: .leading)
- .background(
- RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall)
- .fill(AppTheme.cardBackground)
- .overlay(
- RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall)
- .stroke(AppTheme.cardBorder, lineWidth: 1)
- )
- )
- }
- private func scoreMetric(label: String, score: Int) -> some View {
- VStack(spacing: 4) {
- TitleScoreBadge(score: score, size: .small)
- Text(label)
- .font(.system(size: 9, weight: .medium))
- .foregroundStyle(AppTheme.textTertiary)
- }
- .frame(maxWidth: .infinity)
- }
- }
- // MARK: - Score Badge
- struct TitleScoreBadge: View {
- enum Size {
- case small
- case large
- var fontSize: CGFloat {
- switch self {
- case .small: 11
- case .large: 18
- }
- }
- var padding: CGFloat {
- switch self {
- case .small: 6
- case .large: 10
- }
- }
- }
- let score: Int
- var size: Size = .small
- var body: some View {
- Text("\(score)")
- .font(.system(size: size.fontSize, weight: .bold, design: .rounded))
- .foregroundStyle(scoreColor)
- .padding(size.padding)
- .background(
- Circle()
- .fill(scoreColor.opacity(0.15))
- )
- .overlay(
- Circle()
- .stroke(scoreColor.opacity(0.3), lineWidth: 1)
- )
- }
- private var scoreColor: Color {
- switch score {
- case 80...100: AppTheme.accentGreen
- case 60..<80: AppTheme.accentYellow
- case 40..<60: AppTheme.accentOrange
- default: Color(hex: 0xEF4444)
- }
- }
- }
- // MARK: - Title Variant Row
- struct TitleVariantRow: View {
- let variant: TitleVariant
- let isSelected: Bool
- let onSelect: () -> Void
- let onApply: () -> Void
- var body: some View {
- HStack(alignment: .top, spacing: 12) {
- Button(action: onSelect) {
- Circle()
- .stroke(isSelected ? AppTheme.accentBlue : AppTheme.cardBorder, lineWidth: 2)
- .frame(width: 16, height: 16)
- .overlay {
- if isSelected {
- Circle()
- .fill(AppTheme.accentBlue)
- .frame(width: 8, height: 8)
- }
- }
- }
- .buttonStyle(AppPlainButtonStyle())
- .hoverOverlay(cornerRadius: 8)
- .padding(.top, 2)
- VStack(alignment: .leading, spacing: 6) {
- HStack(alignment: .top) {
- Text(variant.title)
- .font(.system(size: 12, weight: .medium))
- .foregroundStyle(AppTheme.textPrimary)
- .fixedSize(horizontal: false, vertical: true)
- Spacer(minLength: 8)
- TitleScoreBadge(score: variant.score)
- }
- Text(variant.reasoning)
- .font(.system(size: 10))
- .foregroundStyle(AppTheme.textTertiary)
- HStack(spacing: 8) {
- Text("\(variant.title.count) chars")
- .font(.system(size: 9))
- .foregroundStyle(
- variant.title.count > 300 ? Color(hex: 0xF87171) : AppTheme.textTertiary
- )
- Spacer()
- Button(action: onApply) {
- Text("Use Title")
- .font(.system(size: 10, weight: .semibold))
- .foregroundStyle(AppTheme.accentBlue)
- }
- .buttonStyle(AppPlainButtonStyle())
- .hoverOverlay(cornerRadius: 8)
- }
- }
- }
- .padding(12)
- .background(
- RoundedRectangle(cornerRadius: 8)
- .fill(isSelected ? AppTheme.accentBlue.opacity(0.08) : AppTheme.panelBackground)
- )
- .overlay(
- RoundedRectangle(cornerRadius: 8)
- .stroke(
- isSelected ? AppTheme.accentBlue.opacity(0.4) : AppTheme.cardBorder,
- lineWidth: 1
- )
- )
- .hoverCard(cornerRadius: 8, isSelected: isSelected)
- }
- }
- // MARK: - Compare Card
- struct TitleCompareCard: View {
- let title: String
- let score: Int?
- let label: String
- let isOriginal: Bool
- var isSelected: Bool = false
- var body: some View {
- VStack(alignment: .leading, spacing: 10) {
- HStack {
- Text(label)
- .font(.system(size: 10, weight: .semibold))
- .foregroundStyle(isOriginal ? AppTheme.textSecondary : AppTheme.accentBlue)
- .padding(.horizontal, 8)
- .padding(.vertical, 3)
- .background(
- Capsule()
- .fill(
- isOriginal
- ? AppTheme.panelBackground
- : AppTheme.accentBlue.opacity(0.15)
- )
- )
- Spacer()
- if let score {
- TitleScoreBadge(score: score)
- }
- }
- Text(title.isEmpty ? "No title" : title)
- .font(.system(size: 13, weight: .semibold))
- .foregroundStyle(title.isEmpty ? AppTheme.textTertiary : AppTheme.textPrimary)
- .fixedSize(horizontal: false, vertical: true)
- HStack {
- Text("\(title.count) / 300 characters")
- .font(.system(size: 9))
- .foregroundStyle(
- title.count > 300 ? Color(hex: 0xF87171) : AppTheme.textTertiary
- )
- Spacer()
- if isSelected {
- Label("Active", systemImage: "checkmark.circle.fill")
- .font(.system(size: 9, weight: .semibold))
- .foregroundStyle(AppTheme.accentGreen)
- }
- }
- }
- .padding(14)
- .frame(maxWidth: .infinity, alignment: .leading)
- .background(
- RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall)
- .fill(AppTheme.cardBackground)
- .overlay(
- RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall)
- .stroke(
- isSelected ? AppTheme.accentBlue.opacity(0.5) : AppTheme.cardBorder,
- lineWidth: isSelected ? 1.5 : 1
- )
- )
- )
- .hoverCard(cornerRadius: AppTheme.cornerRadiusSmall, isSelected: isSelected)
- .hoverCursor()
- }
- }
- // MARK: - Feed Preview Row
- struct TitleFeedPreviewRow: View {
- let title: String
- let subreddit: String
- var isHighlighted: Bool = false
- var body: some View {
- HStack(alignment: .top, spacing: 10) {
- RoundedRectangle(cornerRadius: 4)
- .fill(AppTheme.cardBackground)
- .frame(width: 72, height: 52)
- .overlay {
- Image(systemName: "photo")
- .font(.system(size: 16))
- .foregroundStyle(AppTheme.textTertiary)
- }
- VStack(alignment: .leading, spacing: 4) {
- Text(subreddit)
- .font(.system(size: 9, weight: .semibold))
- .foregroundStyle(AppTheme.textTertiary)
- Text(title.isEmpty ? "Title preview" : title)
- .font(.system(size: 11, weight: .semibold))
- .foregroundStyle(title.isEmpty ? AppTheme.textTertiary : AppTheme.textPrimary)
- .lineLimit(2)
- .fixedSize(horizontal: false, vertical: true)
- }
- Spacer(minLength: 0)
- }
- .padding(10)
- .background(
- RoundedRectangle(cornerRadius: 8)
- .fill(isHighlighted ? AppTheme.accentBlue.opacity(0.08) : AppTheme.panelBackground)
- )
- .overlay(
- RoundedRectangle(cornerRadius: 8)
- .stroke(
- isHighlighted ? AppTheme.accentBlue.opacity(0.4) : AppTheme.cardBorder,
- lineWidth: 1
- )
- )
- }
- }
- // MARK: - Empty State
- struct TitleOptimizerEmptyState: View {
- let icon: String
- let title: String
- let subtitle: String
- var body: some View {
- VStack(spacing: 10) {
- Image(systemName: icon)
- .font(.system(size: 28))
- .foregroundStyle(AppTheme.accentBlue.opacity(0.6))
- Text(title)
- .font(.system(size: 12, weight: .semibold))
- .foregroundStyle(AppTheme.textSecondary)
- Text(subtitle)
- .font(.system(size: 10))
- .foregroundStyle(AppTheme.textTertiary)
- .multilineTextAlignment(.center)
- }
- .frame(maxWidth: .infinity)
- .padding(.vertical, 32)
- .background(
- RoundedRectangle(cornerRadius: 8)
- .fill(AppTheme.panelBackground)
- .overlay(
- RoundedRectangle(cornerRadius: 8)
- .strokeBorder(
- AppTheme.accentBlue.opacity(0.2),
- style: StrokeStyle(lineWidth: 1, dash: [6, 4])
- )
- )
- )
- }
- }
|