Skip to content

Commit

Permalink
[Home] Starting to work on homepage stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
kirb committed Aug 7, 2022
1 parent 9cf89f4 commit eb5ba43
Show file tree
Hide file tree
Showing 22 changed files with 425 additions and 88 deletions.
8 changes: 8 additions & 0 deletions Zebra.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@
4EA26EBA27CCEEBC0019A5AA /* BrowseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA26EB927CCEEBC0019A5AA /* BrowseViewController.swift */; };
4EA26EC427CCF6500019A5AA /* DepictionKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA26EC327CCF6500019A5AA /* DepictionKit */; };
4EA2F1DD27DA2A0D0080DC35 /* Installed.pack in Resources */ = {isa = PBXBuildFile; fileRef = 4EA2F1DC27DA2A0D0080DC35 /* Installed.pack */; };
4EA833972875544B00029B9C /* SectionDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA833962875544B00029B9C /* SectionDateHeaderView.swift */; };
4EA8339928755AA800029B9C /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA8339828755AA800029B9C /* NumberFormatter.swift */; };
4EA8339B287570B000029B9C /* WebImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA8339A287570B000029B9C /* WebImageView.swift */; };
4EA893DD27DDABD600E2EC7A /* sandboxed.json in Resources */ = {isa = PBXBuildFile; fileRef = 4EA893DC27DDABD600E2EC7A /* sandboxed.json */; };
4EA893DF27DF3D2100E2EC7A /* Dictionary+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA893DE27DF3D2100E2EC7A /* Dictionary+Extensions.swift */; };
4EAC00452863390700B80531 /* SourceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1428E7D277A2731005B0885 /* SourceTableViewCell.swift */; };
Expand Down Expand Up @@ -278,7 +280,9 @@
4EA26EB927CCEEBC0019A5AA /* BrowseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseViewController.swift; sourceTree = "<group>"; };
4EA26EC227CCF5430019A5AA /* DepictionKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = DepictionKit; path = Vendor/DepictionKit; sourceTree = "<group>"; };
4EA2F1DC27DA2A0D0080DC35 /* Installed.pack */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Installed.pack; sourceTree = "<group>"; };
4EA833962875544B00029B9C /* SectionDateHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionDateHeaderView.swift; sourceTree = "<group>"; };
4EA8339828755AA800029B9C /* NumberFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberFormatter.swift; sourceTree = "<group>"; };
4EA8339A287570B000029B9C /* WebImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebImageView.swift; sourceTree = "<group>"; };
4EA893DC27DDABD600E2EC7A /* sandboxed.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = sandboxed.json; sourceTree = "<group>"; };
4EA893DE27DF3D2100E2EC7A /* Dictionary+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+Extensions.swift"; sourceTree = "<group>"; };
4EB23508283D2F5F00713CBB /* URLSession+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+Additions.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -900,7 +904,9 @@
89B701D42641CAAA00F50DAD /* ZBErrorViewController.mm */,
4EBA840B27D1F9A100766DBE /* SectionHeaderView.swift */,
4E840B3827D61ED700039938 /* SectionHeaderButton.swift */,
4EA833962875544B00029B9C /* SectionDateHeaderView.swift */,
4EBA840D27D21F4700766DBE /* InfoFooterView.swift */,
4EA8339A287570B000029B9C /* WebImageView.swift */,
4EBA841A27D3119800766DBE /* IconImageView.swift */,
4E840B3A27D6484900039938 /* GradientView.swift */,
4E138C67284C690C0058D94D /* ProgressDonut.swift */,
Expand Down Expand Up @@ -1525,7 +1531,9 @@
4EB8C48728583EAD00FFC744 /* NavigationController.swift in Sources */,
4EC7F28527B28D770078F953 /* Command.swift in Sources */,
4EC7F29C27B3A19F0078F953 /* RootViewController.swift in Sources */,
4EA8339B287570B000029B9C /* WebImageView.swift in Sources */,
4EA26EB727CCDAC80019A5AA /* BaseSceneDelegate.swift in Sources */,
4EA833972875544B00029B9C /* SectionDateHeaderView.swift in Sources */,
4EB8C48528583C5500FFC744 /* NavigationBar.swift in Sources */,
4EA26EBA27CCEEBC0019A5AA /* BrowseViewController.swift in Sources */,
4E1F060828411A47006B3F0C /* PackageViewController.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Zebra/Controllers/App/AppSceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ extension AppSceneDelegate: NSToolbarDelegate {
item.isNavigational = true
item.action = #selector(RootViewController.goBack)
item.image = UIImage(systemName: "chevron.backward")
item.label = .back
item.toolTip = .back

default: break
}
Expand Down
5 changes: 5 additions & 0 deletions Zebra/Controllers/Plains/SourceRefreshController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class SourceRefreshController: NSObject {
}

static let refreshProgressDidChangeNotification = Notification.Name(rawValue: "SourceRefreshProgressDidChangeNotification")
static let refreshDidFinishNotification = Notification.Name(rawValue: "SourceRefreshDidFinishNotification")

private static let legacySourceHosts = ["repo.dynastic.co", "apt.bingner.com"]

Expand Down Expand Up @@ -700,6 +701,10 @@ class SourceRefreshController: NSObject {
#endif

endRefresh()

DispatchQueue.main.async {
NotificationCenter.default.post(name: Self.refreshDidFinishNotification, object: nil)
}
}

private func cancel() {
Expand Down
4 changes: 4 additions & 0 deletions Zebra/Extensions/Array+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ extension Array {
func compact<ElementOfResult>() -> [ElementOfResult] where Element == ElementOfResult? {
compactMap { $0 }
}

func safeSubSequence(_ range: Range<Int>) -> SubSequence {
self[Swift.max(range.lowerBound, 0)..<Swift.min(range.upperBound, count)]
}
}

extension Array where Element == String {
Expand Down
5 changes: 5 additions & 0 deletions Zebra/Extensions/String+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ extension String {
options: options.union(.regularExpression),
range: startIndex..<endIndex)
}

func matches(regex: String, options: CompareOptions = []) -> Bool {
range(of: regex,
options: options.union(.regularExpression)) != nil
}
}
6 changes: 3 additions & 3 deletions Zebra/Extensions/UIFont+Additions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ extension UIFont {
@objc static let monospace = UIFont.monospacedSystemFont(ofSize: 11, weight: .regular)
@objc static let boldMonospace = UIFont.monospacedSystemFont(ofSize: 11, weight: .bold)

class func preferredFont(forTextStyle style: TextStyle, weight: Weight) -> UIFont {
let dynamicFont = preferredFont(forTextStyle: style)
let font = systemFont(ofSize: dynamicFont.pointSize, weight: weight)
class func preferredFont(forTextStyle style: TextStyle, scale: CGFloat = 1, minimumSize: CGFloat = 0, weight: Weight = .regular) -> UIFont {
let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style)
let font = systemFont(ofSize: max(descriptor.pointSize * scale, minimumSize), weight: weight)
return UIFontMetrics(forTextStyle: style)
.scaledFont(for: font)
}
Expand Down
2 changes: 1 addition & 1 deletion Zebra/Extensions/UIImage+Additions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ extension UIImageView {

/// > Note: Wait till the image view has been laid out before calling this. If `frame.size == .zero`,
/// > the image load will be skipped. Call this from `layoutSubviews` to ensure relayout as needed.
func load(url: URL?, usingScale: Bool = false, fallbackImage: UIImage? = nil) {
@objc func load(url: URL?, usingScale: Bool = false, fallbackImage: UIImage? = nil) {
let scale = (window?.screen ?? .main).scale
var sources = Self.sources(url: url, scale: usingScale ? scale : 1)
if sources.isEmpty || frame.size == .zero {
Expand Down
4 changes: 2 additions & 2 deletions Zebra/Extensions/UIListContentConfiguration+Additions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension UIListContentConfiguration {
var config = UIListContentConfiguration.cell()
config.textProperties.font = .preferredFont(forTextStyle: .headline)
config.imageProperties.preferredSymbolConfiguration = .init(textStyle: .headline)
config.imageToTextPadding = 4
config.imageToTextPadding = 10
return config
}

Expand All @@ -27,7 +27,7 @@ extension UIListContentConfiguration {
config.secondaryTextProperties.numberOfLines = 1
config.textToSecondaryTextVerticalPadding = 2
config.imageProperties.preferredSymbolConfiguration = .init(textStyle: .headline)
config.imageToTextPadding = 4
config.imageToTextPadding = 10
return config
}

Expand Down
15 changes: 14 additions & 1 deletion Zebra/Model/PLPackage+Additions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,23 @@ import Plains

extension Package {

// MARK: - Actions
// We do a bunch of cleaning up we shouldn‘t really need to do because Packix.
private static let osVersionRegex = "::ios(ios)?(\\d+(\\.\\d+)*).*$"

var isCommercial: Bool { tags.contains("cydia::commercial") }

var isCompatible: Bool {
let minimumTag = tags.first(where: { $0.matches(regex: "^compatible_min\(Self.osVersionRegex)", options: .caseInsensitive) })
let maximumTag = tags.first(where: { $0.matches(regex: "^compatible_max\(Self.osVersionRegex)", options: .caseInsensitive) })
let minimumVersion = minimumTag?.replacingOccurrences(regex: "^compatible_min\(Self.osVersionRegex)", with: "$2", options: .caseInsensitive) ?? "0.0"
let maximumVersion = maximumTag?.replacingOccurrences(regex: "^compatible_max\(Self.osVersionRegex)", with: "$2", options: .caseInsensitive) ?? "99.99"
let systemVersion = UIDevice.current.systemVersion
return minimumVersion.compare(systemVersion, options: .numeric) != .orderedDescending &&
maximumVersion.compare(systemVersion, options: .numeric) != .orderedAscending
}

// MARK: - Actions

var possibleActions: ZBPackageActionType {
var actions: ZBPackageActionType = []
if let _ = source {
Expand Down
2 changes: 2 additions & 0 deletions Zebra/Tabs/Home/Errors/ErrorsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class ErrorsViewController: ListCollectionViewController {

let section = NSCollectionLayoutSection.list(using: configuration,
layoutEnvironment: layoutEnvironment)
section.interGroupSpacing = 8
section.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0)
section.contentInsetsReference = .none
section.boundarySupplementaryItems = [.header]
return section
Expand Down
6 changes: 3 additions & 3 deletions Zebra/Tabs/Home/HomeErrorCollectionViewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class HomeErrorCollectionViewCell: UICollectionViewListCell {
.customView(configuration: .init(customView: UIView(),
placement: .trailing(),
isHidden: true,
reservedLayoutWidth: .custom(15)))
reservedLayoutWidth: .custom(30)))
]
}

Expand All @@ -47,8 +47,8 @@ class HomeErrorCollectionViewCell: UICollectionViewListCell {

var backgroundConfiguration = UIBackgroundConfiguration.clear()
backgroundConfiguration.backgroundColor = state.isHighlighted ? .systemGray4 : .systemGray6
backgroundConfiguration.cornerRadius = 20
backgroundConfiguration.edgesAddingLayoutMarginsToBackgroundInsets = .all
backgroundConfiguration.cornerRadius = 15
backgroundConfiguration.edgesAddingLayoutMarginsToBackgroundInsets = [.leading, .trailing]
self.backgroundConfiguration = backgroundConfiguration.updated(for: state)
}

Expand Down
56 changes: 49 additions & 7 deletions Zebra/Tabs/Home/HomeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class HomeViewController: ListCollectionViewController {

private var dataSource: UICollectionViewDiffableDataSource<Section, Value>!

private var isVisible = false

override class func createLayout() -> CollectionViewCompositionalLayout {
CollectionViewCompositionalLayout { index, environment in
switch index {
Expand All @@ -42,13 +44,15 @@ class HomeViewController: ListCollectionViewController {
return section

case 1:
let section = NSCollectionLayoutSection(group: .oneAcross(heightDimension: .estimated(72)))
let section = NSCollectionLayoutSection(group: .oneAcross(heightDimension: .estimated(52)))
section.interGroupSpacing = 15
section.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0)
section.contentInsetsReference = .none
return section

default:
let section = NSCollectionLayoutSection(group: .listGrid(environment: environment,
heightDimension: .estimated(52)))
heightDimension: .estimated(80)))
section.contentInsetsReference = .none
// section.boundarySupplementaryItems = [.header]
return section
Expand Down Expand Up @@ -95,13 +99,17 @@ class HomeViewController: ListCollectionViewController {
fatalError()
}
})

NotificationCenter.default.addObserver(self, selector: #selector(refreshDidFinish), name: SourceRefreshController.refreshDidFinishNotification, object: nil)
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange), name: SourceRefreshController.refreshProgressDidChangeNotification, object: nil)
update()
refreshProgressDidChange()
updatePromotedPackages()
}

override func viewDidDisappear(_ animated: Bool) {
Expand All @@ -113,13 +121,17 @@ class HomeViewController: ListCollectionViewController {
@objc private func refreshProgressDidChange() {
// Filter to only errors. Warnings are mostly annoying and not particularly useful.
errorCount = UInt(SourceRefreshController.shared.refreshErrors.count) + ErrorManager.shared.errorCount(at: .error)
let percent = SourceRefreshController.shared.progress.fractionCompleted

DispatchQueue.main.async {
self.update()
self.updateProgress(percent: percent)
}
}

let progress = SourceRefreshController.shared.progress
let percent = progress.fractionCompleted
self.navigationProgressBar?.setProgress(Float(percent), animated: true)
@objc private func refreshDidFinish() {
promotedPackages = nil
if isVisible {
updatePromotedPackages()
}
}

Expand All @@ -141,13 +153,43 @@ class HomeViewController: ListCollectionViewController {
snapshot.appendItems([.featured])
}
snapshot.appendSections([.notice])
dataSource.apply(snapshot, animatingDifferences: true, completion: nil)
}

private func updateProgress(percent: Double) {
navigationProgressBar?.setProgress(Float(percent), animated: true)

var snapshot = dataSource.snapshot()
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .notice))
if Device.isDemo {
snapshot.appendItems([.notice(reason: .sandboxed)])
}
if errorCount > 0 {
snapshot.appendItems([.notice(reason: .refreshErrors(count: errorCount))])
}
dataSource.apply(snapshot, animatingDifferences: true, completion: nil)
dataSource.apply(snapshot, animatingDifferences: false)
}

private func updatePromotedPackages() {
if promotedPackages != nil {
return
}

Task.detached {
let promotedPackages = await PromotedPackagesFetcher.getHomeCarouselItems()

await MainActor.run {
self.promotedPackages = promotedPackages

var snapshot = self.dataSource.snapshot()
if #available(iOS 15, *) {
snapshot.reconfigureItems([.featured])
} else {
snapshot.reloadItems([.featured])
}
self.dataSource.apply(snapshot, animatingDifferences: true)
}
}
}

}
Expand Down
24 changes: 8 additions & 16 deletions Zebra/UI/Carousel/CarouselItemCollectionViewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ class CarouselItemCollectionViewCell: UICollectionViewCell {
static let size = CGSize(width: 314, height: 175)

var item: CarouselItem? {
didSet { updateItem() }
didSet { setNeedsUpdateConfiguration() }
}

private var imageView: UIImageView!
private var imageView: WebImageView!
private var overlayView: GradientView!
private var titleLabel: UILabel!
private var detailLabel: UILabel!
Expand All @@ -26,7 +26,7 @@ class CarouselItemCollectionViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)

imageView = UIImageView(frame: bounds)
imageView = WebImageView(frame: bounds)
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
imageView.backgroundColor = .systemBackground
imageView.contentMode = .scaleAspectFill
Expand Down Expand Up @@ -97,14 +97,9 @@ class CarouselItemCollectionViewCell: UICollectionViewCell {
super.prepareForReuse()
}

override func layoutSubviews() {
super.layoutSubviews()
override func updateConfiguration(using state: UICellConfigurationState) {
super.updateConfiguration(using: state)

imageView.load(url: item?.imageURL,
fallbackImage: UIImage(named: "banner-fallback"))
}

private func updateItem() {
let displayTitle = item?.displayTitle ?? true
titleLabel.isHidden = !displayTitle
detailLabel.isHidden = !displayTitle
Expand All @@ -114,13 +109,10 @@ class CarouselItemCollectionViewCell: UICollectionViewCell {
detailLabel.text = displayTitle ? item?.subtitle : nil
accessibilityLabel = displayTitle ? nil : [item?.subtitle, item?.title].compact().joined(separator: ", ")

setNeedsLayout()
}
imageView.load(url: item?.imageURL,
fallbackImage: UIImage(named: "banner-fallback"))

override var isHighlighted: Bool {
didSet {
highlightView.alpha = isHighlighted ? 1 : 0
}
highlightView.alpha = state.isHighlighted ? 1 : 0
}

}
21 changes: 19 additions & 2 deletions Zebra/UI/Carousel/CarouselViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import UIKit

class CarouselViewController: ListCollectionViewController {

static let height: CGFloat = CarouselItemCollectionViewCell.size.height + (15 * 2)
static let height: CGFloat = CarouselItemCollectionViewCell.size.height + 10 + 15

var items = [CarouselItem]() {
didSet { updateItems() }
Expand All @@ -32,6 +32,7 @@ class CarouselViewController: ListCollectionViewController {
internal var errorLabel: UILabel!

private var dataSource: UICollectionViewDiffableDataSource<Int, CarouselItem>!
private var preloadTasks = [IndexPath: KingfisherTask]()

override class func createLayout() -> CollectionViewCompositionalLayout {
CollectionViewCompositionalLayout { _, environment in
Expand All @@ -41,7 +42,8 @@ class CarouselViewController: ListCollectionViewController {
subitems: [NSCollectionLayoutItem(layoutSize: .full)])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 20
section.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20)
section.contentInsetsReference = .layoutMargins
section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 0, bottom: 15, trailing: 0)

switch environment.traitCollection.horizontalSizeClass {
case .regular, .unspecified:
Expand Down Expand Up @@ -160,4 +162,19 @@ extension CarouselViewController: UICollectionViewDelegateFlowLayout { // UIColl
return self.collectionView(collectionView, previewForHighlightingContextMenuWithConfiguration: configuration)
}

func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
let item = items[indexPath.item]
preloadTasks[indexPath] = UIImageView.preload(url: item.imageURL,
screen: view.window?.screen ?? .main)
}
}

func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
preloadTasks[indexPath]?.cancel()
preloadTasks[indexPath] = nil
}
}

}
Loading

0 comments on commit eb5ba43

Please sign in to comment.