266 lines
9.7 KiB
Swift
266 lines
9.7 KiB
Swift
//
|
||
// 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()
|
||
}
|