Files
legado-ios/App/Features/Activity/ReadingHistoryView.swift
T

141 lines
6.1 KiB
Swift

import SwiftUI
struct ReadingHistoryView: View {
@EnvironmentObject private var activity: ReadingActivityModel
@EnvironmentObject private var books: BookLibraryModel
@EnvironmentObject private var sources: SourceLibraryModel
@AppStorage("enableReadRecord") private var enableReadRecord = true
@AppStorage("readRecordSort") private var sortMode = 0
@State private var searchText = ""
@State private var confirmsClear = false
@State private var pendingDelete: ReadRecordSummary?
private var displayedRecords: [ReadRecordSummary] {
activity.summaries(searchKey: searchText, sortMode: sortMode)
}
var body: some View {
Group {
if activity.summaries.isEmpty {
ReadingUnavailableView(
"还没有阅读记录",
systemImage: "clock.arrow.circlepath",
description: Text(enableReadRecord ? "翻页后会按设备累计阅读时长。" : "阅读记录当前已关闭。")
)
} else {
List {
Section {
ReadingLabeledContent("累计阅读", value: duration(activity.summaries.reduce(0) { $0 + $1.readTime }))
ReadingLabeledContent("读过", value: "\(activity.summaries.count) 本")
}
if displayedRecords.isEmpty {
ReadingUnavailableView.search(text: searchText)
} else {
ForEach(displayedRecords) { record in
NavigationLink {
destination(for: record)
} label: {
HStack(spacing: 13) {
Image(systemName: "book.pages.fill")
.foregroundStyle(ReadingTheme.accent)
.frame(width: 38, height: 38)
.background(ReadingTheme.accentSoft.opacity(0.55), in: RoundedRectangle(cornerRadius: 11))
VStack(alignment: .leading, spacing: 4) {
Text(record.bookName).font(.headline)
Text(duration(record.readTime))
.font(.caption).foregroundStyle(.secondary)
}
Spacer()
Text(date(record.lastRead)).font(.caption).foregroundStyle(.tertiary)
}
}
.swipeActions {
Button("删除", role: .destructive) { pendingDelete = record }
}
}
}
}
.listStyle(.insetGrouped)
}
}
.navigationTitle("阅读记录")
.searchable(text: $searchText, prompt: "搜索")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Menu("阅读记录选项", systemImage: "ellipsis.circle") {
Toggle("记录阅读时长", isOn: $enableReadRecord)
Section("排序") {
sortButton("书名", value: 0)
sortButton("阅读时长", value: 1)
sortButton("最后阅读", value: 2)
}
if !activity.summaries.isEmpty {
Button("清空", systemImage: "trash", role: .destructive) { confirmsClear = true }
}
}
}
}
.alert("清空阅读记录", isPresented: $confirmsClear) {
Button("取消", role: .cancel) {}
Button("删除", role: .destructive) { Task { await activity.clearHistory() } }
} message: {
Text("确定删除全部阅读记录吗?")
}
.alert("删除阅读记录", isPresented: Binding(
get: { pendingDelete != nil },
set: { if !$0 { pendingDelete = nil } }
)) {
Button("取消", role: .cancel) { pendingDelete = nil }
Button("删除", role: .destructive) {
guard let record = pendingDelete else { return }
pendingDelete = nil
Task { await activity.removeHistory(bookName: record.bookName) }
}
} message: {
Text("确定删除“\(pendingDelete?.bookName ?? "")”的阅读记录吗?")
}
}
@ViewBuilder private func destination(for record: ReadRecordSummary) -> some View {
if let book = books.books.first(where: { $0.name == record.bookName }) {
BookshelfReadView(
book: book,
source: sources.sources.first(where: { $0.bookSourceUrl == book.origin })
)
} else {
SearchView(initialKeyword: record.bookName)
}
}
@ViewBuilder private func sortButton(_ title: String, value: Int) -> some View {
Button {
sortMode = value
} label: {
if sortMode == value { Label(title, systemImage: "checkmark") }
else { Text(title) }
}
}
private func duration(_ milliseconds: Int64) -> String {
let value = max(milliseconds, 0)
let days = value / 86_400_000
let hours = value % 86_400_000 / 3_600_000
let minutes = value % 3_600_000 / 60_000
let seconds = value % 60_000 / 1_000
var result = ""
if days > 0 { result += "\(days)天" }
if hours > 0 { result += "\(hours)小时" }
if minutes > 0 { result += "\(minutes)分钟" }
if seconds > 0 { result += "\(seconds)秒" }
return result.isEmpty ? "0秒" : result
}
private func date(_ milliseconds: Int64) -> String {
let formatter = DateFormatter()
guard milliseconds > 0 else { return "" }
formatter.locale = .current
formatter.dateFormat = "yyyy-MM-dd"
return formatter.string(from: Date(timeIntervalSince1970: Double(milliseconds) / 1_000))
}
}