Create TimelineCollectionViewController. Make navigation bar and toolbar solid.

This commit is contained in:
Brent Simmons
2025-02-12 08:12:08 -08:00
parent 15f46206ee
commit 418fbc1763
6 changed files with 227 additions and 8 deletions

View File

@@ -10,8 +10,9 @@ import Foundation
public typealias ArticleSetBlock = (Set<Article>) -> Void
public final class Article: Hashable {
public final class Article: Hashable, Identifiable {
public let id: String // Combination of articleID and accountID  unique across all local databases and accounts (not persisted, though)
public let articleID: String // Unique database ID (possibly sync service ID)
public let accountID: String
public let feedID: String // Likely a URL, but not necessarily
@@ -44,11 +45,10 @@ public final class Article: Hashable {
self.authors = authors
self.status = status
if let articleID = articleID {
self.articleID = articleID
} else {
self.articleID = Article.calculatedArticleID(feedID: feedID, uniqueID: uniqueID)
}
let logicalArticleID = articleID ?? Article.calculatedArticleID(feedID: feedID, uniqueID: uniqueID)
self.articleID = logicalArticleID
self.id = "\(logicalArticleID):\(accountID)"
}
public static func calculatedArticleID(feedID: String, uniqueID: String) -> String {

View File

@@ -0,0 +1,34 @@
//
// UIColor+RSCore.swift
// RSCore
//
// Created by Brent Simmons on 2/12/25.
//
#if os(iOS)
import Foundation
import UIKit
extension UIColor {
public convenience init(hex: String) {
var s = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
if (s.hasPrefix("#")) {
s.removeFirst()
}
var rgb: UInt64 = 0
Scanner(string: s).scanHexInt64(&rgb)
let red = CGFloat((rgb >> 16) & 0xFF) / 255.0
let green = CGFloat((rgb >> 8) & 0xFF) / 255.0
let blue = CGFloat(rgb & 0xFF) / 255.0
self.init(red: red, green: green, blue: blue, alpha: 1.0)
}
}
#endif

View File

@@ -35,12 +35,15 @@ extension AppColor {
extension AppColor {
#if os(iOS)
static var barColor = UIColor(hex: "#708090")
static var controlBackground = color("controlBackgroundColor")
static var fullScreenBackground = color("fullScreenBackgroundColor")
static var iconBackground = color("iconBackgroundColor")
static var navigationBarBackground = barColor
static var secondaryAccent = color("secondaryAccentColor")
static var sectionHeader = color("sectionHeaderColor")
static var tickMark = color("tickMarkColor")
static var toolbarBackground = barColor
static var vibrantText = color("vibrantTextColor")
#endif
}

View File

@@ -78,6 +78,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
ArticleStatusSyncTimer.shared.update()
#endif
configureAppearance()
// Create window and UI
mainWindowController = MainWindowController()
@@ -256,6 +258,37 @@ private extension AppDelegate {
window.overrideUserInterfaceStyle = updatedStyle
}
}
// https://developer.apple.com/documentation/technotes/tn3106-customizing-uinavigationbar-appearance
func configureAppearance() {
let navigationBarAppearance = createNavigationBarAppearance()
let appearance = UINavigationBar.appearance()
appearance.scrollEdgeAppearance = navigationBarAppearance
appearance.compactAppearance = navigationBarAppearance
appearance.standardAppearance = navigationBarAppearance
appearance.compactScrollEdgeAppearance = navigationBarAppearance
}
func createNavigationBarAppearance() -> UINavigationBarAppearance {
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.configureWithOpaqueBackground()
navigationBarAppearance.backgroundColor = AppColor.navigationBarBackground
navigationBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
navigationBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
let barButtonItemAppearance = UIBarButtonItemAppearance(style: .plain)
barButtonItemAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.white]
barButtonItemAppearance.disabled.titleTextAttributes = [.foregroundColor: UIColor.lightText]
barButtonItemAppearance.highlighted.titleTextAttributes = [.foregroundColor: UIColor.label]
barButtonItemAppearance.focused.titleTextAttributes = [.foregroundColor: UIColor.white]
navigationBarAppearance.buttonAppearance = barButtonItemAppearance
navigationBarAppearance.backButtonAppearance = barButtonItemAppearance
navigationBarAppearance.doneButtonAppearance = barButtonItemAppearance
return navigationBarAppearance
}
}
// MARK: - BackgroundTaskManagerDelegate

View File

@@ -61,12 +61,36 @@ final class SidebarViewController: UICollectionViewController {
super.viewDidLoad()
collectionView.backgroundColor = .systemRed
title = "Feeds"
navigationController?.navigationBar.prefersLargeTitles = true
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground() // Ensures solid background
appearance.backgroundColor = AppColor.navigationBarBackground // Set your desired color
appearance.titleTextAttributes = [.foregroundColor: UIColor.white] // Regular title text
appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white] // Large title text
appearance.shadowColor = .clear
// Apply the appearance settings
navigationController?.navigationBar.standardAppearance = appearance
navigationController?.navigationBar.scrollEdgeAppearance = appearance
navigationController?.navigationBar.compactAppearance = appearance // Optional
navigationController?.navigationBar.compactScrollEdgeAppearance = appearance
navigationController?.navigationBar.isTranslucent = false
if let subviews = navigationController?.navigationBar.subviews {
for subview in subviews {
if subview.frame.height < 2 {
subview.isHidden = true
}
}
}
navigationItem.rightBarButtonItem = filterButton
toolbar.barTintColor = AppColor.toolbarBackground
toolbar.isTranslucent = false
toolbar.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(toolbar)

View File

@@ -0,0 +1,125 @@
//
// TimelineCollectionViewController.swift
// NetNewsWire-iOS
//
// Created by Brent Simmons on 2/9/25.
// Copyright © 2025 Ranchero Software. All rights reserved.
//
import Foundation
import UIKit
import Articles
typealias TimelineSectionID = Int
typealias TimelineArticleID = String
final class TimelineCell: UICollectionViewCell {
static let reuseIdentifier = "TimelineCell"
let titleLabel: UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 16)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(titleLabel)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - API
func configure(_ article: Article?, showFeedName: Bool, showIcon: Bool) {
if let article {
titleLabel.text = article.title
} else {
titleLabel.text = ""
}
}
}
final class TimelineArticlesManager {
var articles = [Article]() {
didSet {
updateArticleIDsToArticles()
}
}
subscript(_ articleID: String) -> Article? {
articleIDsToArticles[articleID]
}
private var articleIDsToArticles = [String: Article]()
private func updateArticleIDsToArticles() {
var d = [String: Article]()
for article in articles {
d[article.id] = article
}
articleIDsToArticles = d
}
}
final class TimelineCollectionViewController: UICollectionViewController {
private let timelineArticlesManager = TimelineArticlesManager()
typealias DataSource = UICollectionViewDiffableDataSource<TimelineSectionID, TimelineArticleID>
private lazy var dataSource = createDataSource()
init() {
var configuration = UICollectionLayoutListConfiguration(appearance: .plain)
configuration.headerMode = .none
let layout = UICollectionViewCompositionalLayout.list(using: configuration)
super.init(collectionViewLayout: layout)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Articles"
collectionView.register(TimelineCell.self, forCellWithReuseIdentifier: TimelineCell.reuseIdentifier)
timelineArticlesManager.articles = [Article]()
applySnapshot()
}
}
private extension TimelineCollectionViewController {
func createDataSource() -> DataSource {
UICollectionViewDiffableDataSource<TimelineSectionID, TimelineArticleID>(collectionView: collectionView) { collectionView, indexPath, articleID -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TimelineCell.reuseIdentifier, for: indexPath) as! TimelineCell
let article = self.timelineArticlesManager[articleID]
cell.configure(article, showFeedName: false, showIcon: false)
return cell
}
}
func applySnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<TimelineSectionID, TimelineArticleID>()
let oneAndOnlySectionID = 0
snapshot.appendSections([oneAndOnlySectionID])
let articleIDs = timelineArticlesManager.articles.map { $0.id }
snapshot.appendItems(articleIDs, toSection: oneAndOnlySectionID)
dataSource.apply(snapshot, animatingDifferences: true)
}
}