fixed some bugs
This commit is contained in:
@@ -6,6 +6,7 @@ struct RootView: View {
|
||||
@StateObject private var auth = AuthManager()
|
||||
@StateObject private var controller = PlayerController()
|
||||
@StateObject private var catalogVM = CatalogViewModel()
|
||||
@StateObject private var historyManager = TrainingHistoryManager()
|
||||
@State private var activeModule: Module = .course
|
||||
@State private var continuousPlayback = false
|
||||
@State private var showImporter = false
|
||||
@@ -14,6 +15,11 @@ struct RootView: View {
|
||||
@State private var currentCourse: Course?
|
||||
@State private var courseStatusText = "正在同步"
|
||||
@State private var showCourseGuardToast = false
|
||||
@State private var managedResults: [UserResultSummary] = []
|
||||
@State private var managedDubShares: [UserDubShareSummary] = []
|
||||
@State private var managementStatus = ""
|
||||
@State private var managementIsLoading = false
|
||||
@State private var deletingHistoryID: String?
|
||||
|
||||
enum Module {
|
||||
case course
|
||||
@@ -49,7 +55,11 @@ struct RootView: View {
|
||||
onFullscreen: enterFullscreen
|
||||
)
|
||||
case .test:
|
||||
TestView(controller: controller, onFullscreen: enterFullscreen)
|
||||
TestView(
|
||||
controller: controller,
|
||||
historyManager: historyManager,
|
||||
onFullscreen: enterFullscreen
|
||||
)
|
||||
case .mine:
|
||||
minePage
|
||||
}
|
||||
@@ -276,10 +286,195 @@ struct RootView: View {
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(Theme.textMuted)
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
HStack(spacing: 10) {
|
||||
Button {
|
||||
Task { await loadManagedHistory() }
|
||||
} label: {
|
||||
Text(managementIsLoading ? "刷新中..." : "刷新记录")
|
||||
.font(.system(size: 13, weight: .bold))
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 14)
|
||||
.frame(height: 36)
|
||||
.background(Theme.buttonBlue)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(managementIsLoading)
|
||||
|
||||
if !managementStatus.isEmpty {
|
||||
Text(managementStatus)
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(Theme.textMuted)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
historyGroup(title: "我的测试结果", isEmpty: managedResults.isEmpty) {
|
||||
ForEach(managedResults) { row in
|
||||
resultRow(row)
|
||||
}
|
||||
}
|
||||
|
||||
historyGroup(title: "我的配音分享", isEmpty: managedDubShares.isEmpty) {
|
||||
ForEach(managedDubShares) { row in
|
||||
dubShareRow(row)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 24)
|
||||
}
|
||||
.task(id: currentCourse?.videoHash) {
|
||||
await loadManagedHistory()
|
||||
}
|
||||
}
|
||||
|
||||
private func historyGroup<Content: View>(
|
||||
title: String,
|
||||
isEmpty: Bool,
|
||||
@ViewBuilder content: () -> Content
|
||||
) -> some View {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text(title)
|
||||
.font(.system(size: 18, weight: .bold))
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 16)
|
||||
if isEmpty {
|
||||
Text("该课程暂无记录")
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(Theme.textMuted)
|
||||
.padding(14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Theme.surface)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
.padding(.horizontal, 16)
|
||||
} else {
|
||||
LazyVStack(spacing: 8, content: content)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func resultRow(_ row: UserResultSummary) -> some View {
|
||||
historyRow(
|
||||
title: "第 \(row.sentenceIndex + 1) 句 · \(String(format: "%.0f", row.overallScore)) 分",
|
||||
subtitle: "\(row.passed ? "通过" : "未通过") · \(displayDate(row.createdAt))\n\(row.sentenceText)",
|
||||
isDeleting: deletingHistoryID == row.id,
|
||||
deleteLabel: "删除"
|
||||
) {
|
||||
await deleteResult(row)
|
||||
}
|
||||
}
|
||||
|
||||
private func dubShareRow(_ row: UserDubShareSummary) -> some View {
|
||||
historyRow(
|
||||
title: row.title,
|
||||
subtitle: "\(row.segmentCount) 句 · 平均 \(String(format: "%.0f", row.averageScore)) 分 · \(displayDate(row.createdAt))",
|
||||
isDeleting: deletingHistoryID == row.id,
|
||||
deleteLabel: "删除"
|
||||
) {
|
||||
await deleteDubShare(row)
|
||||
}
|
||||
}
|
||||
|
||||
private func historyRow(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
isDeleting: Bool,
|
||||
deleteLabel: String,
|
||||
action: @escaping () async -> Void
|
||||
) -> some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(title)
|
||||
.font(.system(size: 15, weight: .bold))
|
||||
.foregroundColor(Theme.textDark)
|
||||
Text(subtitle)
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(Theme.textSubtle)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
HStack {
|
||||
Spacer()
|
||||
Button {
|
||||
Task { await action() }
|
||||
} label: {
|
||||
Text(isDeleting ? "删除中..." : deleteLabel)
|
||||
.font(.system(size: 12, weight: .bold))
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 12)
|
||||
.frame(height: 28)
|
||||
.background(isDeleting ? Theme.border : Theme.danger)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 7))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(isDeleting)
|
||||
}
|
||||
}
|
||||
.padding(12)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Theme.surface)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Theme.lightBorder, lineWidth: 1)
|
||||
)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
}
|
||||
|
||||
private func loadManagedHistory() async {
|
||||
guard let course = currentCourse else {
|
||||
managementStatus = "请先选择课程"
|
||||
managedResults = []
|
||||
managedDubShares = []
|
||||
return
|
||||
}
|
||||
managementIsLoading = true
|
||||
managementStatus = "正在加载..."
|
||||
defer { managementIsLoading = false }
|
||||
do {
|
||||
async let resultsTask = UserHistoryAPI.results(token: auth.token)
|
||||
async let sharesTask = UserHistoryAPI.dubShares(token: auth.token)
|
||||
let (allResults, allShares) = try await (resultsTask, sharesTask)
|
||||
managedResults = allResults.filter { $0.videoHash.caseInsensitiveCompare(course.videoHash) == .orderedSame }
|
||||
managedDubShares = allShares.filter { $0.videoHash.caseInsensitiveCompare(course.videoHash) == .orderedSame }
|
||||
managementStatus = "共 \(managedResults.count) 条测试、\(managedDubShares.count) 条配音"
|
||||
} catch {
|
||||
managementStatus = "记录加载失败:\(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteResult(_ row: UserResultSummary) async {
|
||||
guard let course = currentCourse, deletingHistoryID == nil else { return }
|
||||
deletingHistoryID = row.id
|
||||
defer { deletingHistoryID = nil }
|
||||
do {
|
||||
try await UserHistoryAPI.deleteResult(token: auth.token, attemptId: row.attemptId)
|
||||
managedResults.removeAll { $0.id == row.id }
|
||||
historyManager.store.removeScore(videoHash: course.videoHash, sentenceIndex: row.sentenceIndex)
|
||||
historyManager.notifyHistoryChanged()
|
||||
managementStatus = "测试记录已删除"
|
||||
} catch {
|
||||
managementStatus = "删除失败:\(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteDubShare(_ row: UserDubShareSummary) async {
|
||||
guard let course = currentCourse, deletingHistoryID == nil else { return }
|
||||
deletingHistoryID = row.id
|
||||
defer { deletingHistoryID = nil }
|
||||
do {
|
||||
try await UserHistoryAPI.deleteDubShare(token: auth.token, shareId: row.shareId)
|
||||
managedDubShares.removeAll { $0.id == row.id }
|
||||
historyManager.store.removeAllAudio(videoHash: course.videoHash)
|
||||
historyManager.notifyHistoryChanged()
|
||||
managementStatus = "配音分享已删除"
|
||||
} catch {
|
||||
managementStatus = "删除失败:\(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
|
||||
private func displayDate(_ value: String) -> String {
|
||||
value.replacingOccurrences(of: "T", with: " ").prefix(16).description
|
||||
}
|
||||
|
||||
// MARK: - Load Courses
|
||||
|
||||
private func loadCourses() async {
|
||||
|
||||
Reference in New Issue
Block a user