import UIKit @MainActor final class RepositoriesViewController: RefreshingTableViewController { private let context: AppContext private let mode: RepositoryPane private var rows: [RepositoryRow] = [] private var currentPage: UInt32 = 0 init(context: AppContext, mode: RepositoryPane) { self.context = context self.mode = mode super.init() title = context.core.activeServerName() ?? "Repositories" } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } override func viewDidLoad() { super.viewDidLoad() navigationItem.leftBarButtonItem = UIBarButtonItem( image: context.symbol("server.rack"), primaryAction: UIAction { [weak self] _ in guard let self else { return } self.navigationController?.pushViewController( ServersViewController(context: self.context), animated: true ) } ) loadContent(refreshing: false) } override func loadContent(refreshing: Bool) { loadPage(1, refreshing: refreshing) } override func loadMoreContent() { loadPage(currentPage + 1, refreshing: false) } private func loadPage(_ page: UInt32, refreshing: Bool) { if page == 1 { resetPagination() beginLoading(refreshing: refreshing) } loadingTask?.cancel() loadingTask = Task { do { let result = try await context.core.repositories(page: page, pane: mode) rows = result.rows currentPage = page finishPagination(hasMore: result.hasMore) tableView.reloadData() tableView.backgroundView = rows.isEmpty ? EmptyBackgroundView( title: "No repositories", detail: "This account does not own any repositories on this server." ) : nil } catch { if !Task.isCancelled { show(error: error) failPagination() } } if page == 1 { endLoading() } } } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { rows.count } override func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "repository") ?? UITableViewCell(style: .subtitle, reuseIdentifier: "repository") let row = rows[indexPath.row] configureTextCell(cell, title: row.name, detail: "\(row.description)\n\(row.meta)") let button = UIButton(type: .system, primaryAction: UIAction { [weak self] _ in self?.toggleFavorite(row) }) button.setImage(context.symbol(row.favorite ? "star.fill" : "star"), for: .normal) button.tintColor = row.favorite ? .systemYellow : .tertiaryLabel button.frame.size = CGSize(width: 44, height: 44) cell.accessoryView = button return cell } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 96 } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let row = rows[indexPath.row] let destination: UIViewController switch mode { case .issues: destination = IssuesViewController( context: context, owner: row.owner, repository: row.name ) case .commits: destination = CommitsViewController( context: context, owner: row.owner, repository: row.name ) case .milestones: destination = MilestonesViewController( context: context, owner: row.owner, repository: row.name ) } navigationController?.pushViewController(destination, animated: true) } private func toggleFavorite(_ row: RepositoryRow) { do { rows = try context.core.toggleFavorite( owner: row.owner, repository: row.name, pane: mode ) tableView.reloadData() } catch { show(error: error) } } }