dimensionhub/dimensionhub/Views/Index/IndexMainView.swift
2025-04-09 15:03:00 +08:00

248 lines
9.4 KiB
Swift

//
// IndexMainView.swift
// dimensionhub
//
// Created by on 2025/4/8.
//
import SwiftUI
import Refresh
//
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()
Button(action: {
appNavigation.append(dest: .followList)
}) {
HStack {
Text("\(indexModel.follow_num)")
.font(.system(size: 17))
.foregroundColor(.black)
.padding([.top, .bottom], 5)
.padding(.leading, 10)
}
}
.contentShape(Rectangle())
.zIndex(1)
}
.padding([.leading, .trailing], 15)
.frame(height: 50)
.background(Color(hex: "#F2F2F2"), ignoresSafeAreaEdges: .top)
ScrollView(.vertical, showsIndicators: false) {
//
RefreshHeader(refreshing: $headerRefreshing, action: {
guard !self.showDateNavPopover else {
return
}
Task {
//
self.headerRefreshing = true
await self.indexModel.loadMoreUpdateDramas(userId: self.userId, mode: .prev)
self.headerRefreshing = false
}
}) { progress in
ProgressView()
}
Rectangle()
.frame(width: 0, height: 15)
//
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
}
}
}
//
RefreshFooter(refreshing: $footerRefreshing, action: {
guard !self.showDateNavPopover else {
return
}
Task {
self.footerRefreshing = true
await self.indexModel.loadMoreUpdateDramas(userId: self.userId, mode: .next)
self.footerRefreshing = false
}
}) {
ProgressView()
}
.noMore(noMore)
.preload(offset: 50)
}
.enableRefresh()
.frame(width: 370)
.coordinateSpace(name: "indexScrollView")
.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}) {
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])
})
}
}
}
}
}
// , [groupId : minY]
struct DramaGroupElementPreferenceKey: PreferenceKey {
static var defaultValue: [String: CGFloat] = [:]
static func reduce(value: inout [String: CGFloat], nextValue: () -> [String: CGFloat]) {
value.merge(nextValue()) { $1 }
}
}
}
#Preview {
IndexMainView()
}