diff --git a/dimensionhub/Assets.xcassets/lost_network.imageset/Contents.json b/dimensionhub/Assets.xcassets/lost_network.imageset/Contents.json new file mode 100644 index 0000000..f6cb6e4 --- /dev/null +++ b/dimensionhub/Assets.xcassets/lost_network.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "lost.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/dimensionhub/Assets.xcassets/lost_network.imageset/lost.jpg b/dimensionhub/Assets.xcassets/lost_network.imageset/lost.jpg new file mode 100644 index 0000000..4989112 Binary files /dev/null and b/dimensionhub/Assets.xcassets/lost_network.imageset/lost.jpg differ diff --git a/dimensionhub/Views/DateNavView.swift b/dimensionhub/Views/DateNavView.swift index a34827a..d116b04 100644 --- a/dimensionhub/Views/DateNavView.swift +++ b/dimensionhub/Views/DateNavView.swift @@ -48,7 +48,7 @@ final class DateNavModel { } else { let response = await API.getDateIndex(userId: userId, as: [DateModel].self) switch response { - case .error(let code, let message): + case .error(_, _): return [] case .result(let models): await DataCache.shared.setDateModelCache(models) diff --git a/dimensionhub/Views/IndexView.swift b/dimensionhub/Views/IndexView.swift index 8a26c62..35f3ddd 100644 --- a/dimensionhub/Views/IndexView.swift +++ b/dimensionhub/Views/IndexView.swift @@ -8,6 +8,7 @@ import SwiftUI import SwiftData import Observation +import Network @Observable final class IndexModel { @@ -194,134 +195,214 @@ final class IndexModel { } struct IndexView: 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 + // 网络状态检测, 第一次进入app时,如果网络没有授权,网络请求会失败 + enum NetworkStatus { + case satisfied + case unsatisfied + } + @State private var networkStatus: NetworkStatus = .satisfied - // 提示信息 - @State var showPrompt: Bool = false - @State var promptMessage: String = "" - - // 是否显示日期弹出层 - @State private var selectGroupId: String = "" - @State private var showDateNavPopover: Bool = false + private static let queue = DispatchQueue(label: "NetworkMonitorQueue") 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.updateDramaGroups, id: \.group_id) { group in - DramaGroupView(group: group) { - 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("scrollView")) - - if screenBounds.height - frame.minY > 50 && contextFrame.minY > 0 && !isMoreLoading { - Task { - self.isMoreLoading = true - let result = await self.indexModel.loadMoreUpdateDramas(userId: self.userId, mode: .next) - switch result { - case .success: - () - case .error(let message): - DispatchQueue.main.async { - self.showPrompt = true - self.promptMessage = message - } - } - self.isMoreLoading = false - } - } - } - }) - - if self.isMoreLoading { - ProgressView() + ZStack { + switch self.networkStatus { + case .unsatisfied: + IndexExceptionView { + self.checkNetworkStatus() } + case .satisfied: + IndexMainView() } - .coordinateSpace(name: "scrollView") - .popover(isPresented: $showDateNavPopover) { - DateNavView(selectGroupId: self.$selectGroupId, showDateNavPopover: $showDateNavPopover) { selectedDate in - Task { - await indexModel.loadDateUpdateDramas(userId: self.userId, date: selectedDate) - } - } - } - .refreshable { - guard !self.isPrevLoading else { - return - } - - // 上拉刷新功能 - self.isPrevLoading = true - let result = await self.indexModel.loadMoreUpdateDramas(userId: self.userId, mode: .prev) - switch result { - case .success: - () - case .error(let message): - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - self.showPrompt = true - self.promptMessage = message - } - } - self.isPrevLoading = false - } - } - .frame(width: 370) - .ignoresSafeArea(edges: .bottom) - .alert(isPresented: $showPrompt) { - Alert(title: Text("提示"), message: Text(self.promptMessage), dismissButton: .default(Text("OK"))) - } - .task { - await self.indexModel.loadData(userId: self.userId) + .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 { + 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.updateDramaGroups, id: \.group_id) { group in + DramaGroupView(group: group) { + 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("scrollView")) + + if screenBounds.height - frame.minY > 50 && contextFrame.minY > 0 && !isMoreLoading { + Task { + self.isMoreLoading = true + let result = await self.indexModel.loadMoreUpdateDramas(userId: self.userId, mode: .next) + switch result { + case .success: + () + case .error(let message): + DispatchQueue.main.async { + self.showPrompt = true + self.promptMessage = message + } + } + self.isMoreLoading = false + } + } + } + }) + + if self.isMoreLoading { + ProgressView() + } + } + .coordinateSpace(name: "scrollView") + .popover(isPresented: $showDateNavPopover) { + DateNavView(selectGroupId: self.$selectGroupId, showDateNavPopover: $showDateNavPopover) { selectedDate in + Task { + await indexModel.loadDateUpdateDramas(userId: self.userId, date: selectedDate) + } + } + } + .refreshable { + guard !self.isPrevLoading else { + return + } + + // 上拉刷新功能 + self.isPrevLoading = true + let result = await self.indexModel.loadMoreUpdateDramas(userId: self.userId, mode: .prev) + switch result { + case .success: + () + case .error(let message): + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.showPrompt = true + self.promptMessage = message + } + } + self.isPrevLoading = false + } + + } + .frame(width: 370) + .ignoresSafeArea(edges: .bottom) + .alert(isPresented: $showPrompt) { + Alert(title: Text("提示"), message: Text(self.promptMessage), dismissButton: .default(Text("OK"))) + } + .task { + await self.indexModel.loadData(userId: self.userId) + } + } + } + // 显示剧集的列表信息 struct DramaCellView: View { let dramaItem: IndexModel.DramaItem @@ -379,7 +460,6 @@ extension IndexView { } } - // 显示分组信息 struct DramaGroupView: View { let group: IndexModel.UpdateDramaGroup