feat(album): add pull-to-refresh to album entry

This commit is contained in:
2026-08-17 15:19:21 +08:00
parent 76aa7bb4aa
commit b127495e52
3 changed files with 48 additions and 7 deletions
@@ -52,7 +52,10 @@ final class TravelAlbumEntryViewModel {
}
/// 重新拉取相册列表。
func loadAlbums(api: any TravelAlbumServing) async {
func loadAlbums(
api: any TravelAlbumServing,
preservingContentOnFailure: Bool = false
) async {
isLoading = true
notifyStateChange()
defer {
@@ -67,12 +70,20 @@ final class TravelAlbumEntryViewModel {
} catch is CancellationError {
return
} catch {
albums = []
albumTotal = 0
if !preservingContentOnFailure {
albums = []
albumTotal = 0
}
onShowMessage?(error.localizedDescription)
}
}
/// 用户下拉刷新相册列表;失败时保留当前内容,避免页面瞬间清空。
func refreshAlbums(api: any TravelAlbumServing) async {
guard !isLoading else { return }
await loadAlbums(api: api, preservingContentOnFailure: true)
}
/// 打开创建相册弹窗。
func openCreateSheet(api: any TravelAlbumServing) async {
guard !isCreating else { return }
@@ -36,6 +36,7 @@ final class TravelAlbumEntryViewController: BaseViewController {
private let heroCard = TravelAlbumHeroCard()
private let titleLabel = UILabel()
private let tableView = UITableView(frame: .zero, style: .plain)
private let refreshControl = UIRefreshControl()
private let emptyView = TravelAlbumEmptyView()
private var dataSource: UITableViewDiffableDataSource<Int, TravelAlbum>!
private var refreshState = TravelAlbumReturnRefreshState()
@@ -65,6 +66,8 @@ final class TravelAlbumEntryViewController: BaseViewController {
tableView.separatorStyle = .none
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 148
tableView.alwaysBounceVertical = true
tableView.refreshControl = refreshControl
tableView.delegate = self
tableView.register(TravelAlbumTaskCell.self, forCellReuseIdentifier: TravelAlbumTaskCell.reuseIdentifier)
@@ -84,6 +87,7 @@ final class TravelAlbumEntryViewController: BaseViewController {
view.addSubview(titleLabel)
view.addSubview(tableView)
view.addSubview(emptyView)
emptyView.isUserInteractionEnabled = false
}
override func setupConstraints() {
@@ -108,6 +112,7 @@ final class TravelAlbumEntryViewController: BaseViewController {
override func bindActions() {
heroCard.addTarget(self, action: #selector(createTapped), for: .touchUpInside)
refreshControl.addTarget(self, action: #selector(refreshPulled), for: .valueChanged)
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
@@ -138,8 +143,8 @@ final class TravelAlbumEntryViewController: BaseViewController {
heroCard.isLoading = viewModel.isCreating
titleLabel.text = "我的任务(\(viewModel.albumTotal))"
titleLabel.isHidden = viewModel.albums.isEmpty
tableView.isHidden = viewModel.albums.isEmpty
emptyView.isHidden = !viewModel.albums.isEmpty || viewModel.isLoading
emptyView.isHidden = !viewModel.albums.isEmpty
|| (viewModel.isLoading && !refreshControl.isRefreshing)
var snapshot = NSDiffableDataSourceSnapshot<Int, TravelAlbum>()
snapshot.appendSections([0])
snapshot.appendItems(viewModel.albums)
@@ -147,13 +152,23 @@ final class TravelAlbumEntryViewController: BaseViewController {
snapshot,
animatingDifferences: !refreshState.suppressesSnapshotAnimations
)
if viewModel.isLoading && viewModel.albums.isEmpty {
if viewModel.isLoading && viewModel.albums.isEmpty && !refreshControl.isRefreshing {
showLoading()
} else {
hideLoading()
}
}
@objc private func refreshPulled() {
Task {
await viewModel.refreshAlbums(api: api)
await MainActor.run {
self.refreshControl.endRefreshing()
self.applyViewModel()
}
}
}
@objc private func createTapped() {
Task {
await viewModel.openCreateSheet(api: api)