fix index view

This commit is contained in:
anlicheng 2025-04-14 17:00:54 +08:00
parent 2f56bc7e74
commit 98265c72d6
4 changed files with 124 additions and 116 deletions

View File

@ -118,8 +118,8 @@ struct API {
} }
print("request url: \(request.url!.absoluteString)") print("request url: \(request.url!.absoluteString)")
let x = String(data: data, encoding: .utf8)! // let x = String(data: data, encoding: .utf8)!
print("url: \(request.url!.path()), data is: \(x)") // print("url: \(request.url!.path()), data is: \(x)")
do { do {
let result = try JSONDecoder().decode(APISuccessResponse<T>.self, from: data) let result = try JSONDecoder().decode(APISuccessResponse<T>.self, from: data)
return .result(result.result) return .result(result.result)

View File

@ -58,11 +58,18 @@ struct FlexImage: View {
} }
} }
case .local(let data): case .local(let data):
Image(uiImage: UIImage(data: data)!) if let uiImage = UIImage(data: data) {
.resizable() Image(uiImage: uiImage)
.aspectRatio(contentMode: .fill) .resizable()
.frame(width: width, height: height) .aspectRatio(contentMode: .fill)
.clipped() .frame(width: width, height: height)
.clipped()
} else {
Image(placeholder)
.resizable()
.aspectRatio(contentMode: .fill)
.clipped()
}
} }
} }

View File

@ -14,6 +14,7 @@ struct IndexMainView: View {
@AppStorage("userId") private var userId: String = Utils.defaultUserId() @AppStorage("userId") private var userId: String = Utils.defaultUserId()
@State var indexModel = IndexModel() @State var indexModel = IndexModel()
@State private var scrollID: Int? = 0
// //
@State var showPrompt: Bool = false @State var showPrompt: Bool = false
@ -62,30 +63,27 @@ struct IndexMainView: View {
// //
LazyVStack(alignment: .center, spacing: 10) { LazyVStack(alignment: .center, spacing: 10) {
ForEach(indexModel.updateDramaGroups, id: \.group_id) { group in ForEach(Array(indexModel.dramaGroupElements.enumerated()), id: \.offset) { idx, item in
DramaGroupView(group: group, model: indexModel) { switch item {
selectGroupId = group.group_id case .label(groupId: let groupId, groupName: let groupName):
indexModel.selectedDate = group.group_id DramaGroupLabelView(group_name: groupName) {
showDateNavPopover = true selectGroupId = groupId
indexModel.selectedDate = groupId
showDateNavPopover = true
}
case .item(groupId: let groupId, item: let item):
DramaGroupItemView(groupId: groupId, item: item)
.id(item.id)
} }
.contentShape(Rectangle())
} }
} }
.scrollTargetLayout()
ProgressView() ProgressView()
.background(GeometryReader { geometry in
let minY = geometry.frame(in: .global).minY
Color.clear.preference(key: FooterOffsetPreferenceKey.self, value: Int(minY))
})
} }
.frame(width: 370) .frame(width: 370)
.coordinateSpace(name: "indexScrollView") .coordinateSpace(name: "indexScrollView")
.onPreferenceChange(FooterOffsetPreferenceKey.self) { offset in .scrollPosition(id: $scrollID)
// state
DispatchQueue.main.async {
indexModel.loadMorePublisher.send((userId, offset))
}
}
.refreshable { .refreshable {
guard !self.showDateNavPopover && !self.headerRefreshing else { guard !self.showDateNavPopover && !self.headerRefreshing else {
return return
@ -100,6 +98,11 @@ struct IndexMainView: View {
} }
} }
} }
.onChange(of: scrollID) { _, newValue in
if let newValue {
indexModel.scrollIDPublisher.send((userId, newValue))
}
}
.overlay(alignment: .topTrailing) { .overlay(alignment: .topTrailing) {
HStack(alignment: .center) { HStack(alignment: .center) {
Button(action: { Button(action: {
@ -139,9 +142,6 @@ struct IndexMainView: View {
.task { .task {
await self.indexModel.loadData(userId: self.userId) await self.indexModel.loadData(userId: self.userId)
} }
.onPreferenceChange(DramaGroupElementPreferenceKey.self) { frames in
indexModel.visiblePublisher.send(frames)
}
} }
} }
@ -149,92 +149,63 @@ struct IndexMainView: View {
extension IndexMainView { extension IndexMainView {
// //
struct DramaGroupView: View { struct DramaGroupLabelView: View {
@EnvironmentObject var appNav: AppNavigation let group_name: String
let group: IndexModel.UpdateDramaGroup
let model: IndexModel
var onTap: () -> Void var onTap: () -> Void
var body: some View { var body: some View {
VStack(alignment: .center, spacing: 10) { VStack(alignment: .center, spacing: 10) {
HStack { HStack {
Spacer() Spacer()
Text(group.group_name) Text(group_name)
.font(.system(size: 18)) .font(.system(size: 18))
.fontWeight(.regular) .fontWeight(.regular)
.onTapGesture { .onTapGesture {
onTap() onTap()
} }
} }
ForEach(group.items, id: \.id) { item in
Button(action: {
appNav.append(dest: .detail(id: item.id))
}) {
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)
}
.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])
})
}
}
} }
} }
} }
// item
} struct DramaGroupItemView: View {
@EnvironmentObject var appNav: AppNavigation
// MARK: PreferenceKey let groupId: String
extension IndexMainView { let item: IndexModel.UpdateDramaGroup.Item
// , [groupId : minY]
struct DramaGroupElementPreferenceKey: PreferenceKey {
static var defaultValue: [String: CGFloat] = [:]
static func reduce(value: inout [String: CGFloat], nextValue: () -> [String: CGFloat]) { var body: some View {
value.merge(nextValue()) { $1 } 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)
struct FooterOffsetPreferenceKey: PreferenceKey { .font(.system(size: 16))
static var defaultValue: Int = 0 .foregroundColor(.white)
.lineLimit(1)
static func reduce(value: inout Int, nextValue: () -> Int) {
value += nextValue() 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 { #Preview {
IndexMainView() IndexMainView()
} }

View File

@ -49,11 +49,19 @@ final class IndexModel {
let update_dramas: [UpdateDramaGroup] let update_dramas: [UpdateDramaGroup]
let follow_num: Int let follow_num: Int
} }
enum GroupElement {
case label(groupId: String, groupName: String)
case item(groupId: String, item: UpdateDramaGroup.Item)
}
var selectedDate: String var selectedDate: String
// //
var updateDramaGroups: [UpdateDramaGroup] = [] var updateDramaGroups: [UpdateDramaGroup] = []
var dramaGroupElements: [GroupElement] = []
var follow_num: String = "0" var follow_num: String = "0"
// group_name // group_name
@ -73,13 +81,10 @@ final class IndexModel {
// //
@ObservationIgnored @ObservationIgnored
private var updateInterval = Date() private var updateInterval = Date()
// footeroffset // scrollID
@ObservationIgnored @ObservationIgnored
var loadMorePublisher = PassthroughSubject<(String, Int), Never>() var scrollIDPublisher = PassthroughSubject<(String, Int), Never>()
@ObservationIgnored
var visiblePublisher = PassthroughSubject<[String: CGFloat], Never>()
@ObservationIgnored @ObservationIgnored
private var bag = Set<AnyCancellable>() private var bag = Set<AnyCancellable>()
@ -87,16 +92,35 @@ final class IndexModel {
init() { init() {
self.selectedDate = "" self.selectedDate = ""
self.loadMorePublisher self.scrollIDPublisher
.debounce(for: 0.2, scheduler: RunLoop.main) .sink { userId, scrollID in
.dropFirst()
.sink { (userId, offset) in self.dramaGroupElements.forEach { element in
switch element {
case .label(let groupId, let groupName):
()
case .item(let groupId, let item):
if item.id == scrollID {
DispatchQueue.main.async {
self.setFixedDrameGroup(groupId: groupId)
}
}
}
}
let ids = self.dramaGroupElements.suffix(6).compactMap { element -> Int? in
switch element {
case .label(_, _):
return nil
case .item(groupId: _, item: let item):
return item.id
}
}
// //
let timeDistance = self.updateInterval.distance(to: Date()) let timeDistance = self.updateInterval.distance(to: Date())
// //
let height = Int(UIScreen.main.bounds.height) if timeDistance > 1.0 && ids.contains(scrollID) {
let distance = height - offset
if timeDistance > 1.0 && (distance >= 0 && distance < 50) {
Task { Task {
await self.loadMoreUpdateDramasTask(userId: userId, mode: .next) await self.loadMoreUpdateDramasTask(userId: userId, mode: .next)
} }
@ -104,19 +128,6 @@ final class IndexModel {
} }
} }
.store(in: &bag) .store(in: &bag)
self.visiblePublisher
.debounce(for: 0.05, scheduler: RunLoop.main)
.dropFirst()
.sink { frames in
let visibleFrames = frames.filter { $0.value >= 0}
if let minFrame = visibleFrames.min(by: { $0.value <= $1.value}) {
DispatchQueue.main.async {
self.setFixedDrameGroup(groupId: minFrame.key)
}
}
}
.store(in: &bag)
} }
func loadData(userId: String) async { func loadData(userId: String) async {
@ -133,6 +144,8 @@ final class IndexModel {
await MainActor.run { await MainActor.run {
self.updateDramaGroups = result.update_dramas self.updateDramaGroups = result.update_dramas
self.dramaGroupElements = transformUpdateDramaGroups(groups: result.update_dramas)
self.fixedDramaGroup = result.update_dramas.first self.fixedDramaGroup = result.update_dramas.first
self.follow_num = result.follow_num >= 100 ? "99+" : "\(result.follow_num)" self.follow_num = result.follow_num >= 100 ? "99+" : "\(result.follow_num)"
} }
@ -169,6 +182,7 @@ final class IndexModel {
displayDramaGroups(self.updateDramaGroups, label: "before") displayDramaGroups(self.updateDramaGroups, label: "before")
await MainActor.run { await MainActor.run {
self.updateDramaGroups = preappendMergeDramaGroups(groups: self.updateDramaGroups, mergeGroups: groups) self.updateDramaGroups = preappendMergeDramaGroups(groups: self.updateDramaGroups, mergeGroups: groups)
self.dramaGroupElements = transformUpdateDramaGroups(groups: self.updateDramaGroups)
} }
displayDramaGroups(self.updateDramaGroups, label: "after") displayDramaGroups(self.updateDramaGroups, label: "after")
} }
@ -186,6 +200,7 @@ final class IndexModel {
displayDramaGroups(self.updateDramaGroups, label: "before") displayDramaGroups(self.updateDramaGroups, label: "before")
await MainActor.run { await MainActor.run {
self.updateDramaGroups = appendMergeDramaGroups(groups: self.updateDramaGroups, mergeGroups: groups) self.updateDramaGroups = appendMergeDramaGroups(groups: self.updateDramaGroups, mergeGroups: groups)
self.dramaGroupElements = transformUpdateDramaGroups(groups: self.updateDramaGroups)
} }
displayDramaGroups(self.updateDramaGroups, label: "after") displayDramaGroups(self.updateDramaGroups, label: "after")
@ -197,15 +212,30 @@ final class IndexModel {
} }
} }
//
private func transformUpdateDramaGroups(groups: [UpdateDramaGroup]) -> [GroupElement] {
var groupElements: [GroupElement] = []
for group in groups {
groupElements.append(.label(groupId: group.group_id, groupName: group.group_name))
for item in group.items {
groupElements.append(.item(groupId: group.group_id, item: item))
}
}
return groupElements
}
// //
func loadDateUpdateDramas(userId: String, date: String) async { func loadDateUpdateDramas(userId: String, date: String) async {
self.updateDramaGroups.removeAll() self.updateDramaGroups.removeAll()
self.dramaGroupElements.removeAll()
let response = await API.loadDateUpdateDramas(userId: userId, date: date, as: [UpdateDramaGroup].self) let response = await API.loadDateUpdateDramas(userId: userId, date: date, as: [UpdateDramaGroup].self)
if case let .result(groups) = response { if case let .result(groups) = response {
preloadGroupImages(groups: groups) preloadGroupImages(groups: groups)
await MainActor.run { await MainActor.run {
self.updateDramaGroups = groups self.updateDramaGroups = groups
self.dramaGroupElements = transformUpdateDramaGroups(groups: self.updateDramaGroups)
self.fixedDramaGroup = groups.first self.fixedDramaGroup = groups.first
} }
} }