iOS: drop demo HomeFeedView (replaced by ContactsView)
This commit is contained in:
parent
b5b36f93cd
commit
42311bcb74
@ -1,222 +0,0 @@
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
struct FeedPost: Identifiable, Hashable {
|
||||
let id = UUID()
|
||||
let author: String
|
||||
let handle: String
|
||||
let timeAgo: String
|
||||
let body: String
|
||||
let replies: Int
|
||||
let reposts: Int
|
||||
let likes: Int
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class FeedStore: ObservableObject {
|
||||
static let shared = FeedStore()
|
||||
@Published var posts: [FeedPost] = [
|
||||
FeedPost(author: "Алик Монтана", handle: "@alik", timeAgo: "2м",
|
||||
body: "Запустили генезис в трёх городах: мос, фра, зел. Время идёт честно.",
|
||||
replies: 12, reposts: 4, likes: 87),
|
||||
FeedPost(author: "Юнона", handle: "@junona", timeAgo: "18м",
|
||||
body: "Никто не читает твои сообщения. Никто не знает, кто ты. Это нормально.",
|
||||
replies: 3, reposts: 22, likes: 154),
|
||||
FeedPost(author: "Frankfurt Node", handle: "@fra", timeAgo: "1ч",
|
||||
body: "Окно 2891844 закрыто. VDF подтверждён. Якорь установлен.",
|
||||
replies: 0, reposts: 1, likes: 9),
|
||||
FeedPost(author: "Helsinki Node", handle: "@zel", timeAgo: "3ч",
|
||||
body: "Reality маска работает. Трафик неотличим от обычного TLS.",
|
||||
replies: 5, reposts: 11, likes: 42),
|
||||
FeedPost(author: "Moscow Node", handle: "@mos", timeAgo: "5ч",
|
||||
body: "efir.org перешёл на :8443 — :443 теперь у Xray. Всё стабильно.",
|
||||
replies: 2, reposts: 3, likes: 18),
|
||||
]
|
||||
|
||||
func publish(_ text: String) {
|
||||
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty else { return }
|
||||
posts.insert(FeedPost(author: "Алик Монтана", handle: "@alik", timeAgo: "сейчас",
|
||||
body: trimmed, replies: 0, reposts: 0, likes: 0), at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
struct HomeFeedView: View {
|
||||
@StateObject private var store = FeedStore.shared
|
||||
@State private var composing = false
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 0) {
|
||||
FeedHeader().padding(.top, ClaudeTheme.Spacing.sm)
|
||||
ForEach(store.posts) { post in
|
||||
PostRow(post: post)
|
||||
Rectangle()
|
||||
.fill(ClaudeTheme.Palette.divider)
|
||||
.frame(height: 0.5)
|
||||
.padding(.leading, 76)
|
||||
}
|
||||
}
|
||||
}
|
||||
.claudeBackground()
|
||||
|
||||
Button { composing = true } label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
.font(.system(size: 22, weight: .medium))
|
||||
.foregroundStyle(ClaudeTheme.Palette.bubbleOutText)
|
||||
.frame(width: 56, height: 56)
|
||||
.background(Circle().fill(ClaudeTheme.Palette.goldGradient))
|
||||
.overlay(Circle().strokeBorder(ClaudeTheme.Palette.goldBright.opacity(0.5), lineWidth: 0.5))
|
||||
.shadow(color: ClaudeTheme.Palette.gold.opacity(0.45), radius: 16, y: 4)
|
||||
}
|
||||
.padding(.trailing, ClaudeTheme.Spacing.lg)
|
||||
.padding(.bottom, ClaudeTheme.Spacing.lg)
|
||||
}
|
||||
.navigationTitle("Эфир")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.sheet(isPresented: $composing) {
|
||||
ComposePostView { text in
|
||||
store.publish(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct FeedHeader: View {
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
Text("Эфир")
|
||||
.font(.system(size: 32, weight: .bold, design: .serif))
|
||||
.foregroundStyle(ClaudeTheme.Palette.goldGradient)
|
||||
Spacer()
|
||||
Image(systemName: "sparkles")
|
||||
.font(.system(size: 18))
|
||||
.foregroundStyle(ClaudeTheme.Palette.gold.opacity(0.6))
|
||||
}
|
||||
.padding(.horizontal, ClaudeTheme.Spacing.lg)
|
||||
.padding(.bottom, 4)
|
||||
}
|
||||
}
|
||||
|
||||
private struct PostRow: View {
|
||||
let post: FeedPost
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top, spacing: ClaudeTheme.Spacing.md) {
|
||||
AvatarView(name: post.author, size: 44)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
HStack(spacing: 6) {
|
||||
Text(post.author)
|
||||
.font(ClaudeTheme.Typography.headline)
|
||||
.foregroundStyle(ClaudeTheme.Palette.textPrimary)
|
||||
Text(post.handle)
|
||||
.font(ClaudeTheme.Typography.caption)
|
||||
.foregroundStyle(ClaudeTheme.Palette.textTertiary)
|
||||
Text("·").foregroundStyle(ClaudeTheme.Palette.textTertiary)
|
||||
Text(post.timeAgo)
|
||||
.font(ClaudeTheme.Typography.caption)
|
||||
.foregroundStyle(ClaudeTheme.Palette.textTertiary)
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Text(post.body)
|
||||
.font(ClaudeTheme.Typography.body)
|
||||
.foregroundStyle(ClaudeTheme.Palette.textPrimary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
HStack(spacing: ClaudeTheme.Spacing.xl) {
|
||||
PostAction(icon: "bubble.left", count: post.replies)
|
||||
PostAction(icon: "arrow.2.squarepath", count: post.reposts)
|
||||
PostAction(icon: "heart", count: post.likes)
|
||||
Spacer()
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
.font(.system(size: 14))
|
||||
.foregroundStyle(ClaudeTheme.Palette.textTertiary)
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, ClaudeTheme.Spacing.lg)
|
||||
.padding(.vertical, ClaudeTheme.Spacing.md)
|
||||
}
|
||||
}
|
||||
|
||||
private struct PostAction: View {
|
||||
let icon: String
|
||||
let count: Int
|
||||
var body: some View {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: icon).font(.system(size: 14))
|
||||
if count > 0 { Text("\(count)").font(ClaudeTheme.Typography.caption) }
|
||||
}
|
||||
.foregroundStyle(ClaudeTheme.Palette.textTertiary)
|
||||
}
|
||||
}
|
||||
|
||||
private struct ComposePostView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
let onPublish: (String) -> Void
|
||||
@State private var text: String = ""
|
||||
@FocusState private var focused: Bool
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
AvatarView(name: "Алик Монтана", size: 44)
|
||||
TextEditor(text: $text)
|
||||
.font(ClaudeTheme.Typography.body)
|
||||
.foregroundStyle(ClaudeTheme.Palette.textPrimary)
|
||||
.scrollContentBackground(.hidden)
|
||||
.focused($focused)
|
||||
.overlay(alignment: .topLeading) {
|
||||
if text.isEmpty {
|
||||
Text("Что происходит в эфире?")
|
||||
.font(ClaudeTheme.Typography.body)
|
||||
.foregroundStyle(ClaudeTheme.Palette.textTertiary)
|
||||
.padding(.top, 8)
|
||||
.padding(.leading, 4)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(ClaudeTheme.Spacing.lg)
|
||||
Spacer()
|
||||
}
|
||||
.claudeBackground()
|
||||
.navigationTitle("Новый пост")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
Button("Отмена") { dismiss() }
|
||||
.foregroundStyle(ClaudeTheme.Palette.textSecondary)
|
||||
}
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
onPublish(text)
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("Опубликовать")
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.foregroundStyle(text.isEmpty
|
||||
? ClaudeTheme.Palette.textTertiary
|
||||
: ClaudeTheme.Palette.bubbleOutText)
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 6)
|
||||
.background(
|
||||
Capsule().fill(text.isEmpty
|
||||
? AnyShapeStyle(ClaudeTheme.Palette.surface)
|
||||
: AnyShapeStyle(ClaudeTheme.Palette.goldGradient))
|
||||
)
|
||||
}
|
||||
.disabled(text.isEmpty)
|
||||
}
|
||||
}
|
||||
.onAppear { focused = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user