fix index view
This commit is contained in:
parent
d411eb28b1
commit
5b14d559bf
@ -6,6 +6,10 @@
|
|||||||
objectVersion = 77;
|
objectVersion = 77;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
C87233D82DA3D2D7006A6CDC /* Refresh in Frameworks */ = {isa = PBXBuildFile; productRef = C87233D72DA3D2D7006A6CDC /* Refresh */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
C85F58D32D64D11000D761E9 /* PBXContainerItemProxy */ = {
|
C85F58D32D64D11000D761E9 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
@ -65,6 +69,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
C87233D82DA3D2D7006A6CDC /* Refresh in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -125,6 +130,7 @@
|
|||||||
);
|
);
|
||||||
name = dimensionhub;
|
name = dimensionhub;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
|
C87233D72DA3D2D7006A6CDC /* Refresh */,
|
||||||
);
|
);
|
||||||
productName = dimensionhub;
|
productName = dimensionhub;
|
||||||
productReference = C85F58C02D64D10F00D761E9 /* dimensionhub.app */;
|
productReference = C85F58C02D64D10F00D761E9 /* dimensionhub.app */;
|
||||||
@ -209,6 +215,7 @@
|
|||||||
mainGroup = C85F58B72D64D10F00D761E9;
|
mainGroup = C85F58B72D64D10F00D761E9;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
|
C87233D62DA3D2D7006A6CDC /* XCRemoteSwiftPackageReference "Refresh" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 77;
|
||||||
productRefGroup = C85F58C12D64D10F00D761E9 /* Products */;
|
productRefGroup = C85F58C12D64D10F00D761E9 /* Products */;
|
||||||
@ -585,6 +592,25 @@
|
|||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
|
C87233D62DA3D2D7006A6CDC /* XCRemoteSwiftPackageReference "Refresh" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/wxxsw/Refresh.git";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 0.2.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
C87233D72DA3D2D7006A6CDC /* Refresh */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = C87233D62DA3D2D7006A6CDC /* XCRemoteSwiftPackageReference "Refresh" */;
|
||||||
|
productName = Refresh;
|
||||||
|
};
|
||||||
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = C85F58B82D64D10F00D761E9 /* Project object */;
|
rootObject = C85F58B82D64D10F00D761E9 /* Project object */;
|
||||||
}
|
}
|
||||||
|
|||||||
48
dimensionhub/Views/Index/IndexExceptionView.swift
Normal file
48
dimensionhub/Views/Index/IndexExceptionView.swift
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// IndexExceptionView.swift
|
||||||
|
// dimensionhub
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2025/4/8.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct IndexExceptionView: View {
|
||||||
|
let onRetry: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
VStack(alignment: .center, spacing: 20) {
|
||||||
|
Spacer()
|
||||||
|
Image("lost_network")
|
||||||
|
|
||||||
|
Text("网络状态待提升,点击重试")
|
||||||
|
.font(.system(size: 13))
|
||||||
|
.foregroundColor(Color(hex: "#333333"))
|
||||||
|
|
||||||
|
Rectangle()
|
||||||
|
.frame(width: 100, height: 25)
|
||||||
|
.foregroundColor(Color(hex: "#F2F2F2"))
|
||||||
|
.overlay {
|
||||||
|
Text("重新加载")
|
||||||
|
.font(.system(size: 13))
|
||||||
|
.foregroundColor(Color(hex: "#999999"))
|
||||||
|
.fontWeight(.regular)
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
onRetry()
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color(hex: "#F6F6F6"), ignoresSafeAreaEdges: .all)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
IndexExceptionView() {
|
||||||
|
print("call me retry")
|
||||||
|
}
|
||||||
|
}
|
||||||
262
dimensionhub/Views/Index/IndexMainView.swift
Normal file
262
dimensionhub/Views/Index/IndexMainView.swift
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
//
|
||||||
|
// IndexMainView.swift
|
||||||
|
// dimensionhub
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2025/4/8.
|
||||||
|
//
|
||||||
|
import SwiftUI
|
||||||
|
import Refresh
|
||||||
|
|
||||||
|
// 首页的主要窗口
|
||||||
|
struct IndexMainView: View {
|
||||||
|
@Environment(\.modelContext) private var modelContext
|
||||||
|
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
|
||||||
|
|
||||||
|
@State var indexModel = IndexModel()
|
||||||
|
@State var isMoreLoading: Bool = false
|
||||||
|
// 前向刷新
|
||||||
|
@State var isPrevLoading: Bool = false
|
||||||
|
|
||||||
|
// 提示信息
|
||||||
|
@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) {
|
||||||
|
Color.clear
|
||||||
|
.overlay {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
Text("亚次元")
|
||||||
|
.font(.system(size: 18, weight: .bold))
|
||||||
|
.padding([.top, .bottom], 5)
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
NavigationLink(destination: FollowListView()) {
|
||||||
|
HStack {
|
||||||
|
Text("♡ \(indexModel.follow_num)")
|
||||||
|
.font(.system(size: 17))
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.padding([.top, .bottom], 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding([.leading, .trailing], 15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: 50)
|
||||||
|
.background(Color(hex: "#F2F2F2"), ignoresSafeAreaEdges: .top)
|
||||||
|
|
||||||
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
|
||||||
|
RefreshHeader(refreshing: $headerRefreshing, action: {
|
||||||
|
print("call me head headerRefreshing")
|
||||||
|
}) { progress in
|
||||||
|
print("progress is: \(progress)")
|
||||||
|
|
||||||
|
return ProgressView()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基于日期的更新列表
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rectangle()
|
||||||
|
// .frame(height: 0)
|
||||||
|
// .background(GeometryReader { geometry in
|
||||||
|
// Color.clear.onChange(of: geometry.frame(in: .global).minY) {_, offset in
|
||||||
|
// let frame = geometry.frame(in: .global)
|
||||||
|
// let screenBounds = UIScreen.main.bounds
|
||||||
|
// let contextFrame = geometry.frame(in: .named("indexScrollView"))
|
||||||
|
//
|
||||||
|
// if screenBounds.height - frame.minY > 50 && contextFrame.minY > 0 && !isMoreLoading {
|
||||||
|
// Task {
|
||||||
|
// self.isMoreLoading = true
|
||||||
|
// await self.indexModel.loadMoreUpdateDramas(userId: self.userId, mode: .next)
|
||||||
|
// self.isMoreLoading = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
RefreshFooter(refreshing: $footerRefreshing, action: {
|
||||||
|
print("call me here $footerRefreshing")
|
||||||
|
Task {
|
||||||
|
self.footerRefreshing = true
|
||||||
|
await self.indexModel.loadMoreUpdateDramas(userId: self.userId, mode: .next)
|
||||||
|
self.footerRefreshing = false
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
.noMore(noMore)
|
||||||
|
.preload(offset: 50)
|
||||||
|
|
||||||
|
if self.isMoreLoading {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.enableRefresh()
|
||||||
|
.frame(width: 370)
|
||||||
|
.coordinateSpace(name: "indexScrollView")
|
||||||
|
// .refreshable {
|
||||||
|
// guard !self.isPrevLoading && !self.showDateNavPopover else {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 上拉刷新功能
|
||||||
|
// self.isPrevLoading = true
|
||||||
|
// await self.indexModel.loadMoreUpdateDramas(userId: self.userId, mode: .prev)
|
||||||
|
// self.isPrevLoading = false
|
||||||
|
// }
|
||||||
|
.overlay(alignment: .topTrailing) {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
NavigationLink {
|
||||||
|
SearchView()
|
||||||
|
} label: {
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
NavigationLink(destination: DetailView(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()
|
||||||
|
}
|
||||||
214
dimensionhub/Views/Index/IndexModel.swift
Normal file
214
dimensionhub/Views/Index/IndexModel.swift
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
//
|
||||||
|
// IndexModel.swift
|
||||||
|
// dimensionhub
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2025/4/8.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Observation
|
||||||
|
|
||||||
|
@Observable
|
||||||
|
final class IndexModel {
|
||||||
|
|
||||||
|
struct DramaItem: Codable {
|
||||||
|
struct Episode: Codable, Identifiable {
|
||||||
|
let id = UUID().uuidString
|
||||||
|
let name: String
|
||||||
|
let thumb: String
|
||||||
|
let num_name: String
|
||||||
|
let play: String
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case name, thumb, num_name, play
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let id: Int
|
||||||
|
let title: String
|
||||||
|
let episodes: [Episode]
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UpdateDramaGroup: Codable {
|
||||||
|
struct Item: Codable {
|
||||||
|
let id: Int
|
||||||
|
let name: String
|
||||||
|
let time: Int
|
||||||
|
let thumb: String
|
||||||
|
let status: String
|
||||||
|
}
|
||||||
|
|
||||||
|
let group_id: String
|
||||||
|
let group_name: String
|
||||||
|
let items: [Item]
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IndexResponse: Codable {
|
||||||
|
let update_dramas: [UpdateDramaGroup]
|
||||||
|
let follow_num: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedDate: String
|
||||||
|
|
||||||
|
// 保存原始的更新数据
|
||||||
|
var updateDramaGroups: [UpdateDramaGroup] = []
|
||||||
|
var follow_num: String = "0"
|
||||||
|
|
||||||
|
// 用来显示固定栏目的group_name
|
||||||
|
var fixedDramaGroup: UpdateDramaGroup? = nil
|
||||||
|
|
||||||
|
@ObservationIgnored
|
||||||
|
private var isLoaded = false
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.selectedDate = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadData(userId: String) async {
|
||||||
|
guard !isLoaded else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = await API.getIndexData(userId: userId, as: IndexResponse.self)
|
||||||
|
switch response {
|
||||||
|
case .error(let code, let message):
|
||||||
|
print("index load data get error_code: \(code), message: \(message)")
|
||||||
|
case .result(let result):
|
||||||
|
await MainActor.run {
|
||||||
|
self.updateDramaGroups = result.update_dramas
|
||||||
|
self.fixedDramaGroup = result.update_dramas.first
|
||||||
|
self.follow_num = result.follow_num >= 100 ? "99+" : "\(result.follow_num)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.isLoaded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFixedDrameGroup(groupId: String) {
|
||||||
|
if let newFixedDramaGroup = self.updateDramaGroups.first(where: {$0.group_id == groupId}),
|
||||||
|
newFixedDramaGroup.group_id != self.fixedDramaGroup?.group_id {
|
||||||
|
self.fixedDramaGroup = newFixedDramaGroup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMoreUpdateDramas(userId: String, mode: API.LoadMode) async {
|
||||||
|
// 按照id来判断不一定正确,需要借助其他值
|
||||||
|
let dramaIds = self.getDramaIds(self.updateDramaGroups)
|
||||||
|
print("current ids: \(dramaIds)")
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case .prev:
|
||||||
|
// 查找最小的id
|
||||||
|
if let firstId = dramaIds.first {
|
||||||
|
let response = await API.loadMoreUpdateDramas(userId: userId, mode: mode, id: firstId, as: [UpdateDramaGroup].self)
|
||||||
|
if case let .result(groups) = response {
|
||||||
|
if groups.count > 0 {
|
||||||
|
|
||||||
|
print("--------- before ------------")
|
||||||
|
displayDramaGroups(self.updateDramaGroups)
|
||||||
|
await MainActor.run {
|
||||||
|
self.updateDramaGroups = preappendMergeDramaGroups(groups: self.updateDramaGroups, mergeGroups: groups)
|
||||||
|
}
|
||||||
|
print("--------- after ------------")
|
||||||
|
displayDramaGroups(self.updateDramaGroups)
|
||||||
|
print("--------- ------------")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .next:
|
||||||
|
if let lastId = dramaIds.last {
|
||||||
|
let response = await API.loadMoreUpdateDramas(userId: userId, mode: mode, id: lastId, as: [UpdateDramaGroup].self)
|
||||||
|
if case let .result(groups) = response {
|
||||||
|
if groups.count > 0 {
|
||||||
|
print("--------- before ------------")
|
||||||
|
displayDramaGroups(self.updateDramaGroups)
|
||||||
|
await MainActor.run {
|
||||||
|
self.updateDramaGroups = appendMergeDramaGroups(groups: self.updateDramaGroups, mergeGroups: groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
print("----------after-----------")
|
||||||
|
displayDramaGroups(self.updateDramaGroups)
|
||||||
|
print("---------------------")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指定日期,并更新日期下对应的数据
|
||||||
|
func loadDateUpdateDramas(userId: String, date: String) async {
|
||||||
|
self.updateDramaGroups.removeAll()
|
||||||
|
let response = await API.loadDateUpdateDramas(userId: userId, date: date, as: [UpdateDramaGroup].self)
|
||||||
|
if case let .result(groups) = response {
|
||||||
|
await MainActor.run {
|
||||||
|
self.updateDramaGroups = groups
|
||||||
|
self.fixedDramaGroup = groups.first
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并groups
|
||||||
|
private func preappendMergeDramaGroups(groups: [UpdateDramaGroup], mergeGroups: [UpdateDramaGroup]) -> [UpdateDramaGroup] {
|
||||||
|
var targetGroups = groups
|
||||||
|
|
||||||
|
for group in mergeGroups {
|
||||||
|
if let idx = targetGroups.firstIndex(where: { $0.group_id == group.group_id}) {
|
||||||
|
var newItems = group.items
|
||||||
|
newItems.append(contentsOf: targetGroups[idx].items)
|
||||||
|
|
||||||
|
targetGroups[idx] = UpdateDramaGroup(group_id: group.group_id, group_name: group.group_name, items: newItems)
|
||||||
|
} else {
|
||||||
|
targetGroups.insert(group, at: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortDramaGroups(groups: targetGroups)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func appendMergeDramaGroups(groups: [UpdateDramaGroup], mergeGroups: [UpdateDramaGroup]) -> [UpdateDramaGroup] {
|
||||||
|
var targetGroups = groups
|
||||||
|
|
||||||
|
for group in mergeGroups {
|
||||||
|
if let idx = targetGroups.firstIndex(where: { $0.group_id == group.group_id}) {
|
||||||
|
var newItems = targetGroups[idx].items
|
||||||
|
newItems.append(contentsOf: group.items)
|
||||||
|
|
||||||
|
targetGroups[idx] = UpdateDramaGroup(group_id: group.group_id, group_name: group.group_name, items: newItems)
|
||||||
|
} else {
|
||||||
|
targetGroups.append(group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortDramaGroups(groups: targetGroups)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按照日期进行排序
|
||||||
|
private func sortDramaGroups(groups: [UpdateDramaGroup]) -> [UpdateDramaGroup] {
|
||||||
|
return groups.sorted { g0, g1 in
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.dateFormat = "yyyy-MM"
|
||||||
|
|
||||||
|
if let date0 = dateFormatter.date(from: g0.group_id),
|
||||||
|
let date1 = dateFormatter.date(from: g1.group_id) {
|
||||||
|
return date0 > date1
|
||||||
|
} else {
|
||||||
|
return g0.group_id > g1.group_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getDramaIds(_ updateDramaGroups: [UpdateDramaGroup]) -> [Int] {
|
||||||
|
return self.updateDramaGroups.flatMap { group in
|
||||||
|
return group.items.map { item in
|
||||||
|
return item.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func displayDramaGroups(_ groups: [UpdateDramaGroup]) {
|
||||||
|
for group in groups {
|
||||||
|
let ids = group.items.map { $0.id}
|
||||||
|
print("group_id: \(group.group_id), items: \(ids)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
61
dimensionhub/Views/Index/IndexView.swift
Normal file
61
dimensionhub/Views/Index/IndexView.swift
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// ContentView.swift
|
||||||
|
// dimensionhub
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2025/2/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftData
|
||||||
|
import Observation
|
||||||
|
import Network
|
||||||
|
|
||||||
|
struct IndexView: View {
|
||||||
|
// 网络状态检测, 第一次进入app时,如果网络没有授权,网络请求会失败
|
||||||
|
enum NetworkStatus {
|
||||||
|
case satisfied
|
||||||
|
case unsatisfied
|
||||||
|
}
|
||||||
|
|
||||||
|
@State private var networkStatus: NetworkStatus = .satisfied
|
||||||
|
|
||||||
|
private static let queue = DispatchQueue(label: "NetworkMonitorQueue")
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
switch self.networkStatus {
|
||||||
|
case .unsatisfied:
|
||||||
|
IndexExceptionView {
|
||||||
|
self.checkNetworkStatus()
|
||||||
|
}
|
||||||
|
case .satisfied:
|
||||||
|
IndexMainView()
|
||||||
|
.id("indexMainView")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
self.checkNetworkStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func checkNetworkStatus() {
|
||||||
|
let monitor = NWPathMonitor()
|
||||||
|
monitor.pathUpdateHandler = { path in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
switch path.status {
|
||||||
|
case .satisfied:
|
||||||
|
self.networkStatus = .satisfied
|
||||||
|
default:
|
||||||
|
self.networkStatus = .unsatisfied
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
monitor.start(queue: Self.queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
IndexView()
|
||||||
|
.modelContainer(for: Item.self, inMemory: true)
|
||||||
|
}
|
||||||
@ -1,530 +0,0 @@
|
|||||||
//
|
|
||||||
// ContentView.swift
|
|
||||||
// dimensionhub
|
|
||||||
//
|
|
||||||
// Created by 安礼成 on 2025/2/18.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import SwiftData
|
|
||||||
import Observation
|
|
||||||
import Network
|
|
||||||
|
|
||||||
@Observable
|
|
||||||
final class IndexModel {
|
|
||||||
|
|
||||||
struct DramaItem: Codable {
|
|
||||||
struct Episode: Codable, Identifiable {
|
|
||||||
let id = UUID().uuidString
|
|
||||||
let name: String
|
|
||||||
let thumb: String
|
|
||||||
let num_name: String
|
|
||||||
let play: String
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case name, thumb, num_name, play
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let id: Int
|
|
||||||
let title: String
|
|
||||||
let episodes: [Episode]
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UpdateDramaGroup: Codable {
|
|
||||||
struct Item: Codable {
|
|
||||||
let id: Int
|
|
||||||
let name: String
|
|
||||||
let time: Int
|
|
||||||
let thumb: String
|
|
||||||
let status: String
|
|
||||||
}
|
|
||||||
|
|
||||||
let group_id: String
|
|
||||||
let group_name: String
|
|
||||||
let items: [Item]
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IndexResponse: Codable {
|
|
||||||
let update_dramas: [UpdateDramaGroup]
|
|
||||||
let follow_num: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedDate: String
|
|
||||||
|
|
||||||
// 保存原始的更新数据
|
|
||||||
var updateDramaGroups: [UpdateDramaGroup] = []
|
|
||||||
var follow_num: String = "0"
|
|
||||||
|
|
||||||
// 用来显示固定栏目的group_name
|
|
||||||
var fixedDramaGroup: UpdateDramaGroup? = nil
|
|
||||||
|
|
||||||
@ObservationIgnored
|
|
||||||
private var isLoaded = false
|
|
||||||
|
|
||||||
init() {
|
|
||||||
self.selectedDate = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadData(userId: String) async {
|
|
||||||
guard !isLoaded else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = await API.getIndexData(userId: userId, as: IndexResponse.self)
|
|
||||||
switch response {
|
|
||||||
case .error(let code, let message):
|
|
||||||
print("index load data get error_code: \(code), message: \(message)")
|
|
||||||
case .result(let result):
|
|
||||||
await MainActor.run {
|
|
||||||
self.updateDramaGroups = result.update_dramas
|
|
||||||
self.fixedDramaGroup = result.update_dramas.first
|
|
||||||
self.follow_num = result.follow_num >= 100 ? "99+" : "\(result.follow_num)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.isLoaded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func setFixedDrameGroup(groupId: String) {
|
|
||||||
if let newFixedDramaGroup = self.updateDramaGroups.first(where: {$0.group_id == groupId}),
|
|
||||||
newFixedDramaGroup.group_id != self.fixedDramaGroup?.group_id {
|
|
||||||
self.fixedDramaGroup = newFixedDramaGroup
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadMoreUpdateDramas(userId: String, mode: API.LoadMode) async {
|
|
||||||
// 按照id来判断不一定正确,需要借助其他值
|
|
||||||
let dramaIds = self.getDramaIds(self.updateDramaGroups)
|
|
||||||
print("current ids: \(dramaIds)")
|
|
||||||
|
|
||||||
switch mode {
|
|
||||||
case .prev:
|
|
||||||
// 查找最小的id
|
|
||||||
if let firstId = dramaIds.first {
|
|
||||||
let response = await API.loadMoreUpdateDramas(userId: userId, mode: mode, id: firstId, as: [UpdateDramaGroup].self)
|
|
||||||
if case let .result(groups) = response {
|
|
||||||
if groups.count > 0 {
|
|
||||||
|
|
||||||
print("--------- before ------------")
|
|
||||||
displayDramaGroups(self.updateDramaGroups)
|
|
||||||
await MainActor.run {
|
|
||||||
self.updateDramaGroups = preappendMergeDramaGroups(groups: self.updateDramaGroups, mergeGroups: groups)
|
|
||||||
}
|
|
||||||
print("--------- after ------------")
|
|
||||||
displayDramaGroups(self.updateDramaGroups)
|
|
||||||
print("--------- ------------")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case .next:
|
|
||||||
if let lastId = dramaIds.last {
|
|
||||||
let response = await API.loadMoreUpdateDramas(userId: userId, mode: mode, id: lastId, as: [UpdateDramaGroup].self)
|
|
||||||
if case let .result(groups) = response {
|
|
||||||
if groups.count > 0 {
|
|
||||||
print("--------- before ------------")
|
|
||||||
displayDramaGroups(self.updateDramaGroups)
|
|
||||||
await MainActor.run {
|
|
||||||
self.updateDramaGroups = appendMergeDramaGroups(groups: self.updateDramaGroups, mergeGroups: groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
print("----------after-----------")
|
|
||||||
displayDramaGroups(self.updateDramaGroups)
|
|
||||||
print("---------------------")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 指定日期,并更新日期下对应的数据
|
|
||||||
func loadDateUpdateDramas(userId: String, date: String) async {
|
|
||||||
self.updateDramaGroups.removeAll()
|
|
||||||
let response = await API.loadDateUpdateDramas(userId: userId, date: date, as: [UpdateDramaGroup].self)
|
|
||||||
if case let .result(groups) = response {
|
|
||||||
await MainActor.run {
|
|
||||||
self.updateDramaGroups = groups
|
|
||||||
self.fixedDramaGroup = groups.first
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合并groups
|
|
||||||
private func preappendMergeDramaGroups(groups: [UpdateDramaGroup], mergeGroups: [UpdateDramaGroup]) -> [UpdateDramaGroup] {
|
|
||||||
var targetGroups = groups
|
|
||||||
|
|
||||||
for group in mergeGroups {
|
|
||||||
if let idx = targetGroups.firstIndex(where: { $0.group_id == group.group_id}) {
|
|
||||||
var newItems = group.items
|
|
||||||
newItems.append(contentsOf: targetGroups[idx].items)
|
|
||||||
|
|
||||||
targetGroups[idx] = UpdateDramaGroup(group_id: group.group_id, group_name: group.group_name, items: newItems)
|
|
||||||
} else {
|
|
||||||
targetGroups.insert(group, at: 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortDramaGroups(groups: targetGroups)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func appendMergeDramaGroups(groups: [UpdateDramaGroup], mergeGroups: [UpdateDramaGroup]) -> [UpdateDramaGroup] {
|
|
||||||
var targetGroups = groups
|
|
||||||
|
|
||||||
for group in mergeGroups {
|
|
||||||
if let idx = targetGroups.firstIndex(where: { $0.group_id == group.group_id}) {
|
|
||||||
var newItems = targetGroups[idx].items
|
|
||||||
newItems.append(contentsOf: group.items)
|
|
||||||
|
|
||||||
targetGroups[idx] = UpdateDramaGroup(group_id: group.group_id, group_name: group.group_name, items: newItems)
|
|
||||||
} else {
|
|
||||||
targetGroups.append(group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortDramaGroups(groups: targetGroups)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按照日期进行排序
|
|
||||||
private func sortDramaGroups(groups: [UpdateDramaGroup]) -> [UpdateDramaGroup] {
|
|
||||||
return groups.sorted { g0, g1 in
|
|
||||||
let dateFormatter = DateFormatter()
|
|
||||||
dateFormatter.dateFormat = "yyyy-MM"
|
|
||||||
|
|
||||||
if let date0 = dateFormatter.date(from: g0.group_id),
|
|
||||||
let date1 = dateFormatter.date(from: g1.group_id) {
|
|
||||||
return date0 > date1
|
|
||||||
} else {
|
|
||||||
return g0.group_id > g1.group_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func getDramaIds(_ updateDramaGroups: [UpdateDramaGroup]) -> [Int] {
|
|
||||||
return self.updateDramaGroups.flatMap { group in
|
|
||||||
return group.items.map { item in
|
|
||||||
return item.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func displayDramaGroups(_ groups: [UpdateDramaGroup]) {
|
|
||||||
for group in groups {
|
|
||||||
let ids = group.items.map { $0.id}
|
|
||||||
print("group_id: \(group.group_id), items: \(ids)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IndexView: View {
|
|
||||||
|
|
||||||
// 网络状态检测, 第一次进入app时,如果网络没有授权,网络请求会失败
|
|
||||||
enum NetworkStatus {
|
|
||||||
case satisfied
|
|
||||||
case unsatisfied
|
|
||||||
}
|
|
||||||
@State private var networkStatus: NetworkStatus = .satisfied
|
|
||||||
|
|
||||||
private static let queue = DispatchQueue(label: "NetworkMonitorQueue")
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ZStack {
|
|
||||||
switch self.networkStatus {
|
|
||||||
case .unsatisfied:
|
|
||||||
IndexExceptionView {
|
|
||||||
self.checkNetworkStatus()
|
|
||||||
}
|
|
||||||
case .satisfied:
|
|
||||||
IndexMainView()
|
|
||||||
.id("indexMainView")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
self.checkNetworkStatus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func checkNetworkStatus() {
|
|
||||||
let monitor = NWPathMonitor()
|
|
||||||
monitor.pathUpdateHandler = { path in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
switch path.status {
|
|
||||||
case .satisfied:
|
|
||||||
self.networkStatus = .satisfied
|
|
||||||
default:
|
|
||||||
self.networkStatus = .unsatisfied
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
monitor.start(queue: Self.queue)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension IndexView {
|
|
||||||
|
|
||||||
struct IndexExceptionView: View {
|
|
||||||
let onRetry: () -> Void
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
VStack(alignment: .center, spacing: 20) {
|
|
||||||
Spacer()
|
|
||||||
Image("lost_network")
|
|
||||||
|
|
||||||
Text("网络状态待提升,点击重试")
|
|
||||||
.font(.system(size: 13))
|
|
||||||
.foregroundColor(Color(hex: "#333333"))
|
|
||||||
|
|
||||||
Rectangle()
|
|
||||||
.frame(width: 100, height: 25)
|
|
||||||
.foregroundColor(Color(hex: "#F2F2F2"))
|
|
||||||
.overlay {
|
|
||||||
Text("重新加载")
|
|
||||||
.font(.system(size: 13))
|
|
||||||
.foregroundColor(Color(hex: "#999999"))
|
|
||||||
.fontWeight(.regular)
|
|
||||||
}
|
|
||||||
.onTapGesture {
|
|
||||||
onRetry()
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.background(Color(hex: "#F6F6F6"), ignoresSafeAreaEdges: .all)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 首页的主要窗口
|
|
||||||
struct IndexMainView: View {
|
|
||||||
@Environment(\.modelContext) private var modelContext
|
|
||||||
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
|
|
||||||
|
|
||||||
@State var indexModel = IndexModel()
|
|
||||||
@State var isMoreLoading: Bool = false
|
|
||||||
// 前向刷新
|
|
||||||
@State var isPrevLoading: Bool = false
|
|
||||||
|
|
||||||
// 提示信息
|
|
||||||
@State var showPrompt: Bool = false
|
|
||||||
@State var promptMessage: String = ""
|
|
||||||
|
|
||||||
// 是否显示日期弹出层
|
|
||||||
@State private var selectGroupId: String = ""
|
|
||||||
@State private var showDateNavPopover: Bool = false
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .center) {
|
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
Color.clear
|
|
||||||
.overlay {
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
Text("亚次元")
|
|
||||||
.font(.system(size: 18, weight: .bold))
|
|
||||||
.padding([.top, .bottom], 5)
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
NavigationLink(destination: FollowListView()) {
|
|
||||||
HStack {
|
|
||||||
Text("♡ \(indexModel.follow_num)")
|
|
||||||
.font(.system(size: 17))
|
|
||||||
.foregroundColor(.black)
|
|
||||||
.padding([.top, .bottom], 5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding([.leading, .trailing], 15)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(height: 50)
|
|
||||||
.background(Color(hex: "#F2F2F2"), ignoresSafeAreaEdges: .top)
|
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
|
||||||
|
|
||||||
// 基于日期的更新列表
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle()
|
|
||||||
.frame(height: 0)
|
|
||||||
.background(GeometryReader { geometry in
|
|
||||||
Color.clear.onChange(of: geometry.frame(in: .global).minY) {_, offset in
|
|
||||||
let frame = geometry.frame(in: .global)
|
|
||||||
let screenBounds = UIScreen.main.bounds
|
|
||||||
let contextFrame = geometry.frame(in: .named("indexScrollView"))
|
|
||||||
|
|
||||||
if screenBounds.height - frame.minY > 50 && contextFrame.minY > 0 && !isMoreLoading {
|
|
||||||
Task {
|
|
||||||
self.isMoreLoading = true
|
|
||||||
await self.indexModel.loadMoreUpdateDramas(userId: self.userId, mode: .next)
|
|
||||||
self.isMoreLoading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if self.isMoreLoading {
|
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(width: 370)
|
|
||||||
.coordinateSpace(name: "indexScrollView")
|
|
||||||
.refreshable {
|
|
||||||
guard !self.isPrevLoading && !self.showDateNavPopover else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上拉刷新功能
|
|
||||||
self.isPrevLoading = true
|
|
||||||
await self.indexModel.loadMoreUpdateDramas(userId: self.userId, mode: .prev)
|
|
||||||
self.isPrevLoading = false
|
|
||||||
}
|
|
||||||
.overlay(alignment: .topTrailing) {
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
NavigationLink {
|
|
||||||
SearchView()
|
|
||||||
} label: {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示分组信息
|
|
||||||
struct DramaGroupView: View {
|
|
||||||
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
|
|
||||||
NavigationLink(destination: DetailView(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 {
|
|
||||||
IndexView()
|
|
||||||
.modelContainer(for: Item.self, inMemory: true)
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user