|
@@ -0,0 +1,633 @@
|
|
|
|
|
+import SwiftUI
|
|
|
|
|
+
|
|
|
|
|
+// MARK: - Pickers
|
|
|
|
|
+
|
|
|
|
|
+struct CommentTypePicker: View {
|
|
|
|
|
+ @Binding var selectedType: CommentType
|
|
|
|
|
+
|
|
|
|
|
+ var body: some View {
|
|
|
|
|
+ HStack(spacing: 8) {
|
|
|
|
|
+ ForEach(CommentType.allCases) { type in
|
|
|
|
|
+ Button {
|
|
|
|
|
+ selectedType = type
|
|
|
|
|
+ } label: {
|
|
|
|
|
+ HStack(spacing: 8) {
|
|
|
|
|
+ Image(systemName: type.systemImage)
|
|
|
|
|
+ .font(.system(size: 12, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(
|
|
|
|
|
+ selectedType == type ? AppTheme.accentGreen : AppTheme.textSecondary
|
|
|
|
|
+ )
|
|
|
|
|
+ .frame(width: 16)
|
|
|
|
|
+
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 1) {
|
|
|
|
|
+ Text(type.title)
|
|
|
|
|
+ .font(.system(size: 11, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textPrimary)
|
|
|
|
|
+ Text(type.subtitle)
|
|
|
|
|
+ .font(.system(size: 9))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
|
|
+ .lineLimit(1)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Spacer(minLength: 0)
|
|
|
|
|
+ }
|
|
|
|
|
+ .padding(.horizontal, 10)
|
|
|
|
|
+ .padding(.vertical, 8)
|
|
|
|
|
+ .background(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: 8)
|
|
|
|
|
+ .fill(
|
|
|
|
|
+ selectedType == type
|
|
|
|
|
+ ? AppTheme.accentGreen.opacity(0.15)
|
|
|
|
|
+ : AppTheme.panelBackground
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: 8)
|
|
|
|
|
+ .stroke(
|
|
|
|
|
+ selectedType == type
|
|
|
|
|
+ ? AppTheme.accentGreen.opacity(0.5)
|
|
|
|
|
+ : AppTheme.cardBorder,
|
|
|
|
|
+ lineWidth: 1
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ .buttonStyle(.plain)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+struct CommentIntentPicker: View {
|
|
|
|
|
+ @Binding var selectedIntent: CommentIntent
|
|
|
|
|
+
|
|
|
|
|
+ var body: some View {
|
|
|
|
|
+ LazyVGrid(
|
|
|
|
|
+ columns: [GridItem(.flexible()), GridItem(.flexible())],
|
|
|
|
|
+ spacing: 8
|
|
|
|
|
+ ) {
|
|
|
|
|
+ ForEach(CommentIntent.allCases) { intent in
|
|
|
|
|
+ Button {
|
|
|
|
|
+ selectedIntent = intent
|
|
|
|
|
+ } label: {
|
|
|
|
|
+ HStack(spacing: 8) {
|
|
|
|
|
+ Image(systemName: intent.systemImage)
|
|
|
|
|
+ .font(.system(size: 11, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(
|
|
|
|
|
+ selectedIntent == intent ? AppTheme.accentGreen : AppTheme.textSecondary
|
|
|
|
|
+ )
|
|
|
|
|
+ .frame(width: 14)
|
|
|
|
|
+
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 1) {
|
|
|
|
|
+ Text(intent.title)
|
|
|
|
|
+ .font(.system(size: 11, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textPrimary)
|
|
|
|
|
+ Text(intent.subtitle)
|
|
|
|
|
+ .font(.system(size: 9))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
|
|
+ .lineLimit(1)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Spacer(minLength: 0)
|
|
|
|
|
+ }
|
|
|
|
|
+ .padding(.horizontal, 10)
|
|
|
|
|
+ .padding(.vertical, 8)
|
|
|
|
|
+ .background(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: 8)
|
|
|
|
|
+ .fill(
|
|
|
|
|
+ selectedIntent == intent
|
|
|
|
|
+ ? AppTheme.accentGreen.opacity(0.15)
|
|
|
|
|
+ : AppTheme.panelBackground
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: 8)
|
|
|
|
|
+ .stroke(
|
|
|
|
|
+ selectedIntent == intent
|
|
|
|
|
+ ? AppTheme.accentGreen.opacity(0.5)
|
|
|
|
|
+ : AppTheme.cardBorder,
|
|
|
|
|
+ lineWidth: 1
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ .buttonStyle(.plain)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+struct CommentLengthPicker: View {
|
|
|
|
|
+ @Binding var selectedLength: CommentLength
|
|
|
|
|
+
|
|
|
|
|
+ var body: some View {
|
|
|
|
|
+ HStack(spacing: 6) {
|
|
|
|
|
+ ForEach(CommentLength.allCases) { length in
|
|
|
|
|
+ Button {
|
|
|
|
|
+ selectedLength = length
|
|
|
|
|
+ } label: {
|
|
|
|
|
+ VStack(spacing: 2) {
|
|
|
|
|
+ Text(length.title)
|
|
|
|
|
+ .font(.system(size: 10, weight: .semibold))
|
|
|
|
|
+ Text(length.wordRange)
|
|
|
|
|
+ .font(.system(size: 8))
|
|
|
|
|
+ .foregroundStyle(
|
|
|
|
|
+ selectedLength == length ? .white.opacity(0.8) : AppTheme.textTertiary
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ .foregroundStyle(selectedLength == length ? .white : AppTheme.textSecondary)
|
|
|
|
|
+ .padding(.horizontal, 12)
|
|
|
|
|
+ .padding(.vertical, 8)
|
|
|
|
|
+ .frame(maxWidth: .infinity)
|
|
|
|
|
+ .background(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: 8)
|
|
|
|
|
+ .fill(
|
|
|
|
|
+ selectedLength == length
|
|
|
|
|
+ ? AppTheme.accentGreen
|
|
|
|
|
+ : AppTheme.panelBackground
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: 8)
|
|
|
|
|
+ .stroke(AppTheme.cardBorder, lineWidth: 1)
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ .buttonStyle(.plain)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+struct CommentTonePicker: View {
|
|
|
|
|
+ @Binding var selectedTone: PostTone
|
|
|
|
|
+
|
|
|
|
|
+ var body: some View {
|
|
|
|
|
+ ScrollView(.horizontal, showsIndicators: false) {
|
|
|
|
|
+ HStack(spacing: 6) {
|
|
|
|
|
+ ForEach(PostTone.allCases) { tone in
|
|
|
|
|
+ Button {
|
|
|
|
|
+ selectedTone = tone
|
|
|
|
|
+ } label: {
|
|
|
|
|
+ Text(tone.title)
|
|
|
|
|
+ .font(.system(size: 10, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(selectedTone == tone ? .white : AppTheme.textSecondary)
|
|
|
|
|
+ .padding(.horizontal, 10)
|
|
|
|
|
+ .padding(.vertical, 6)
|
|
|
|
|
+ .background(
|
|
|
|
|
+ Capsule()
|
|
|
|
|
+ .fill(
|
|
|
|
|
+ selectedTone == tone
|
|
|
|
|
+ ? AppTheme.accentGreen
|
|
|
|
|
+ : AppTheme.panelBackground
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ Capsule()
|
|
|
|
|
+ .stroke(
|
|
|
|
|
+ selectedTone == tone
|
|
|
|
|
+ ? AppTheme.accentGreen.opacity(0.5)
|
|
|
|
|
+ : AppTheme.cardBorder,
|
|
|
|
|
+ lineWidth: 1
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ .buttonStyle(.plain)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+struct CommentVariantCountPicker: View {
|
|
|
|
|
+ @Binding var selectedCount: CommentVariantCount
|
|
|
|
|
+
|
|
|
|
|
+ var body: some View {
|
|
|
|
|
+ HStack(spacing: 6) {
|
|
|
|
|
+ ForEach(CommentVariantCount.allCases) { count in
|
|
|
|
|
+ Button {
|
|
|
|
|
+ selectedCount = count
|
|
|
|
|
+ } label: {
|
|
|
|
|
+ Text(count.label)
|
|
|
|
|
+ .font(.system(size: 10, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(selectedCount == count ? .white : AppTheme.textSecondary)
|
|
|
|
|
+ .padding(.horizontal, 12)
|
|
|
|
|
+ .padding(.vertical, 6)
|
|
|
|
|
+ .background(
|
|
|
|
|
+ Capsule()
|
|
|
|
|
+ .fill(
|
|
|
|
|
+ selectedCount == count
|
|
|
|
|
+ ? AppTheme.accentGreen
|
|
|
|
|
+ : AppTheme.panelBackground
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ Capsule()
|
|
|
|
|
+ .stroke(AppTheme.cardBorder, lineWidth: 1)
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ .buttonStyle(.plain)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+struct CommentToggleRow: View {
|
|
|
|
|
+ let title: String
|
|
|
|
|
+ let subtitle: String
|
|
|
|
|
+ @Binding var isOn: Bool
|
|
|
|
|
+
|
|
|
|
|
+ var body: some View {
|
|
|
|
|
+ Toggle(isOn: $isOn) {
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 2) {
|
|
|
|
|
+ Text(title)
|
|
|
|
|
+ .font(.system(size: 11, weight: .medium))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textPrimary)
|
|
|
|
|
+ Text(subtitle)
|
|
|
|
|
+ .font(.system(size: 9))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .toggleStyle(.switch)
|
|
|
|
|
+ .tint(AppTheme.accentGreen)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// MARK: - Reddit Etiquette Card
|
|
|
|
|
+
|
|
|
|
|
+struct RedditCommentEtiquetteCard: View {
|
|
|
|
|
+ var body: some View {
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 8) {
|
|
|
|
|
+ HStack(spacing: 6) {
|
|
|
|
|
+ Image(systemName: "info.circle.fill")
|
|
|
|
|
+ .font(.system(size: 11))
|
|
|
|
|
+ .foregroundStyle(AppTheme.accentGreen)
|
|
|
|
|
+ Text("Reddit Comment Tips")
|
|
|
|
|
+ .font(.system(size: 11, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textPrimary)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 4) {
|
|
|
|
|
+ etiquetteRow("10,000 character limit per comment")
|
|
|
|
|
+ etiquetteRow("Markdown: **bold**, *italic*, > quotes, `code`")
|
|
|
|
|
+ etiquetteRow("Stay on-topic for the subreddit")
|
|
|
|
|
+ etiquetteRow("Be civil — disagree with ideas, not people")
|
|
|
|
|
+ etiquetteRow("Edit with \"Edit:\" to clarify, don't bait-and-switch")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .padding(12)
|
|
|
|
|
+ .frame(maxWidth: .infinity, alignment: .leading)
|
|
|
|
|
+ .background(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall)
|
|
|
|
|
+ .fill(AppTheme.accentGreen.opacity(0.06))
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall)
|
|
|
|
|
+ .stroke(AppTheme.accentGreen.opacity(0.2), lineWidth: 1)
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func etiquetteRow(_ text: String) -> some View {
|
|
|
|
|
+ HStack(alignment: .top, spacing: 6) {
|
|
|
|
|
+ Text("•")
|
|
|
|
|
+ .font(.system(size: 10))
|
|
|
|
|
+ .foregroundStyle(AppTheme.accentGreen)
|
|
|
|
|
+ Text(text)
|
|
|
|
|
+ .font(.system(size: 10))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
|
|
+ .fixedSize(horizontal: false, vertical: true)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// MARK: - Thread Context Card
|
|
|
|
|
+
|
|
|
|
|
+struct RedditThreadContextCard: View {
|
|
|
|
|
+ let postTitle: String
|
|
|
|
|
+ let postBody: String
|
|
|
|
|
+ let parentComment: String
|
|
|
|
|
+ let commentType: CommentType
|
|
|
|
|
+ let formattedSubreddit: String
|
|
|
|
|
+
|
|
|
|
|
+ var body: some View {
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 10) {
|
|
|
|
|
+ HStack(spacing: 6) {
|
|
|
|
|
+ Image(systemName: "doc.text")
|
|
|
|
|
+ .font(.system(size: 11))
|
|
|
|
|
+ .foregroundStyle(AppTheme.accentOrange)
|
|
|
|
|
+ Text("Thread Context")
|
|
|
|
|
+ .font(.system(size: 11, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textPrimary)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 8) {
|
|
|
|
|
+ contextBlock(
|
|
|
|
|
+ label: formattedSubreddit,
|
|
|
|
|
+ content: postTitle.isEmpty ? "Post title" : postTitle,
|
|
|
|
|
+ isTitle: true
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if !postBody.isEmpty {
|
|
|
|
|
+ contextBlock(label: "Post body", content: postBody, isTitle: false)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if commentType == .reply, !parentComment.isEmpty {
|
|
|
|
|
+ contextBlock(label: "Replying to", content: parentComment, isTitle: false, isQuoted: true)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .padding(12)
|
|
|
|
|
+ .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 contextBlock(
|
|
|
|
|
+ label: String,
|
|
|
|
|
+ content: String,
|
|
|
|
|
+ isTitle: Bool,
|
|
|
|
|
+ isQuoted: Bool = false
|
|
|
|
|
+ ) -> some View {
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 4) {
|
|
|
|
|
+ Text(label)
|
|
|
|
|
+ .font(.system(size: 9, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
|
|
+
|
|
|
|
|
+ if isQuoted {
|
|
|
|
|
+ HStack(spacing: 0) {
|
|
|
|
|
+ Rectangle()
|
|
|
|
|
+ .fill(AppTheme.accentGreen.opacity(0.5))
|
|
|
|
|
+ .frame(width: 3)
|
|
|
|
|
+ Text(content)
|
|
|
|
|
+ .font(.system(size: 10))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
|
|
+ .lineLimit(3)
|
|
|
|
|
+ .padding(.leading, 8)
|
|
|
|
|
+ }
|
|
|
|
|
+ .padding(.vertical, 4)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Text(content)
|
|
|
|
|
+ .font(.system(size: isTitle ? 11 : 10, weight: isTitle ? .semibold : .regular))
|
|
|
|
|
+ .foregroundStyle(isTitle ? AppTheme.textPrimary : AppTheme.textSecondary)
|
|
|
|
|
+ .lineLimit(isTitle ? 2 : 3)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// MARK: - Comment Preview
|
|
|
|
|
+
|
|
|
|
|
+struct RedditCommentPreviewCard: View {
|
|
|
|
|
+ let commentBody: String
|
|
|
|
|
+ let formattedSubreddit: String
|
|
|
|
|
+ let commentType: CommentType
|
|
|
|
|
+ let parentComment: String
|
|
|
|
|
+ var isReply: Bool = false
|
|
|
|
|
+
|
|
|
|
|
+ var body: some View {
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 0) {
|
|
|
|
|
+ if commentType == .reply, !parentComment.isEmpty {
|
|
|
|
|
+ parentCommentRow
|
|
|
|
|
+ .padding(.leading, 14)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ commentRow
|
|
|
|
|
+ .padding(.leading, commentType == .reply ? 28 : 0)
|
|
|
|
|
+ }
|
|
|
|
|
+ .background(AppTheme.panelBackground)
|
|
|
|
|
+ .clipShape(RoundedRectangle(cornerRadius: AppTheme.cornerRadius))
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: AppTheme.cornerRadius)
|
|
|
|
|
+ .stroke(AppTheme.cardBorder, lineWidth: 1)
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private var parentCommentRow: some View {
|
|
|
|
|
+ HStack(alignment: .top, spacing: 8) {
|
|
|
|
|
+ voteColumn
|
|
|
|
|
+
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 6) {
|
|
|
|
|
+ commentHeader(username: "u/OriginalPoster")
|
|
|
|
|
+
|
|
|
|
|
+ Text(parentComment)
|
|
|
|
|
+ .font(.system(size: 11))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
|
|
+ .lineLimit(3)
|
|
|
|
|
+ .fixedSize(horizontal: false, vertical: true)
|
|
|
|
|
+
|
|
|
|
|
+ commentActions
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .padding(.horizontal, 14)
|
|
|
|
|
+ .padding(.vertical, 12)
|
|
|
|
|
+ .opacity(0.7)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private var commentRow: some View {
|
|
|
|
|
+ HStack(alignment: .top, spacing: 8) {
|
|
|
|
|
+ voteColumn
|
|
|
|
|
+
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 6) {
|
|
|
|
|
+ commentHeader(username: "u/ReddoraUser")
|
|
|
|
|
+
|
|
|
|
|
+ if commentBody.isEmpty {
|
|
|
|
|
+ Text("Your comment will appear here…")
|
|
|
|
|
+ .font(.system(size: 12))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
|
|
+ .italic()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ PostMarkdownText(text: commentBody, fontSize: 12, color: AppTheme.textPrimary)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ commentActions
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .padding(.horizontal, 14)
|
|
|
|
|
+ .padding(.vertical, 12)
|
|
|
|
|
+ .background(
|
|
|
|
|
+ commentType == .reply
|
|
|
|
|
+ ? AppTheme.accentGreen.opacity(0.04)
|
|
|
|
|
+ : Color.clear
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private var voteColumn: some View {
|
|
|
|
|
+ VStack(spacing: 2) {
|
|
|
|
|
+ Image(systemName: "arrow.up")
|
|
|
|
|
+ .font(.system(size: 12, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
|
|
+ Text("1")
|
|
|
|
|
+ .font(.system(size: 10, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
|
|
+ Image(systemName: "arrow.down")
|
|
|
|
|
+ .font(.system(size: 12, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
|
|
+ }
|
|
|
|
|
+ .frame(width: 24)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func commentHeader(username: String) -> some View {
|
|
|
|
|
+ HStack(spacing: 4) {
|
|
|
|
|
+ Text(username)
|
|
|
|
|
+ .font(.system(size: 10, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textPrimary)
|
|
|
|
|
+ Text("·")
|
|
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
|
|
+ Text("just now")
|
|
|
|
|
+ .font(.system(size: 9))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
|
|
+ if commentType == .reply, username == "u/ReddoraUser" {
|
|
|
|
|
+ Text("·")
|
|
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
|
|
+ Text(formattedSubreddit)
|
|
|
|
|
+ .font(.system(size: 9, weight: .medium))
|
|
|
|
|
+ .foregroundStyle(AppTheme.accentGreen)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private var commentActions: some View {
|
|
|
|
|
+ HStack(spacing: 12) {
|
|
|
|
|
+ actionButton("Reply", icon: "arrowshape.turn.up.left")
|
|
|
|
|
+ actionButton("Share", icon: "square.and.arrow.up")
|
|
|
|
|
+ actionButton("Award", icon: "seal")
|
|
|
|
|
+ actionButton("⋯", icon: nil)
|
|
|
|
|
+ }
|
|
|
|
|
+ .padding(.top, 2)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func actionButton(_ title: String, icon: String?) -> some View {
|
|
|
|
|
+ HStack(spacing: 4) {
|
|
|
|
|
+ if let icon {
|
|
|
|
|
+ Image(systemName: icon)
|
|
|
|
|
+ .font(.system(size: 9, weight: .semibold))
|
|
|
|
|
+ }
|
|
|
|
|
+ Text(title)
|
|
|
|
|
+ .font(.system(size: 9, weight: .semibold))
|
|
|
|
|
+ }
|
|
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// MARK: - Variant Row
|
|
|
|
|
+
|
|
|
|
|
+struct CommentVariantRow: View {
|
|
|
|
|
+ let variant: CommentVariant
|
|
|
|
|
+ let isSelected: Bool
|
|
|
|
|
+ let onSelect: () -> Void
|
|
|
|
|
+ let onApply: () -> Void
|
|
|
|
|
+
|
|
|
|
|
+ var body: some View {
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 8) {
|
|
|
|
|
+ HStack {
|
|
|
|
|
+ CommentScoreBadge(score: variant.score)
|
|
|
|
|
+
|
|
|
|
|
+ Text(variant.reasoning)
|
|
|
|
|
+ .font(.system(size: 9))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
|
|
+ .lineLimit(1)
|
|
|
|
|
+
|
|
|
|
|
+ Spacer()
|
|
|
|
|
+
|
|
|
|
|
+ HStack(spacing: 6) {
|
|
|
|
|
+ Button("Select", action: onSelect)
|
|
|
|
|
+ .font(.system(size: 9, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(isSelected ? AppTheme.accentGreen : AppTheme.textSecondary)
|
|
|
|
|
+ .buttonStyle(.plain)
|
|
|
|
|
+
|
|
|
|
|
+ Button("Apply", action: onApply)
|
|
|
|
|
+ .font(.system(size: 9, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(.white)
|
|
|
|
|
+ .padding(.horizontal, 8)
|
|
|
|
|
+ .padding(.vertical, 4)
|
|
|
|
|
+ .background(AppTheme.accentGreen.opacity(0.8))
|
|
|
|
|
+ .clipShape(RoundedRectangle(cornerRadius: 5))
|
|
|
|
|
+ .buttonStyle(.plain)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Text(variant.body)
|
|
|
|
|
+ .font(.system(size: 11))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
|
|
+ .lineLimit(4)
|
|
|
|
|
+ .fixedSize(horizontal: false, vertical: true)
|
|
|
|
|
+ }
|
|
|
|
|
+ .padding(12)
|
|
|
|
|
+ .frame(maxWidth: .infinity, alignment: .leading)
|
|
|
|
|
+ .background(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: 8)
|
|
|
|
|
+ .fill(isSelected ? AppTheme.accentGreen.opacity(0.1) : AppTheme.panelBackground)
|
|
|
|
|
+ )
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: 8)
|
|
|
|
|
+ .stroke(
|
|
|
|
|
+ isSelected ? AppTheme.accentGreen.opacity(0.4) : AppTheme.cardBorder,
|
|
|
|
|
+ lineWidth: 1
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ .onTapGesture(perform: onSelect)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+struct CommentScoreBadge: View {
|
|
|
|
|
+ let score: Int
|
|
|
|
|
+
|
|
|
|
|
+ var body: some View {
|
|
|
|
|
+ Text("\(score)")
|
|
|
|
|
+ .font(.system(size: 10, weight: .bold))
|
|
|
|
|
+ .foregroundStyle(scoreColor)
|
|
|
|
|
+ .padding(.horizontal, 6)
|
|
|
|
|
+ .padding(.vertical, 3)
|
|
|
|
|
+ .background(scoreColor.opacity(0.15))
|
|
|
|
|
+ .clipShape(RoundedRectangle(cornerRadius: 4))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private var scoreColor: Color {
|
|
|
|
|
+ switch score {
|
|
|
|
|
+ case 80...: AppTheme.accentGreen
|
|
|
|
|
+ case 60..<80: AppTheme.accentYellow
|
|
|
|
|
+ default: AppTheme.textTertiary
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+struct CommentWriterEmptyState: 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.accentGreen.opacity(0.6))
|
|
|
|
|
+ Text(title)
|
|
|
|
|
+ .font(.system(size: 12, weight: .semibold))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textPrimary)
|
|
|
|
|
+ Text(subtitle)
|
|
|
|
|
+ .font(.system(size: 10))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
|
|
+ .multilineTextAlignment(.center)
|
|
|
|
|
+ }
|
|
|
|
|
+ .frame(maxWidth: .infinity)
|
|
|
|
|
+ .padding(.vertical, 32)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+struct CommentSecondaryButtonStyle: ButtonStyle {
|
|
|
|
|
+ func makeBody(configuration: Configuration) -> some View {
|
|
|
|
|
+ configuration.label
|
|
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
|
|
+ .padding(.horizontal, 12)
|
|
|
|
|
+ .padding(.vertical, 8)
|
|
|
|
|
+ .background(AppTheme.cardBackground.opacity(configuration.isPressed ? 0.6 : 1))
|
|
|
|
|
+ .clipShape(RoundedRectangle(cornerRadius: 8))
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: 8)
|
|
|
|
|
+ .stroke(AppTheme.cardBorder, lineWidth: 1)
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}
|