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