dimensionhub/dimensionhub/Views/Index/IndexMainView.swift
2025-04-13 17:16:52 +08:00

266 lines
9.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// IndexMainView.swift
// dimensionhub
//
// Created by on 2025/4/8.
//
import SwiftUI
import Combine
//
struct IndexMainView: View {
@Environment(\.modelContext) private var modelContext
@EnvironmentObject var appNavigation: AppNavigation
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
@State var indexModel = IndexModel()
//
@State var showPrompt: Bool = false
@State var promptMessage: String = ""
//
@State private var selectGroupId: String = ""
@State private var showDateNavPopover: Bool = false
//
@State private var headerRefreshing: Bool = false
@State private var footerRefreshing: Bool = false
@State private var noMore: Bool = false
var body: some View {
VStack(alignment: .center) {
HStack(alignment: .center) {
Text("亚次元")
.font(.system(size: 18, weight: .bold))
.padding([.top, .bottom], 5)
Spacer()
HStack {
Text("\(indexModel.follow_num)")
.font(.system(size: 17))
.foregroundColor(.black)
.padding([.top, .bottom], 5)
.padding(.leading, 10)
}
.contentShape(Rectangle())
.highPriorityGesture(
TapGesture().onEnded {
appNavigation.append(dest: .followList)
}
)
.zIndex(1)
}
.padding([.leading, .trailing], 15)
.frame(height: 50)
.background(Color(hex: "#F2F2F2"), ignoresSafeAreaEdges: .top)
ScrollView(.vertical, showsIndicators: false) {
Rectangle()
.frame(width: 0, height: 10)
//
LazyVStack(alignment: .center, spacing: 10) {
ForEach(indexModel.updateDramaGroups, id: \.group_id) { group in
DramaGroupView(group: group, model: indexModel) {
selectGroupId = group.group_id
indexModel.selectedDate = group.group_id
showDateNavPopover = true
}
.contentShape(Rectangle())
}
}
ProgressView()
.id(UUID())
.background(GeometryReader { geometry in
let minY = geometry.frame(in: .global).minY
Color.clear.preference(key: FooterOffsetPreferenceKey.self, value: Int(minY))
})
}
.frame(width: 370)
.coordinateSpace(name: "indexScrollView")
.onPreferenceChange(FooterOffsetPreferenceKey.self) { offset in
// state
DispatchQueue.main.async {
indexModel.loadMorePublisher.send((userId, offset))
}
}
.refreshable {
guard !self.showDateNavPopover && !self.headerRefreshing else {
return
}
//
self.headerRefreshing = true
Task {
await self.indexModel.loadMoreUpdateDramasTask(userId: self.userId, mode: .prev)
DispatchQueue.main.async {
self.headerRefreshing = false
}
}
}
.overlay(alignment: .topTrailing) {
HStack(alignment: .center) {
Button(action: {
appNavigation.append(dest: .search)
}) {
Image(systemName: "magnifyingglass")
.font(.system(size: 20))
}
Spacer()
if let fixedDramaGroup = indexModel.fixedDramaGroup {
Text(fixedDramaGroup.group_name)
.font(.system(size: 18))
.fontWeight(.regular)
.onTapGesture {
selectGroupId = fixedDramaGroup.group_id
indexModel.selectedDate = fixedDramaGroup.group_id
showDateNavPopover = true
}
}
}
.padding([.top, .bottom], 8)
.background(.white)
}
}
.ignoresSafeArea(edges: .bottom)
.popover(isPresented: $showDateNavPopover) {
DateNavView(selectGroupId: self.$selectGroupId, showDateNavPopover: $showDateNavPopover) { selectedDate in
Task {
await indexModel.loadDateUpdateDramas(userId: self.userId, date: selectedDate)
}
}
}
.alert(isPresented: $showPrompt) {
Alert(title: Text("提示"), message: Text(self.promptMessage), dismissButton: .default(Text("OK")))
}
.task {
await self.indexModel.loadData(userId: self.userId)
}
.onPreferenceChange(DramaGroupElementPreferenceKey.self) { frames in
let visibleFrames = frames.filter { $0.value >= 0}
if let minFrame = visibleFrames.min(by: { $0.value <= $1.value}) {
DispatchQueue.main.async {
indexModel.setFixedDrameGroup(groupId: minFrame.key)
}
}
}
}
}
extension IndexMainView {
//
struct DramaGroupView: View {
@EnvironmentObject var appNav: AppNavigation
let group: IndexModel.UpdateDramaGroup
let model: IndexModel
var onTap: () -> Void
var body: some View {
VStack(alignment: .center, spacing: 10) {
HStack {
Spacer()
Text(group.group_name)
.font(.system(size: 18))
.fontWeight(.regular)
.onTapGesture {
onTap()
}
}
ForEach(group.items, id: \.id) { item in
Button(action: {
appNav.append(dest: .detail(id: item.id))
}) {
AsyncImage(url: URL(string: item.thumb)) { phase in
switch phase {
case .empty:
ProgressView()
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 370, height: 180)
.clipped()
default:
Image("ph_img_big")
.resizable()
.aspectRatio(contentMode: .fill)
.clipped()
}
}
.frame(width: 370, height: 180)
.overlay(alignment: .topLeading) {
VStack(alignment: .leading, spacing: 8) {
Text(item.name)
.font(.system(size: 16))
.foregroundColor(.white)
.lineLimit(1)
Text(item.status)
.font(.system(size: 12))
.foregroundColor(.white)
.lineLimit(1)
}
.padding(5)
.background(
Color.black.opacity(0.6)
)
.cornerRadius(5)
.padding(8)
}
.background(GeometryReader { geometry in
let height = geometry.size.height
let minY = geometry.frame(in: .named("indexScrollView")).minY
let y = minY >= 0 ? minY : minY + height
Color.clear
.preference(key: DramaGroupElementPreferenceKey.self, value: [ group.group_id : y])
})
}
}
}
}
}
}
// MARK: PreferenceKey
extension IndexMainView {
// , [groupId : minY]
struct DramaGroupElementPreferenceKey: PreferenceKey {
static var defaultValue: [String: CGFloat] = [:]
static func reduce(value: inout [String: CGFloat], nextValue: () -> [String: CGFloat]) {
value.merge(nextValue()) { $1 }
}
}
//
struct FooterOffsetPreferenceKey: PreferenceKey {
static var defaultValue: Int = 0
static func reduce(value: inout Int, nextValue: () -> Int) {
value += nextValue()
}
}
}
#Preview {
IndexMainView()
}