fix index view
This commit is contained in:
parent
2f56bc7e74
commit
98265c72d6
@ -118,8 +118,8 @@ struct API {
|
||||
}
|
||||
|
||||
print("request url: \(request.url!.absoluteString)")
|
||||
let x = String(data: data, encoding: .utf8)!
|
||||
print("url: \(request.url!.path()), data is: \(x)")
|
||||
// let x = String(data: data, encoding: .utf8)!
|
||||
// print("url: \(request.url!.path()), data is: \(x)")
|
||||
do {
|
||||
let result = try JSONDecoder().decode(APISuccessResponse<T>.self, from: data)
|
||||
return .result(result.result)
|
||||
|
||||
@ -58,11 +58,18 @@ struct FlexImage: View {
|
||||
}
|
||||
}
|
||||
case .local(let data):
|
||||
Image(uiImage: UIImage(data: data)!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: width, height: height)
|
||||
.clipped()
|
||||
if let uiImage = UIImage(data: data) {
|
||||
Image(uiImage: uiImage)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: width, height: height)
|
||||
.clipped()
|
||||
} else {
|
||||
Image(placeholder)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.clipped()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ struct IndexMainView: View {
|
||||
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
|
||||
|
||||
@State var indexModel = IndexModel()
|
||||
@State private var scrollID: Int? = 0
|
||||
|
||||
// 提示信息
|
||||
@State var showPrompt: Bool = false
|
||||
@ -62,30 +63,27 @@ struct IndexMainView: View {
|
||||
|
||||
// 基于日期的更新列表
|
||||
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
|
||||
ForEach(Array(indexModel.dramaGroupElements.enumerated()), id: \.offset) { idx, item in
|
||||
switch item {
|
||||
case .label(groupId: let groupId, groupName: let groupName):
|
||||
DramaGroupLabelView(group_name: groupName) {
|
||||
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()
|
||||
.background(GeometryReader { geometry in
|
||||
let minY = geometry.frame(in: .global).minY
|
||||
Color.clear.preference(key: FooterOffsetPreferenceKey.self, value: Int(minY))
|
||||
})
|
||||
}
|
||||
.frame(width: 370)
|
||||
.coordinateSpace(name: "indexScrollView")
|
||||
.onPreferenceChange(FooterOffsetPreferenceKey.self) { offset in
|
||||
// 延迟到当前帧结束更新state的值,避免导致循环更新的警告
|
||||
DispatchQueue.main.async {
|
||||
indexModel.loadMorePublisher.send((userId, offset))
|
||||
}
|
||||
}
|
||||
.scrollPosition(id: $scrollID)
|
||||
.refreshable {
|
||||
guard !self.showDateNavPopover && !self.headerRefreshing else {
|
||||
return
|
||||
@ -100,6 +98,11 @@ struct IndexMainView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: scrollID) { _, newValue in
|
||||
if let newValue {
|
||||
indexModel.scrollIDPublisher.send((userId, newValue))
|
||||
}
|
||||
}
|
||||
.overlay(alignment: .topTrailing) {
|
||||
HStack(alignment: .center) {
|
||||
Button(action: {
|
||||
@ -139,9 +142,6 @@ struct IndexMainView: View {
|
||||
.task {
|
||||
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 {
|
||||
|
||||
// 显示分组信息
|
||||
struct DramaGroupView: View {
|
||||
@EnvironmentObject var appNav: AppNavigation
|
||||
|
||||
let group: IndexModel.UpdateDramaGroup
|
||||
let model: IndexModel
|
||||
|
||||
struct DramaGroupLabelView: View {
|
||||
let group_name: String
|
||||
var onTap: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .center, spacing: 10) {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text(group.group_name)
|
||||
Text(group_name)
|
||||
.font(.system(size: 18))
|
||||
.fontWeight(.regular)
|
||||
.onTapGesture {
|
||||
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
|
||||
extension IndexMainView {
|
||||
|
||||
// 元素的坐标的距离变化, [groupId : minY]
|
||||
struct DramaGroupElementPreferenceKey: PreferenceKey {
|
||||
static var defaultValue: [String: CGFloat] = [:]
|
||||
let groupId: String
|
||||
let item: IndexModel.UpdateDramaGroup.Item
|
||||
|
||||
static func reduce(value: inout [String: CGFloat], nextValue: () -> [String: CGFloat]) {
|
||||
value.merge(nextValue()) { $1 }
|
||||
}
|
||||
}
|
||||
|
||||
// 底部的元素的坐标信息变化
|
||||
struct FooterOffsetPreferenceKey: PreferenceKey {
|
||||
static var defaultValue: Int = 0
|
||||
|
||||
static func reduce(value: inout Int, nextValue: () -> Int) {
|
||||
value += nextValue()
|
||||
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()
|
||||
}
|
||||
|
||||
@ -49,11 +49,19 @@ final class IndexModel {
|
||||
let update_dramas: [UpdateDramaGroup]
|
||||
let follow_num: Int
|
||||
}
|
||||
|
||||
|
||||
enum GroupElement {
|
||||
case label(groupId: String, groupName: String)
|
||||
case item(groupId: String, item: UpdateDramaGroup.Item)
|
||||
}
|
||||
|
||||
var selectedDate: String
|
||||
|
||||
// 保存原始的更新数据
|
||||
var updateDramaGroups: [UpdateDramaGroup] = []
|
||||
|
||||
var dramaGroupElements: [GroupElement] = []
|
||||
|
||||
var follow_num: String = "0"
|
||||
|
||||
// 用来显示固定栏目的group_name
|
||||
@ -73,13 +81,10 @@ final class IndexModel {
|
||||
// 用来追踪最近的更新时间
|
||||
@ObservationIgnored
|
||||
private var updateInterval = Date()
|
||||
|
||||
// 用来记录追踪当前footer的offset的变化
|
||||
|
||||
// 用来追踪scrollID的变化
|
||||
@ObservationIgnored
|
||||
var loadMorePublisher = PassthroughSubject<(String, Int), Never>()
|
||||
|
||||
@ObservationIgnored
|
||||
var visiblePublisher = PassthroughSubject<[String: CGFloat], Never>()
|
||||
var scrollIDPublisher = PassthroughSubject<(String, Int), Never>()
|
||||
|
||||
@ObservationIgnored
|
||||
private var bag = Set<AnyCancellable>()
|
||||
@ -87,16 +92,35 @@ final class IndexModel {
|
||||
init() {
|
||||
self.selectedDate = ""
|
||||
|
||||
self.loadMorePublisher
|
||||
.debounce(for: 0.2, scheduler: RunLoop.main)
|
||||
.dropFirst()
|
||||
.sink { (userId, offset) in
|
||||
self.scrollIDPublisher
|
||||
.sink { userId, scrollID 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 height = Int(UIScreen.main.bounds.height)
|
||||
let distance = height - offset
|
||||
if timeDistance > 1.0 && (distance >= 0 && distance < 50) {
|
||||
if timeDistance > 1.0 && ids.contains(scrollID) {
|
||||
Task {
|
||||
await self.loadMoreUpdateDramasTask(userId: userId, mode: .next)
|
||||
}
|
||||
@ -104,19 +128,6 @@ final class IndexModel {
|
||||
}
|
||||
}
|
||||
.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 {
|
||||
@ -133,6 +144,8 @@ final class IndexModel {
|
||||
|
||||
await MainActor.run {
|
||||
self.updateDramaGroups = result.update_dramas
|
||||
self.dramaGroupElements = transformUpdateDramaGroups(groups: result.update_dramas)
|
||||
|
||||
self.fixedDramaGroup = result.update_dramas.first
|
||||
self.follow_num = result.follow_num >= 100 ? "99+" : "\(result.follow_num)"
|
||||
}
|
||||
@ -169,6 +182,7 @@ final class IndexModel {
|
||||
displayDramaGroups(self.updateDramaGroups, label: "before")
|
||||
await MainActor.run {
|
||||
self.updateDramaGroups = preappendMergeDramaGroups(groups: self.updateDramaGroups, mergeGroups: groups)
|
||||
self.dramaGroupElements = transformUpdateDramaGroups(groups: self.updateDramaGroups)
|
||||
}
|
||||
displayDramaGroups(self.updateDramaGroups, label: "after")
|
||||
}
|
||||
@ -186,6 +200,7 @@ final class IndexModel {
|
||||
displayDramaGroups(self.updateDramaGroups, label: "before")
|
||||
await MainActor.run {
|
||||
self.updateDramaGroups = appendMergeDramaGroups(groups: self.updateDramaGroups, mergeGroups: groups)
|
||||
self.dramaGroupElements = transformUpdateDramaGroups(groups: self.updateDramaGroups)
|
||||
}
|
||||
|
||||
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 {
|
||||
self.updateDramaGroups.removeAll()
|
||||
self.dramaGroupElements.removeAll()
|
||||
|
||||
let response = await API.loadDateUpdateDramas(userId: userId, date: date, as: [UpdateDramaGroup].self)
|
||||
if case let .result(groups) = response {
|
||||
preloadGroupImages(groups: groups)
|
||||
|
||||
await MainActor.run {
|
||||
self.updateDramaGroups = groups
|
||||
self.dramaGroupElements = transformUpdateDramaGroups(groups: self.updateDramaGroups)
|
||||
self.fixedDramaGroup = groups.first
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user