From ad58d9ea5a4449ae409b7f79554092e3a3f472ae Mon Sep 17 00:00:00 2001 From: fish Date: Tue, 21 Jul 2026 21:35:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E8=AE=B0=E5=BD=95=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=97=A5=E5=8E=86=E8=A7=86=E5=9B=BE=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=8C=89=E6=97=A5=E6=9C=9F=E5=88=87=E6=8D=A2=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=AD=A6=E4=B9=A0=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- .../Views/HistoryViewController.swift | 148 +++++++++++++----- 1 file changed, 110 insertions(+), 38 deletions(-) diff --git a/LearnEnglish/LearnEnglish/Views/HistoryViewController.swift b/LearnEnglish/LearnEnglish/Views/HistoryViewController.swift index f22aa9e..bef01b6 100644 --- a/LearnEnglish/LearnEnglish/Views/HistoryViewController.swift +++ b/LearnEnglish/LearnEnglish/Views/HistoryViewController.swift @@ -1,12 +1,22 @@ import UIKit -final class HistoryViewController: UITableViewController { - private let sections: [(title: String, items: [LearnedRecord])] +final class HistoryViewController: UIViewController { + private var selectedDate = Date() + private var selectedRecords: [LearnedRecord] = [] private var speechObserver: NSObjectProtocol? + private let calendarView = UICalendarView() + private let tableView = UITableView(frame: .zero, style: .plain) + private let emptyLabel = UILabel() + private let formatter: DateFormatter = { + let f = DateFormatter() + f.locale = Locale(identifier: "zh_CN") + f.dateFormat = "yyyy-MM-dd" + return f + }() + init() { - sections = LearnedStore.shared.sectioned - super.init(style: .plain) + super.init(nibName: nil, bundle: nil) } @available(*, unavailable) @@ -22,11 +32,11 @@ final class HistoryViewController: UITableViewController { super.viewDidLoad() title = "学习记录" navigationItem.largeTitleDisplayMode = .never - tableView.register(WordCardCell.self, forCellReuseIdentifier: "cell") - tableView.separatorStyle = .none - tableView.backgroundColor = .systemBackground - tableView.contentInset.top = 8 - updateEmptyState() + view.backgroundColor = .systemBackground + + setupCalendar() + setupTable() + loadRecords(for: selectedDate) speechObserver = NotificationCenter.default.addObserver( forName: SpeechService.speakingDidChangeNotification, object: nil, queue: .main @@ -35,45 +45,107 @@ final class HistoryViewController: UITableViewController { } } - private func updateEmptyState() { - if sections.isEmpty { - let label = UILabel() - label.text = "暂无学习记录" - label.textAlignment = .center - label.textColor = .secondaryLabel - label.font = .preferredFont(forTextStyle: .body) - let bg = UIView() - bg.addSubview(label) - label.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - label.centerXAnchor.constraint(equalTo: bg.centerXAnchor), - label.centerYAnchor.constraint(equalTo: bg.centerYAnchor) - ]) - tableView.backgroundView = bg + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + calendarView.reloadDecorations(forDateComponents: recordDates(), animated: false) + loadRecords(for: selectedDate) + } + + private func setupCalendar() { + calendarView.delegate = self + calendarView.calendar = .current + calendarView.locale = Locale(identifier: "zh_CN") + let selection = UICalendarSelectionSingleDate(delegate: self) + calendarView.selectionBehavior = selection + selection.selectedDate = dateComponents(from: selectedDate) + + view.addSubview(calendarView) + calendarView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + calendarView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + calendarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + calendarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + calendarView.heightAnchor.constraint(equalToConstant: 360) + ]) + } + + private func setupTable() { + tableView.dataSource = self + tableView.delegate = self + tableView.register(WordCardCell.self, forCellReuseIdentifier: "cell") + tableView.separatorStyle = .none + tableView.backgroundColor = .systemBackground + + emptyLabel.text = "当天暂无学习记录" + emptyLabel.textAlignment = .center + emptyLabel.textColor = .secondaryLabel + emptyLabel.font = .preferredFont(forTextStyle: .body) + emptyLabel.isHidden = true + tableView.backgroundView = emptyLabel + + view.addSubview(tableView) + tableView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint(equalTo: calendarView.bottomAnchor), + tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + } + + private func loadRecords(for date: Date) { + let dateStr = formatter.string(from: date) + let all = LearnedStore.shared.records + selectedRecords = all.filter { formatter.string(from: $0.learnedAt) == dateStr } + .sorted { $0.learnedAt > $1.learnedAt } + emptyLabel.isHidden = !selectedRecords.isEmpty + tableView.reloadData() + } + + private func recordDates() -> [DateComponents] { + LearnedStore.shared.records.map { + dateComponents(from: $0.learnedAt) } } + private func dateComponents(from date: Date) -> DateComponents { + Calendar.current.dateComponents([.year, .month, .day], from: date) + } + private func updateVisibleSpeakers() { for cell in tableView.visibleCells.compactMap({ $0 as? WordCardCell }) { cell.updateSpeakerState() } } +} - override func numberOfSections(in tableView: UITableView) -> Int { - sections.count +extension HistoryViewController: UICalendarViewDelegate { + func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? { + let dates = recordDates() + if dates.contains(where: { $0.year == dateComponents.year && $0.month == dateComponents.month && $0.day == dateComponents.day }) { + return .default(color: Theme.Color.accent, size: .small) + } + return nil + } +} + +extension HistoryViewController: UICalendarSelectionSingleDateDelegate { + func dateSelection(_ selection: UICalendarSelectionSingleDate, didSelectDate dateComponents: DateComponents?) { + guard let dateComponents, + let date = Calendar.current.date(from: dateComponents) else { return } + selectedDate = date + loadRecords(for: date) + } +} + +extension HistoryViewController: UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + selectedRecords.count } - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - sections[section].title - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - sections[section].items.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! WordCardCell - let record = sections[indexPath.section].items[indexPath.row] + let record = selectedRecords[indexPath.row] cell.configure(with: record.word) cell.onSpeak = { word in SpeechService.shared.speak(word.word) @@ -86,9 +158,9 @@ final class HistoryViewController: UITableViewController { return cell } - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { Haptics.selection() - let record = sections[indexPath.section].items[indexPath.row] + let record = selectedRecords[indexPath.row] let detail = WordDetailViewController(word: record.word, categoryName: "") navigationController?.pushViewController(detail, animated: true) }