add favor view
This commit is contained in:
parent
cfcf59502d
commit
d8dae2fa3c
@ -36,6 +36,7 @@ struct API {
|
||||
// 服务器地址
|
||||
static let baseUrl = "https://dimensionhub.s5s8.com"
|
||||
|
||||
// 获取首页的数据
|
||||
static func getIndexData<T: Codable>(userId: String, as: T.Type) async -> APIResponse<T> {
|
||||
let request = URLRequest(url: URL(string: baseUrl + "/api/index?user_id=\(userId)")!)
|
||||
|
||||
@ -49,6 +50,13 @@ struct API {
|
||||
return await doRequest(request: request, as: T.self)
|
||||
}
|
||||
|
||||
// 获取用户收藏的数据列表
|
||||
static func getFavorData<T: Codable>(userId: String, id: Int, as: T.Type) async -> APIResponse<T> {
|
||||
let request = URLRequest(url: URL(string: baseUrl + "/api/favor?user_id=\(userId)&id=\(id)")!)
|
||||
|
||||
return await doRequest(request: request, as: T.self)
|
||||
}
|
||||
|
||||
// 指定时间索引
|
||||
static func loadDateUpdateDramas<T: Codable>(userId: String, date: String, as: T.Type) async -> APIResponse<T> {
|
||||
let request = URLRequest(url: URL(string: baseUrl + "/api/load_date_dramas?user_id=\(userId)&date=\(date)")!)
|
||||
|
||||
235
dimensionhub/Views/FavorView.swift
Normal file
235
dimensionhub/Views/FavorView.swift
Normal file
@ -0,0 +1,235 @@
|
||||
//
|
||||
// FavorView.swift
|
||||
// dimensionhub
|
||||
//
|
||||
// Created by 安礼成 on 2025/3/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import Observation
|
||||
|
||||
@Observable
|
||||
final class FavorModel {
|
||||
|
||||
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 FavorResponse: Codable {
|
||||
let dramas: [DramaItem]
|
||||
let has_more: Bool
|
||||
}
|
||||
|
||||
var dramas: [DramaItem]
|
||||
|
||||
// 标记是否还有新数据,避免空请求
|
||||
@ObservationIgnored
|
||||
private var hasMore: Bool
|
||||
|
||||
init() {
|
||||
self.dramas = []
|
||||
self.hasMore = true
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func loadData(userId: String) async {
|
||||
let response = await API.getFavorData(userId: userId, id: 0, as: FavorResponse.self)
|
||||
switch response {
|
||||
case .error(let code, let message):
|
||||
print("index load data get error_code: \(code), message: \(message)")
|
||||
case .result(let result):
|
||||
self.dramas = result.dramas
|
||||
self.hasMore = result.has_more
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func loadMoreFavorDramas(userId: String) async {
|
||||
guard self.hasMore else {
|
||||
return
|
||||
}
|
||||
|
||||
let lastId = self.dramas.last?.id ?? 0
|
||||
let response = await API.getFavorData(userId: userId, id: lastId, as: FavorResponse.self)
|
||||
switch response {
|
||||
case .result(let result):
|
||||
self.dramas.append(contentsOf: result.dramas)
|
||||
self.hasMore = result.has_more
|
||||
case .error(let code, let string):
|
||||
print("load more favor get error code: \(code), message: \(string)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FavorView: View {
|
||||
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
|
||||
|
||||
@State var favorModel = FavorModel()
|
||||
@State var isMoreLoading: Bool = false
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .center) {
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Color.clear
|
||||
.overlay {
|
||||
HStack(alignment: .center) {
|
||||
Text("亚次元")
|
||||
.font(.system(size: 16))
|
||||
.padding([.top, .bottom], 5)
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
Text("♡ 12")
|
||||
.font(.system(size: 16))
|
||||
.padding([.top, .bottom], 5)
|
||||
}
|
||||
}
|
||||
.padding([.leading, .trailing], 15)
|
||||
}
|
||||
}
|
||||
.frame(height: 50)
|
||||
.background(Color(hex: "#F2F2F2"), ignoresSafeAreaEdges: .top)
|
||||
|
||||
VStack(alignment: .center) {
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
HStack(alignment: .center) {
|
||||
Spacer()
|
||||
Text("番剧补完计划")
|
||||
.font(.system(size: 24))
|
||||
.foregroundColor(Color(hex: "#999999"))
|
||||
}
|
||||
|
||||
ForEach(favorModel.dramas, id: \.id) { drama in
|
||||
DramaCellView(dramaItem: drama)
|
||||
}
|
||||
|
||||
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.favorModel.loadMoreFavorDramas(userId: userId)
|
||||
self.isMoreLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if self.isMoreLoading {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
.coordinateSpace(name: "indexScrollView")
|
||||
}
|
||||
.frame(width: 370)
|
||||
}
|
||||
.ignoresSafeArea(edges: .bottom)
|
||||
.task {
|
||||
await self.favorModel.loadData(userId: self.userId)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension FavorView {
|
||||
// 显示剧集的列表信息
|
||||
struct DramaCellView: View {
|
||||
let dramaItem: FavorModel.DramaItem
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
NavigationLink(destination: DetailView(id: dramaItem.id)) {
|
||||
Text(dramaItem.title)
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(Color(hex: "#333333"))
|
||||
}
|
||||
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
LazyHStack(alignment: .center, spacing: 5) {
|
||||
ForEach(dramaItem.episodes) { item in
|
||||
VStack(alignment: .center) {
|
||||
GeometryReader { geometry in
|
||||
|
||||
AsyncImage(url: URL(string: item.thumb)) { phase in
|
||||
switch phase {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: geometry.frame(in: .local).width, height: 80)
|
||||
.clipped()
|
||||
default:
|
||||
Image("ph_img_medium")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: geometry.frame(in: .local).width, height: 80)
|
||||
.clipped()
|
||||
}
|
||||
}
|
||||
.frame(width: geometry.frame(in: .local).width, height: 80)
|
||||
.overlay(alignment: .topLeading) {
|
||||
if !item.num_name.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text(item.num_name)
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(3)
|
||||
.background(
|
||||
Color.black.opacity(0.6)
|
||||
)
|
||||
.cornerRadius(3)
|
||||
.padding(3)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(item.name)
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(Color(hex: "#333333"))
|
||||
.lineLimit(1)
|
||||
}
|
||||
.frame(width: 120, height: 100)
|
||||
.onTapGesture {
|
||||
if let playUrl = URL(string: item.play) {
|
||||
UIApplication.shared.open(playUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -296,10 +296,12 @@ extension IndexView {
|
||||
.padding([.top, .bottom], 5)
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
Text("♡ 12")
|
||||
.font(.system(size: 16))
|
||||
.padding([.top, .bottom], 5)
|
||||
NavigationLink(destination: FavorView()) {
|
||||
HStack {
|
||||
Text("♡ 12")
|
||||
.font(.system(size: 16))
|
||||
.padding([.top, .bottom], 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding([.leading, .trailing], 15)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user