282 lines
9.6 KiB
Swift
282 lines
9.6 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
|
|
@Environment(\.userId) private var userId
|
|
|
|
@State var indexModel = IndexModel()
|
|
@State private var scrollID: IndexModel.ScrollTarget? = nil
|
|
|
|
// 提示信息
|
|
@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
|
|
|
|
@State private var scrollOffset: CGFloat = 0
|
|
@State private var showFloatGroupLabel: Bool = false
|
|
|
|
@State private var scrollHeight: Double = 0
|
|
|
|
var body: some View {
|
|
VStack(alignment: .center) {
|
|
|
|
HStack(alignment: .center) {
|
|
Text("亚次元")
|
|
.font(.system(size: 20, weight: .bold))
|
|
.padding([.top, .bottom], 5)
|
|
Spacer()
|
|
|
|
HStack {
|
|
Text("♡ \(indexModel.follow_num)")
|
|
.font(.system(size: 18))
|
|
.foregroundColor(.black)
|
|
.padding([.top, .bottom], 5)
|
|
.padding(.leading, 10)
|
|
}
|
|
.contentShape(Rectangle())
|
|
.highPriorityGesture(
|
|
TapGesture().onEnded {
|
|
appNavigation.append(dest: .followList)
|
|
}
|
|
)
|
|
.zIndex(1)
|
|
.task {
|
|
await indexModel.reloadFollowNum(userId: userId)
|
|
}
|
|
}
|
|
.padding([.leading, .trailing], 15)
|
|
.frame(height: 50)
|
|
.background(Color(hex: "#F2F2F2"), ignoresSafeAreaEdges: .top)
|
|
|
|
ScrollView(.vertical, showsIndicators: false) {
|
|
ScrollViewOffsetReader(offset: $scrollOffset)
|
|
|
|
MixGroupLabelView(fixedDramaGroup: indexModel.fixedDramaGroup) {
|
|
if let groupId = indexModel.fixedDramaGroup?.group_id {
|
|
selectGroupId = groupId
|
|
indexModel.selectedDate = groupId
|
|
showDateNavPopover = true
|
|
}
|
|
}
|
|
.opacity(!showFloatGroupLabel ? 1 : 0)
|
|
|
|
// 基于日期的更新列表
|
|
LazyVStack(alignment: .center, spacing: 10) {
|
|
ForEach(indexModel.dramaGroupElements, id: \.id) { item in
|
|
switch item.data {
|
|
case .label(let groupId, let groupName, _):
|
|
DramaGroupLabelView(group_name: groupName) {
|
|
selectGroupId = groupId
|
|
indexModel.selectedDate = groupId
|
|
showDateNavPopover = true
|
|
}
|
|
.id(item.id)
|
|
case .item(let groupId, let item, _):
|
|
DramaGroupItemView(groupId: groupId, item: item)
|
|
.id(item.id)
|
|
}
|
|
}
|
|
}
|
|
.scrollTargetLayout()
|
|
|
|
ProgressView()
|
|
}
|
|
.frame(width: 370)
|
|
.coordinateSpace(name: "indexScrollView")
|
|
.scrollPosition(id: $scrollID)
|
|
.onChange(of: scrollOffset) {
|
|
self.showFloatGroupLabel = scrollOffset < 0
|
|
indexModel.offsetChanged(offset: scrollOffset)
|
|
}
|
|
.refreshable {
|
|
guard !self.showDateNavPopover && !self.headerRefreshing else {
|
|
return
|
|
}
|
|
|
|
// 上拉刷新功能
|
|
self.headerRefreshing = true
|
|
Task {
|
|
await self.indexModel.loadPrevUpdateDramasTask(userId: self.userId) { anchorGroupElement in
|
|
DispatchQueue.main.async {
|
|
self.headerRefreshing = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onChange(of: scrollID) { _, newValue in
|
|
if let newValue {
|
|
indexModel.scrollIDPublisher.send((userId, newValue))
|
|
}
|
|
}
|
|
.overlay(alignment: .topTrailing) {
|
|
MixGroupLabelView(fixedDramaGroup: indexModel.fixedDramaGroup) {
|
|
if let groupId = indexModel.fixedDramaGroup?.group_id {
|
|
selectGroupId = groupId
|
|
indexModel.selectedDate = groupId
|
|
showDateNavPopover = true
|
|
}
|
|
|
|
}
|
|
.opacity(showFloatGroupLabel ? 1 : 0)
|
|
}
|
|
}
|
|
.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)
|
|
|
|
|
|
}
|
|
}
|
|
|
|
struct ScrollViewOffsetReader: View {
|
|
@Binding var offset: CGFloat
|
|
|
|
var body: some View {
|
|
GeometryReader { geometry in
|
|
Color.clear
|
|
.preference(
|
|
key: ScrollOffsetPreferenceKey.self,
|
|
value: geometry.frame(in: .named("indexScrollView")).origin.y
|
|
)
|
|
}
|
|
.frame(height: 0)
|
|
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
|
|
DispatchQueue.main.async {
|
|
self.offset = value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ScrollOffsetPreferenceKey: PreferenceKey {
|
|
static var defaultValue: CGFloat = 0
|
|
|
|
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
|
value = nextValue()
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
extension IndexMainView {
|
|
|
|
struct MixGroupLabelView: View {
|
|
@EnvironmentObject var appNav: AppNavigation
|
|
|
|
let fixedDramaGroup: IndexModel.UpdateDramaGroup?
|
|
let onTap: () -> Void
|
|
|
|
var body: some View {
|
|
HStack(alignment: .center) {
|
|
Button(action: {
|
|
appNav.append(dest: .search)
|
|
}) {
|
|
Image(systemName: "magnifyingglass")
|
|
.font(.system(size: 20))
|
|
}
|
|
|
|
Spacer()
|
|
|
|
if let fixedDramaGroup {
|
|
Text(fixedDramaGroup.group_name)
|
|
.font(.system(size: 18))
|
|
.fontWeight(.regular)
|
|
.onTapGesture {
|
|
onTap()
|
|
}
|
|
}
|
|
}
|
|
.padding([.top, .bottom], 8)
|
|
.background(.white)
|
|
}
|
|
}
|
|
|
|
// 显示分组信息
|
|
struct DramaGroupLabelView: View {
|
|
let group_name: String
|
|
var onTap: () -> Void
|
|
|
|
var body: some View {
|
|
VStack(alignment: .center, spacing: 10) {
|
|
HStack {
|
|
Spacer()
|
|
Text(group_name)
|
|
.font(.system(size: 18))
|
|
.fontWeight(.regular)
|
|
.onTapGesture {
|
|
onTap()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 显示具体的item
|
|
struct DramaGroupItemView: View {
|
|
@EnvironmentObject var appNav: AppNavigation
|
|
|
|
let groupId: String
|
|
let item: IndexModel.UpdateDramaGroup.Item
|
|
|
|
var body: some View {
|
|
FlexImage(urlString: item.thumb, width: 370, height: 180, placeholder: "ph_img_big")
|
|
.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)
|
|
}
|
|
.contentShape(Rectangle())
|
|
.onTapGesture {
|
|
appNav.append(dest: .detail(id: item.id))
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#Preview {
|
|
IndexMainView()
|
|
}
|