单词详情页新增历史记录入口,可查看单个单词的学习统计
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,10 @@ final class LearnedStore {
|
||||
}
|
||||
}
|
||||
|
||||
func record(for word: Word) -> LearnedRecord? {
|
||||
records.first { $0.word.id == word.id }
|
||||
}
|
||||
|
||||
func isRecorded(_ word: Word) -> Bool {
|
||||
records.contains { $0.word.id == word.id }
|
||||
}
|
||||
|
||||
@@ -31,7 +31,10 @@ final class WordDetailViewController: UIViewController {
|
||||
view.backgroundColor = .systemBackground
|
||||
title = categoryName
|
||||
navigationItem.largeTitleDisplayMode = .never
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "学习", style: .plain, target: self, action: #selector(startSpelling))
|
||||
navigationItem.rightBarButtonItems = [
|
||||
UIBarButtonItem(image: UIImage(systemName: "clock.arrow.circlepath"), style: .plain, target: self, action: #selector(showHistory)),
|
||||
UIBarButtonItem(title: "学习", style: .plain, target: self, action: #selector(startSpelling))
|
||||
]
|
||||
buildLayout()
|
||||
updateSpeakState()
|
||||
speechObserver = NotificationCenter.default.addObserver(
|
||||
@@ -187,6 +190,10 @@ final class WordDetailViewController: UIViewController {
|
||||
return card
|
||||
}
|
||||
|
||||
@objc private func showHistory() {
|
||||
navigationController?.pushViewController(WordHistoryViewController(word: word), animated: true)
|
||||
}
|
||||
|
||||
@objc private func startSpelling() {
|
||||
Haptics.selection()
|
||||
let config = QuizConfig(mode: .spelling, words: [word], isStudyMode: true)
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import UIKit
|
||||
|
||||
final class WordHistoryViewController: UIViewController {
|
||||
private let word: Word
|
||||
private let record: LearnedRecord?
|
||||
|
||||
init(word: Word) {
|
||||
self.word = word
|
||||
record = LearnedStore.shared.record(for: word)
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) { fatalError() }
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
title = "学习记录"
|
||||
navigationItem.largeTitleDisplayMode = .never
|
||||
view.backgroundColor = .systemBackground
|
||||
buildLayout()
|
||||
}
|
||||
|
||||
private func buildLayout() {
|
||||
guard let record else {
|
||||
let label = UILabel()
|
||||
label.text = "暂无学习记录"
|
||||
label.textAlignment = .center
|
||||
label.textColor = .secondaryLabel
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
view.addSubview(label)
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||
])
|
||||
return
|
||||
}
|
||||
|
||||
let wordCard = makeWordCard(record)
|
||||
let statsCard = makeStatsCard(record)
|
||||
|
||||
let stack = UIStackView(arrangedSubviews: [wordCard, statsCard])
|
||||
stack.axis = .vertical
|
||||
stack.spacing = 20
|
||||
|
||||
let scrollView = UIScrollView()
|
||||
scrollView.alwaysBounceVertical = true
|
||||
view.addSubview(scrollView)
|
||||
scrollView.addSubview(stack)
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
||||
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
stack.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 20),
|
||||
stack.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: 16),
|
||||
stack.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -16),
|
||||
stack.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -16),
|
||||
stack.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor, constant: -32)
|
||||
])
|
||||
}
|
||||
|
||||
private func makeWordCard(_ record: LearnedRecord) -> UIView {
|
||||
let card = UIView()
|
||||
card.backgroundColor = Theme.Color.cardBackground
|
||||
card.layer.cornerRadius = Theme.Metrics.cardRadius
|
||||
|
||||
let wordLabel = UILabel()
|
||||
wordLabel.text = record.word.word
|
||||
wordLabel.font = .preferredFont(forTextStyle: .headline)
|
||||
|
||||
let phoneticLabel = UILabel()
|
||||
phoneticLabel.text = record.word.phonetic
|
||||
phoneticLabel.font = .preferredFont(forTextStyle: .caption1)
|
||||
phoneticLabel.textColor = .secondaryLabel
|
||||
|
||||
let meaningLabel = UILabel()
|
||||
meaningLabel.text = record.word.meaning
|
||||
meaningLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
meaningLabel.textColor = .secondaryLabel
|
||||
|
||||
let textStack = UIStackView(arrangedSubviews: [wordLabel, phoneticLabel, meaningLabel])
|
||||
textStack.axis = .vertical
|
||||
textStack.spacing = 8
|
||||
|
||||
card.addSubview(textStack)
|
||||
textStack.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
textStack.topAnchor.constraint(equalTo: card.topAnchor, constant: 14),
|
||||
textStack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 14),
|
||||
textStack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -14),
|
||||
textStack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -14)
|
||||
])
|
||||
return card
|
||||
}
|
||||
|
||||
private func makeStatsCard(_ record: LearnedRecord) -> UIView {
|
||||
let card = UIView()
|
||||
card.backgroundColor = Theme.Color.cardBackground
|
||||
card.layer.cornerRadius = Theme.Metrics.cardRadius
|
||||
|
||||
let title = UILabel()
|
||||
title.text = "学习统计"
|
||||
title.font = .preferredFont(forTextStyle: .caption1)
|
||||
title.textColor = .secondaryLabel
|
||||
|
||||
let countLabel = UILabel()
|
||||
countLabel.text = "\(record.count) 次"
|
||||
countLabel.font = .systemFont(ofSize: 36, weight: .bold)
|
||||
|
||||
let accuracyLabel = UILabel()
|
||||
accuracyLabel.text = "正确率 \(record.accuracy)%"
|
||||
accuracyLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
accuracyLabel.textColor = Theme.Color.correct
|
||||
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "zh_CN")
|
||||
formatter.dateFormat = "yyyy-MM-dd HH:mm"
|
||||
let dateLabel = UILabel()
|
||||
dateLabel.text = "最近学习:\(formatter.string(from: record.learnedAt))"
|
||||
dateLabel.font = .preferredFont(forTextStyle: .caption1)
|
||||
dateLabel.textColor = .tertiaryLabel
|
||||
|
||||
let statsStack = UIStackView(arrangedSubviews: [countLabel, accuracyLabel])
|
||||
statsStack.axis = .horizontal
|
||||
statsStack.spacing = 16
|
||||
statsStack.alignment = .lastBaseline
|
||||
|
||||
let stack = UIStackView(arrangedSubviews: [title, statsStack, dateLabel])
|
||||
stack.axis = .vertical
|
||||
stack.spacing = 12
|
||||
|
||||
card.addSubview(stack)
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
stack.topAnchor.constraint(equalTo: card.topAnchor, constant: 16),
|
||||
stack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
|
||||
stack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -16),
|
||||
stack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -16)
|
||||
])
|
||||
return card
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user