|
|
@@ -4,24 +4,35 @@ struct TitleOptimizerView: View {
|
|
|
@Environment(\.requirePremiumAccess) private var requirePremiumAccess
|
|
|
@Bindable var viewModel: TitleOptimizerViewModel
|
|
|
|
|
|
+ private enum Layout {
|
|
|
+ static let horizontalPadding: CGFloat = 24
|
|
|
+ static let columnWidth: CGFloat = 300
|
|
|
+ static let columnSpacing: CGFloat = 20
|
|
|
+ static let sectionSpacing: CGFloat = 12
|
|
|
+ }
|
|
|
+
|
|
|
var body: some View {
|
|
|
VStack(spacing: 0) {
|
|
|
header
|
|
|
- tabBar
|
|
|
+ messageBanners
|
|
|
|
|
|
ScrollView {
|
|
|
- PostPageBoundary {
|
|
|
- switch viewModel.selectedTab {
|
|
|
- case .optimize:
|
|
|
- optimizeContent
|
|
|
- case .compare:
|
|
|
- compareContent
|
|
|
- case .preview:
|
|
|
- previewContent
|
|
|
+ VStack(alignment: .leading, spacing: 12) {
|
|
|
+ tabBar
|
|
|
+
|
|
|
+ PostPageBoundary {
|
|
|
+ switch viewModel.selectedTab {
|
|
|
+ case .optimize:
|
|
|
+ optimizeContent
|
|
|
+ case .compare:
|
|
|
+ compareContent
|
|
|
+ case .preview:
|
|
|
+ previewContent
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- .padding(.horizontal, 24)
|
|
|
- .padding(.top, 8)
|
|
|
+ .padding(.horizontal, Layout.horizontalPadding)
|
|
|
+ .padding(.top, 12)
|
|
|
.padding(.bottom, 24)
|
|
|
}
|
|
|
}
|
|
|
@@ -47,7 +58,7 @@ struct TitleOptimizerView: View {
|
|
|
|
|
|
headerActions
|
|
|
}
|
|
|
- .padding(.horizontal, 24)
|
|
|
+ .padding(.horizontal, Layout.horizontalPadding)
|
|
|
.padding(.vertical, 16)
|
|
|
.overlay(alignment: .bottom) {
|
|
|
Rectangle()
|
|
|
@@ -56,6 +67,19 @@ struct TitleOptimizerView: View {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ @ViewBuilder
|
|
|
+ private var messageBanners: some View {
|
|
|
+ if let error = viewModel.errorMessage {
|
|
|
+ PostGeneratorMessageBanner(message: error, isError: true)
|
|
|
+ .padding(.horizontal, Layout.horizontalPadding)
|
|
|
+ .padding(.top, 8)
|
|
|
+ } else if let success = viewModel.successMessage {
|
|
|
+ PostGeneratorMessageBanner(message: success, isError: false)
|
|
|
+ .padding(.horizontal, Layout.horizontalPadding)
|
|
|
+ .padding(.top, 8)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private var headerActions: some View {
|
|
|
HStack(spacing: 8) {
|
|
|
Button {
|
|
|
@@ -105,44 +129,100 @@ struct TitleOptimizerView: View {
|
|
|
// MARK: - Tab Bar
|
|
|
|
|
|
private var tabBar: some View {
|
|
|
- HStack(spacing: 4) {
|
|
|
+ HStack(spacing: 0) {
|
|
|
ForEach(TitleOptimizerTab.allCases) { tab in
|
|
|
Button {
|
|
|
- viewModel.selectedTab = tab
|
|
|
+ withAnimation(.easeInOut(duration: 0.15)) {
|
|
|
+ viewModel.selectedTab = tab
|
|
|
+ }
|
|
|
} label: {
|
|
|
Text(tab.title)
|
|
|
.font(.system(size: 11, weight: .semibold))
|
|
|
.foregroundStyle(viewModel.selectedTab == tab ? AppTheme.textPrimary : AppTheme.textSecondary)
|
|
|
- .padding(.horizontal, 14)
|
|
|
- .padding(.vertical, 8)
|
|
|
+ .frame(maxWidth: .infinity)
|
|
|
+ .padding(.vertical, 7)
|
|
|
.background(
|
|
|
viewModel.selectedTab == tab
|
|
|
? AppTheme.cardBackground
|
|
|
: Color.clear
|
|
|
)
|
|
|
- .clipShape(RoundedRectangle(cornerRadius: 8))
|
|
|
+ .clipShape(RoundedRectangle(cornerRadius: 7))
|
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
|
}
|
|
|
-
|
|
|
- Spacer()
|
|
|
}
|
|
|
- .padding(.horizontal, 24)
|
|
|
- .padding(.top, 12)
|
|
|
- .padding(.bottom, 4)
|
|
|
+ .padding(3)
|
|
|
+ .frame(width: 300)
|
|
|
+ .background(AppTheme.panelBackground)
|
|
|
+ .clipShape(RoundedRectangle(cornerRadius: 9))
|
|
|
+ .overlay(
|
|
|
+ RoundedRectangle(cornerRadius: 9)
|
|
|
+ .stroke(AppTheme.cardBorder, lineWidth: 1)
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
// MARK: - Optimize
|
|
|
|
|
|
private var optimizeContent: some View {
|
|
|
- HStack(alignment: .top, spacing: 16) {
|
|
|
- configurationPanel
|
|
|
- editorPanel
|
|
|
+ ViewThatFits(in: .horizontal) {
|
|
|
+ optimizeColumns
|
|
|
+
|
|
|
+ VStack(alignment: .leading, spacing: Layout.columnSpacing) {
|
|
|
+ optimizeColumn(title: "Configuration", icon: "slider.horizontal.3") {
|
|
|
+ configurationPanel
|
|
|
+ }
|
|
|
+ optimizeColumn(title: "Title & Suggestions", icon: "textformat") {
|
|
|
+ editorPanel
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private var optimizeColumns: some View {
|
|
|
+ HStack(alignment: .top, spacing: Layout.columnSpacing) {
|
|
|
+ optimizeColumn(title: "Configuration", icon: "slider.horizontal.3") {
|
|
|
+ configurationPanel
|
|
|
+ }
|
|
|
+ .frame(width: Layout.columnWidth)
|
|
|
+
|
|
|
+ Rectangle()
|
|
|
+ .fill(AppTheme.border)
|
|
|
+ .frame(width: 1)
|
|
|
+ .padding(.vertical, 2)
|
|
|
+
|
|
|
+ optimizeColumn(title: "Title & Suggestions", icon: "textformat") {
|
|
|
+ editorPanel
|
|
|
+ }
|
|
|
+ .frame(maxWidth: .infinity)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func optimizeColumn<Content: View>(
|
|
|
+ title: String,
|
|
|
+ icon: String,
|
|
|
+ @ViewBuilder content: () -> Content
|
|
|
+ ) -> some View {
|
|
|
+ VStack(alignment: .leading, spacing: Layout.sectionSpacing) {
|
|
|
+ sectionHeader(title: title, icon: icon)
|
|
|
+ content()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private func sectionHeader(title: String, icon: String) -> some View {
|
|
|
+ HStack(spacing: 6) {
|
|
|
+ Image(systemName: icon)
|
|
|
+ .font(.system(size: 10, weight: .semibold))
|
|
|
+ .foregroundStyle(AppTheme.accentBlue)
|
|
|
+ Text(title)
|
|
|
+ .font(.system(size: 11, weight: .semibold))
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
+ }
|
|
|
+ .textCase(.uppercase)
|
|
|
+ .tracking(0.4)
|
|
|
+ }
|
|
|
+
|
|
|
private var configurationPanel: some View {
|
|
|
- VStack(spacing: 12) {
|
|
|
+ VStack(spacing: Layout.sectionSpacing) {
|
|
|
PostFormCard(title: "Target Subreddit", subtitle: "Tailor titles for the community") {
|
|
|
PostFormField(
|
|
|
label: "Subreddit",
|
|
|
@@ -173,13 +253,39 @@ struct TitleOptimizerView: View {
|
|
|
TitleGoalPicker(selectedGoal: $viewModel.draft.titleGoal)
|
|
|
}
|
|
|
|
|
|
- PostFormCard(title: "Post Context", subtitle: "Optional — affects preview style") {
|
|
|
+ PostFormCard(title: "Preview Context", subtitle: "Optional — affects preview style") {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
PostTypePicker(selectedType: Binding(
|
|
|
get: { viewModel.draft.postType },
|
|
|
set: { viewModel.selectPostType($0) }
|
|
|
))
|
|
|
|
|
|
+ HStack(spacing: 8) {
|
|
|
+ PostTagToggle(
|
|
|
+ title: "NSFW",
|
|
|
+ systemImage: "exclamationmark.triangle.fill",
|
|
|
+ activeColor: Color(hex: 0xEF4444),
|
|
|
+ isOn: $viewModel.draft.isNSFW
|
|
|
+ )
|
|
|
+ .frame(maxWidth: .infinity)
|
|
|
+
|
|
|
+ PostTagToggle(
|
|
|
+ title: "Spoiler",
|
|
|
+ systemImage: "eye.slash.fill",
|
|
|
+ activeColor: AppTheme.textSecondary,
|
|
|
+ isOn: $viewModel.draft.isSpoiler
|
|
|
+ )
|
|
|
+ .frame(maxWidth: .infinity)
|
|
|
+
|
|
|
+ PostTagToggle(
|
|
|
+ title: "OC",
|
|
|
+ systemImage: "star.fill",
|
|
|
+ activeColor: AppTheme.accentGreen,
|
|
|
+ isOn: $viewModel.draft.isOC
|
|
|
+ )
|
|
|
+ .frame(maxWidth: .infinity)
|
|
|
+ }
|
|
|
+
|
|
|
PostFormField(
|
|
|
label: "Flair",
|
|
|
placeholder: "Discussion, Question, Meme…",
|
|
|
@@ -188,44 +294,14 @@ struct TitleOptimizerView: View {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- PostFormCard(title: "Post Tags", subtitle: "Reddit content labels") {
|
|
|
- HStack(spacing: 6) {
|
|
|
- PostTagToggle(
|
|
|
- title: "NSFW",
|
|
|
- systemImage: "exclamationmark.triangle.fill",
|
|
|
- activeColor: Color(hex: 0xEF4444),
|
|
|
- isOn: $viewModel.draft.isNSFW
|
|
|
- )
|
|
|
- PostTagToggle(
|
|
|
- title: "Spoiler",
|
|
|
- systemImage: "eye.slash.fill",
|
|
|
- activeColor: AppTheme.textSecondary,
|
|
|
- isOn: $viewModel.draft.isSpoiler
|
|
|
- )
|
|
|
- PostTagToggle(
|
|
|
- title: "OC",
|
|
|
- systemImage: "star.fill",
|
|
|
- activeColor: AppTheme.accentGreen,
|
|
|
- isOn: $viewModel.draft.isOC
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
PostFormCard(title: "Suggestions", subtitle: "How many variants to generate") {
|
|
|
TitleVariantCountPicker(selectedCount: $viewModel.draft.variantCount)
|
|
|
}
|
|
|
-
|
|
|
- if let error = viewModel.errorMessage {
|
|
|
- PostGeneratorMessageBanner(message: error, isError: true)
|
|
|
- } else if let success = viewModel.successMessage {
|
|
|
- PostGeneratorMessageBanner(message: success, isError: false)
|
|
|
- }
|
|
|
}
|
|
|
- .frame(width: 300)
|
|
|
}
|
|
|
|
|
|
private var editorPanel: some View {
|
|
|
- VStack(spacing: 12) {
|
|
|
+ VStack(spacing: Layout.sectionSpacing) {
|
|
|
PostFormCard(title: "Original Title", subtitle: "The title you want to improve") {
|
|
|
PostFormField(
|
|
|
label: "Post Title",
|
|
|
@@ -242,7 +318,6 @@ struct TitleOptimizerView: View {
|
|
|
|
|
|
suggestionsSection
|
|
|
}
|
|
|
- .frame(maxWidth: .infinity)
|
|
|
}
|
|
|
|
|
|
@ViewBuilder
|
|
|
@@ -275,9 +350,9 @@ struct TitleOptimizerView: View {
|
|
|
}
|
|
|
|
|
|
private var characterCountFooter: some View {
|
|
|
- HStack {
|
|
|
+ HStack(spacing: 12) {
|
|
|
Text("\(viewModel.draft.originalTitle.count) / 300")
|
|
|
- .font(.system(size: 10))
|
|
|
+ .font(.system(size: 10, weight: .medium, design: .monospaced))
|
|
|
.foregroundStyle(
|
|
|
viewModel.draft.originalTitle.count > 300 ? Color(hex: 0xF87171) : AppTheme.textTertiary
|
|
|
)
|
|
|
@@ -288,74 +363,84 @@ struct TitleOptimizerView: View {
|
|
|
.font(.system(size: 10))
|
|
|
.foregroundStyle(AppTheme.textTertiary)
|
|
|
}
|
|
|
+ .padding(.horizontal, 12)
|
|
|
+ .padding(.vertical, 9)
|
|
|
+ .background(
|
|
|
+ RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall)
|
|
|
+ .fill(AppTheme.cardBackground)
|
|
|
+ .overlay(
|
|
|
+ RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall)
|
|
|
+ .stroke(AppTheme.cardBorder, lineWidth: 1)
|
|
|
+ )
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
// MARK: - Compare
|
|
|
|
|
|
private var compareContent: some View {
|
|
|
- VStack(spacing: 16) {
|
|
|
+ VStack(alignment: .leading, spacing: Layout.columnSpacing) {
|
|
|
+ compareHeader
|
|
|
+
|
|
|
if viewModel.variants.isEmpty {
|
|
|
TitleOptimizerEmptyState(
|
|
|
icon: "arrow.left.arrow.right",
|
|
|
title: "Nothing to compare yet",
|
|
|
subtitle: "Generate title suggestions on the Optimize tab first."
|
|
|
)
|
|
|
+ .frame(maxWidth: .infinity)
|
|
|
} else {
|
|
|
- compareHeader
|
|
|
-
|
|
|
- TitleCompareCard(
|
|
|
- title: viewModel.draft.originalTitle,
|
|
|
- score: viewModel.analysis?.overallScore,
|
|
|
- label: "Original",
|
|
|
- isOriginal: true,
|
|
|
- isSelected: viewModel.selectedVariantID == nil
|
|
|
- )
|
|
|
+ compareBody
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- HStack {
|
|
|
- Rectangle()
|
|
|
- .fill(AppTheme.border)
|
|
|
- .frame(height: 1)
|
|
|
- Text("vs")
|
|
|
- .font(.system(size: 10, weight: .semibold))
|
|
|
- .foregroundStyle(AppTheme.textTertiary)
|
|
|
- .padding(.horizontal, 8)
|
|
|
- Rectangle()
|
|
|
- .fill(AppTheme.border)
|
|
|
- .frame(height: 1)
|
|
|
- }
|
|
|
+ private var compareBody: some View {
|
|
|
+ VStack(spacing: Layout.sectionSpacing) {
|
|
|
+ TitleCompareCard(
|
|
|
+ title: viewModel.draft.originalTitle,
|
|
|
+ score: viewModel.analysis?.overallScore,
|
|
|
+ label: "Original",
|
|
|
+ isOriginal: true,
|
|
|
+ isSelected: viewModel.selectedVariantID == nil
|
|
|
+ )
|
|
|
|
|
|
- VStack(spacing: 8) {
|
|
|
- ForEach(viewModel.variants) { variant in
|
|
|
- TitleCompareCard(
|
|
|
- title: variant.title,
|
|
|
- score: variant.score,
|
|
|
- label: "Variant #\(variantRank(variant))",
|
|
|
- isOriginal: false,
|
|
|
- isSelected: viewModel.selectedVariantID == variant.id
|
|
|
- )
|
|
|
- .onTapGesture {
|
|
|
- viewModel.selectVariant(variant)
|
|
|
- }
|
|
|
+ HStack {
|
|
|
+ Rectangle()
|
|
|
+ .fill(AppTheme.border)
|
|
|
+ .frame(height: 1)
|
|
|
+ Text("vs")
|
|
|
+ .font(.system(size: 10, weight: .semibold))
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
+ .padding(.horizontal, 8)
|
|
|
+ Rectangle()
|
|
|
+ .fill(AppTheme.border)
|
|
|
+ .frame(height: 1)
|
|
|
+ }
|
|
|
+
|
|
|
+ VStack(spacing: 8) {
|
|
|
+ ForEach(viewModel.variants) { variant in
|
|
|
+ TitleCompareCard(
|
|
|
+ title: variant.title,
|
|
|
+ score: variant.score,
|
|
|
+ label: "Variant #\(variantRank(variant))",
|
|
|
+ isOriginal: false,
|
|
|
+ isSelected: viewModel.selectedVariantID == variant.id
|
|
|
+ )
|
|
|
+ .onTapGesture {
|
|
|
+ viewModel.selectVariant(variant)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- compareMetadata
|
|
|
}
|
|
|
+
|
|
|
+ compareMetadata
|
|
|
}
|
|
|
.frame(maxWidth: 640)
|
|
|
.frame(maxWidth: .infinity)
|
|
|
}
|
|
|
|
|
|
private var compareHeader: some View {
|
|
|
- HStack {
|
|
|
- VStack(alignment: .leading, spacing: 2) {
|
|
|
- Text("Side-by-Side Comparison")
|
|
|
- .font(.system(size: 12, weight: .semibold))
|
|
|
- .foregroundStyle(AppTheme.textPrimary)
|
|
|
- Text("Tap a variant to select it as active")
|
|
|
- .font(.system(size: 10))
|
|
|
- .foregroundStyle(AppTheme.textSecondary)
|
|
|
- }
|
|
|
+ HStack(alignment: .top) {
|
|
|
+ sectionHeader(title: "Side-by-Side Comparison", icon: "arrow.left.arrow.right")
|
|
|
|
|
|
Spacer()
|
|
|
|
|
|
@@ -368,15 +453,19 @@ struct TitleOptimizerView: View {
|
|
|
.font(.system(size: 10, weight: .semibold))
|
|
|
.foregroundStyle(AppTheme.textSecondary)
|
|
|
}
|
|
|
+ .padding(.top, 1)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private var compareMetadata: some View {
|
|
|
- VStack(alignment: .leading, spacing: 8) {
|
|
|
+ VStack(alignment: .leading, spacing: 0) {
|
|
|
metadataRow(label: "Subreddit", value: viewModel.formattedSubreddit)
|
|
|
+ metadataDivider
|
|
|
metadataRow(label: "Goal", value: viewModel.draft.titleGoal.title)
|
|
|
+ metadataDivider
|
|
|
metadataRow(label: "Tone", value: viewModel.draft.tone.title)
|
|
|
+ metadataDivider
|
|
|
metadataRow(label: "Active", value: viewModel.activeTitle)
|
|
|
}
|
|
|
.padding(14)
|
|
|
@@ -398,54 +487,64 @@ struct TitleOptimizerView: View {
|
|
|
// MARK: - Preview
|
|
|
|
|
|
private var previewContent: some View {
|
|
|
- VStack(spacing: 16) {
|
|
|
- Text("Live Preview")
|
|
|
- .font(.system(size: 12, weight: .semibold))
|
|
|
- .foregroundStyle(AppTheme.textSecondary)
|
|
|
- .frame(maxWidth: .infinity, alignment: .leading)
|
|
|
+ VStack(alignment: .leading, spacing: Layout.columnSpacing) {
|
|
|
+ sectionHeader(title: "Live Preview", icon: "eye")
|
|
|
|
|
|
- RedditPostPreviewCard(
|
|
|
- draft: viewModel.previewDraft,
|
|
|
- formattedSubreddit: viewModel.formattedSubreddit
|
|
|
- )
|
|
|
+ HStack {
|
|
|
+ Spacer(minLength: 0)
|
|
|
|
|
|
- PostFormCard(title: "Feed Preview", subtitle: "How your title looks in a crowded feed") {
|
|
|
- VStack(spacing: 6) {
|
|
|
- TitleFeedPreviewRow(
|
|
|
- title: "Other trending post in this subreddit right now",
|
|
|
- subreddit: viewModel.formattedSubreddit
|
|
|
- )
|
|
|
- TitleFeedPreviewRow(
|
|
|
- title: viewModel.activeTitle,
|
|
|
- subreddit: viewModel.formattedSubreddit,
|
|
|
- isHighlighted: true
|
|
|
- )
|
|
|
- TitleFeedPreviewRow(
|
|
|
- title: "Another popular discussion happening nearby",
|
|
|
- subreddit: viewModel.formattedSubreddit
|
|
|
+ VStack(spacing: Layout.sectionSpacing) {
|
|
|
+ RedditPostPreviewCard(
|
|
|
+ draft: viewModel.previewDraft,
|
|
|
+ formattedSubreddit: viewModel.formattedSubreddit
|
|
|
)
|
|
|
+
|
|
|
+ PostFormCard(title: "Feed Preview", subtitle: "How your title looks in a crowded feed") {
|
|
|
+ VStack(spacing: 6) {
|
|
|
+ TitleFeedPreviewRow(
|
|
|
+ title: "Other trending post in this subreddit right now",
|
|
|
+ subreddit: viewModel.formattedSubreddit
|
|
|
+ )
|
|
|
+ TitleFeedPreviewRow(
|
|
|
+ title: viewModel.activeTitle,
|
|
|
+ subreddit: viewModel.formattedSubreddit,
|
|
|
+ isHighlighted: true
|
|
|
+ )
|
|
|
+ TitleFeedPreviewRow(
|
|
|
+ title: "Another popular discussion happening nearby",
|
|
|
+ subreddit: viewModel.formattedSubreddit
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ previewMetadata
|
|
|
}
|
|
|
- }
|
|
|
+ .frame(maxWidth: 560)
|
|
|
|
|
|
- previewMetadata
|
|
|
+ Spacer(minLength: 0)
|
|
|
+ }
|
|
|
}
|
|
|
- .frame(maxWidth: 560)
|
|
|
- .frame(maxWidth: .infinity)
|
|
|
}
|
|
|
|
|
|
private var previewMetadata: some View {
|
|
|
- VStack(alignment: .leading, spacing: 8) {
|
|
|
+ VStack(alignment: .leading, spacing: 0) {
|
|
|
metadataRow(label: "Active Title", value: viewModel.activeTitle)
|
|
|
+ metadataDivider
|
|
|
metadataRow(label: "Subreddit", value: viewModel.formattedSubreddit)
|
|
|
+ metadataDivider
|
|
|
metadataRow(label: "Goal", value: viewModel.draft.titleGoal.title)
|
|
|
+ metadataDivider
|
|
|
metadataRow(label: "Tone", value: viewModel.draft.tone.title)
|
|
|
+ metadataDivider
|
|
|
metadataRow(label: "Type", value: viewModel.draft.postType.title)
|
|
|
|
|
|
if let score = viewModel.analysis?.overallScore {
|
|
|
+ metadataDivider
|
|
|
metadataRow(label: "Score", value: "\(score)/100")
|
|
|
}
|
|
|
|
|
|
if viewModel.draft.isNSFW || viewModel.draft.isSpoiler || viewModel.draft.isOC {
|
|
|
+ metadataDivider
|
|
|
metadataRow(
|
|
|
label: "Tags",
|
|
|
value: [
|
|
|
@@ -470,12 +569,19 @@ struct TitleOptimizerView: View {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+ private var metadataDivider: some View {
|
|
|
+ Rectangle()
|
|
|
+ .fill(AppTheme.border)
|
|
|
+ .frame(height: 1)
|
|
|
+ .padding(.vertical, 6)
|
|
|
+ }
|
|
|
+
|
|
|
private func metadataRow(label: String, value: String) -> some View {
|
|
|
- HStack(alignment: .top) {
|
|
|
+ HStack(alignment: .top, spacing: 12) {
|
|
|
Text(label)
|
|
|
.font(.system(size: 10, weight: .medium))
|
|
|
.foregroundStyle(AppTheme.textTertiary)
|
|
|
- .frame(width: 80, alignment: .leading)
|
|
|
+ .frame(width: 72, alignment: .leading)
|
|
|
Text(value)
|
|
|
.font(.system(size: 10))
|
|
|
.foregroundStyle(AppTheme.textSecondary)
|