351 lines
12 KiB
Swift
351 lines
12 KiB
Swift
//
|
|
// ContentView.swift
|
|
// dimensionhub
|
|
//
|
|
// Created by 安礼成 on 2025/2/18.
|
|
//
|
|
|
|
import SwiftUI
|
|
import SwiftData
|
|
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: Int32
|
|
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 dramas: [DramaItem]
|
|
}
|
|
|
|
struct UpdateDramaShowItem {
|
|
enum Element {
|
|
case group(UpdateDramaGroup)
|
|
case item(UpdateDramaGroup.Item)
|
|
}
|
|
|
|
let id = UUID().uuidString
|
|
let element: Element
|
|
|
|
init(element: Element) {
|
|
self.element = element
|
|
}
|
|
}
|
|
|
|
var dramas: [DramaItem]
|
|
var showUpdateDramas: [UpdateDramaShowItem]
|
|
var selectedDate: String
|
|
|
|
init() {
|
|
self.dramas = []
|
|
self.showUpdateDramas = []
|
|
self.selectedDate = ""
|
|
}
|
|
|
|
@MainActor
|
|
func loadData() async {
|
|
let response = await API.getIndexData(userId: 1, as: IndexResponse.self)
|
|
switch response {
|
|
case .error(let code, let message):
|
|
print(code)
|
|
print(message)
|
|
case .result(let result):
|
|
self.dramas = result.dramas
|
|
self.showUpdateDramas = groupUpdateDramas(updateDramaGroups: result.update_dramas)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
func loadMoreUpdateDramas(userId: Int, mode: API.LoadMode, id: Int) async {
|
|
let response = await API.loadMoreUpdateDramas(userId: userId, mode: mode, id: id, as: [UpdateDramaGroup].self)
|
|
if case let .result(groups) = response {
|
|
let showItems = groupUpdateDramas(updateDramaGroups: groups)
|
|
self.showUpdateDramas.append(contentsOf: showItems)
|
|
}
|
|
}
|
|
|
|
private func groupUpdateDramas(updateDramaGroups: [UpdateDramaGroup]) -> [UpdateDramaShowItem] {
|
|
var updateItems: [UpdateDramaShowItem] = []
|
|
updateDramaGroups.forEach { group in
|
|
updateItems.append(.init(element: .group(group)))
|
|
group.items.forEach { item in
|
|
updateItems.append(.init(element: .item(item)))
|
|
}
|
|
}
|
|
return updateItems
|
|
}
|
|
|
|
}
|
|
|
|
struct IndexView: View {
|
|
@Environment(\.modelContext) private var modelContext
|
|
//@Query private var items: [Item]
|
|
@Query private var userModel: [UserModel]
|
|
|
|
@State var indexModel = IndexModel()
|
|
@State var isLoading: Bool = false
|
|
|
|
// 是否显示日期弹出层
|
|
@State private var selectGroupId: String = ""
|
|
@State private var showDateNavPopover: Bool = false
|
|
|
|
var body: some View {
|
|
VStack(alignment: .center) {
|
|
|
|
HStack(alignment: .center) {
|
|
Color.clear
|
|
.overlay {
|
|
Text("亚次元")
|
|
.font(.system(size: 16))
|
|
.padding([.top, .bottom], 5)
|
|
}
|
|
}
|
|
.frame(height: 50)
|
|
.background(Color(hex: "#F2F2F2"), ignoresSafeAreaEdges: .bottom)
|
|
|
|
HStack(alignment: .center) {
|
|
Spacer()
|
|
Text("番剧补完计划")
|
|
.font(.system(size: 24))
|
|
.foregroundColor(Color(hex: "#999999"))
|
|
}
|
|
|
|
ForEach(indexModel.dramas, id: \.id) { drama in
|
|
DramaCellView(dramaItem: drama)
|
|
}
|
|
|
|
// 基于日期的更新列表
|
|
|
|
ScrollView(.vertical, showsIndicators: false) {
|
|
VStack(alignment: .center, spacing: 10) {
|
|
ForEach(indexModel.showUpdateDramas, id: \.id) { drama in
|
|
switch drama.element {
|
|
case .group(let group):
|
|
HStack {
|
|
Spacer()
|
|
Text(group.group_name)
|
|
.font(.system(size: 18))
|
|
.fontWeight(.regular)
|
|
.onTapGesture {
|
|
selectGroupId = group.group_id
|
|
print("current group_id: \(self.selectGroupId)")
|
|
indexModel.selectedDate = group.group_id
|
|
showDateNavPopover = true
|
|
}
|
|
}
|
|
case .item(let item):
|
|
AsyncImage(url: URL(string: item.thumb)) { image in
|
|
image.resizable()
|
|
} placeholder: {
|
|
ProgressView()
|
|
}
|
|
.frame(width: 370, height: 180)
|
|
.overlay {
|
|
HStack {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
Text(item.name)
|
|
.font(.system(size: 16))
|
|
.foregroundColor(Color(hex: "#333333"))
|
|
.lineLimit(1)
|
|
|
|
Text(item.status)
|
|
.font(.system(size: 12))
|
|
.foregroundColor(Color(hex: "#333333"))
|
|
.lineLimit(1)
|
|
|
|
Spacer()
|
|
}
|
|
Spacer()
|
|
}
|
|
.padding([.top, .leading], 10)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onChange(of: indexModel.selectedDate) { oldValue, newValue in
|
|
print("old date: \(oldValue), new value: \(newValue)")
|
|
}
|
|
|
|
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
|
|
|
|
if screenBounds.height - frame.minY > 50 && !isLoading {
|
|
Task {
|
|
self.isLoading = true
|
|
await self.indexModel.loadMoreUpdateDramas(userId: 1, mode: .next, id: 1234)
|
|
self.isLoading = false
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
if self.isLoading {
|
|
ProgressView()
|
|
}
|
|
}
|
|
.popover(isPresented: $showDateNavPopover) {
|
|
DateNavView(selectGroupId: self.$selectGroupId, showDateNavPopover: $showDateNavPopover) { selectedDate in
|
|
print("new selected date: " + selectedDate)
|
|
}
|
|
}
|
|
}
|
|
.frame(width: 370)
|
|
.ignoresSafeArea(edges: .bottom)
|
|
.task {
|
|
//await self.indexModel.loadData()
|
|
print(userModel)
|
|
}
|
|
}
|
|
|
|
private func addItem() {
|
|
// withAnimation {
|
|
// let newItem = Item(timestamp: Date())
|
|
// modelContext.insert(newItem)
|
|
// }
|
|
}
|
|
|
|
private func deleteItems(offsets: IndexSet) {
|
|
// withAnimation {
|
|
// for index in offsets {
|
|
// modelContext.delete(items[index])
|
|
// }
|
|
// }
|
|
}
|
|
|
|
}
|
|
|
|
extension IndexView {
|
|
|
|
// 显示剧集的列表信息
|
|
struct DramaCellView: View {
|
|
let dramaItem: IndexModel.DramaItem
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading) {
|
|
|
|
NavigationLink(destination: DetailView()) {
|
|
Text(dramaItem.title)
|
|
.font(.system(size: 20))
|
|
.foregroundColor(Color(hex: "#333333"))
|
|
}
|
|
|
|
ScrollView(.horizontal, showsIndicators: false) {
|
|
HStack(alignment: .center, spacing: 5) {
|
|
ForEach(dramaItem.episodes) { item in
|
|
VStack(alignment: .center) {
|
|
GeometryReader { geometry in
|
|
AsyncImage(url: URL(string: item.thumb)) { image in
|
|
image.resizable()
|
|
} placeholder: {
|
|
ProgressView()
|
|
}
|
|
.frame(width: geometry.frame(in: .local).width, height: 80)
|
|
.overlay {
|
|
VStack {
|
|
HStack(alignment: .center) {
|
|
Text(item.num_name)
|
|
.font(.system(size: 12))
|
|
.foregroundColor(Color(hex: "#333333"))
|
|
|
|
Spacer()
|
|
}
|
|
Spacer()
|
|
}
|
|
.padding([.top, .leading], 5)
|
|
}
|
|
}
|
|
|
|
Text(item.name)
|
|
.font(.system(size: 12))
|
|
.foregroundColor(Color(hex: "#333333"))
|
|
.lineLimit(1)
|
|
}
|
|
.frame(width: 120, height: 100)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
extension IndexView {
|
|
|
|
struct UpdateDramaCellView: View {
|
|
let showItem: IndexModel.UpdateDramaShowItem
|
|
@Binding var showDateNavPopover: Bool
|
|
|
|
var body: some View {
|
|
switch showItem.element {
|
|
case .group(let group):
|
|
Text(group.group_name)
|
|
.onTapGesture {
|
|
showDateNavPopover = true
|
|
}
|
|
case .item(let item):
|
|
VStack(alignment: .center) {
|
|
AsyncImage(url: URL(string: item.thumb)) { image in
|
|
image.resizable()
|
|
} placeholder: {
|
|
ProgressView()
|
|
}
|
|
.frame(width: 80, height: 80)
|
|
.overlay {
|
|
VStack(alignment: .leading) {
|
|
Text(item.name)
|
|
.lineLimit(1)
|
|
|
|
Text(item.status)
|
|
.lineLimit(1)
|
|
}
|
|
}
|
|
}
|
|
.frame(width: 100, height: 120)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#Preview {
|
|
IndexView()
|
|
.modelContainer(for: Item.self, inMemory: true)
|
|
}
|