mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
[AltStore] Improves MyAppsViewController UI
Adds “No Updates Available” text Prevents unnecessary reload animations
This commit is contained in:
@@ -603,6 +603,7 @@ World</string>
|
||||
<constraint firstItem="xnC-tS-ZdV" firstAttribute="centerY" secondItem="IgU-aM-YrX" secondAttribute="centerY" id="qCU-ye-fSf"/>
|
||||
</constraints>
|
||||
<edgeInsets key="layoutMargins" top="10" left="20" bottom="10" right="20"/>
|
||||
<viewLayoutGuide key="safeArea" id="wu0-44-ei8"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="descriptionLabel" destination="ErG-8A-uqY" id="iuN-kE-IEm"/>
|
||||
@@ -871,6 +872,28 @@ World</string>
|
||||
</segue>
|
||||
</connections>
|
||||
</collectionViewCell>
|
||||
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="NoUpdatesCell" id="h0f-XI-UA5">
|
||||
<rect key="frame" x="0.0" y="125" width="375" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="No Updates Available" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="z04-yg-x1t">
|
||||
<rect key="frame" x="104" y="20" width="167" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
|
||||
<color key="textColor" name="Green"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</view>
|
||||
<constraints>
|
||||
<constraint firstItem="z04-yg-x1t" firstAttribute="centerY" secondItem="h0f-XI-UA5" secondAttribute="centerY" id="3dw-fe-ACP"/>
|
||||
<constraint firstItem="z04-yg-x1t" firstAttribute="centerX" secondItem="h0f-XI-UA5" secondAttribute="centerX" id="AIh-kx-SmK"/>
|
||||
<constraint firstItem="z04-yg-x1t" firstAttribute="top" relation="greaterThanOrEqual" secondItem="h0f-XI-UA5" secondAttribute="top" constant="10" id="QwS-y9-ahl"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="z04-yg-x1t" secondAttribute="bottom" constant="10" id="uQI-7x-E3b"/>
|
||||
</constraints>
|
||||
</collectionViewCell>
|
||||
</cells>
|
||||
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="InstalledAppsHeader" id="Crb-NU-1Ye" customClass="InstalledAppsCollectionHeaderView" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
|
||||
|
||||
@@ -17,6 +17,7 @@ extension MyAppsViewController
|
||||
{
|
||||
private enum Section: Int, CaseIterable
|
||||
{
|
||||
case noUpdates
|
||||
case updates
|
||||
case installedApps
|
||||
}
|
||||
@@ -37,6 +38,7 @@ private extension Date
|
||||
class MyAppsViewController: UICollectionViewController
|
||||
{
|
||||
private lazy var dataSource = self.makeDataSource()
|
||||
private lazy var noUpdatesDataSource = self.makeNoUpdatesDataSource()
|
||||
private lazy var updatesDataSource = self.makeUpdatesDataSource()
|
||||
private lazy var installedAppsDataSource = self.makeInstalledAppsDataSource()
|
||||
|
||||
@@ -69,6 +71,9 @@ class MyAppsViewController: UICollectionViewController
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
// Allows us to intercept delegate callbacks.
|
||||
self.updatesDataSource.fetchedResultsController.delegate = self
|
||||
|
||||
self.collectionView.dataSource = self.dataSource
|
||||
self.collectionView.prefetchDataSource = self.dataSource
|
||||
|
||||
@@ -97,11 +102,26 @@ private extension MyAppsViewController
|
||||
{
|
||||
func makeDataSource() -> RSTCompositeCollectionViewPrefetchingDataSource<InstalledApp, UIImage>
|
||||
{
|
||||
let dataSource = RSTCompositeCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(dataSources: [self.updatesDataSource, self.installedAppsDataSource])
|
||||
let dataSource = RSTCompositeCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(dataSources: [self.noUpdatesDataSource, self.updatesDataSource, self.installedAppsDataSource])
|
||||
dataSource.proxy = self
|
||||
return dataSource
|
||||
}
|
||||
|
||||
func makeNoUpdatesDataSource() -> RSTDynamicCollectionViewPrefetchingDataSource<InstalledApp, UIImage>
|
||||
{
|
||||
let dynamicDataSource = RSTDynamicCollectionViewPrefetchingDataSource<InstalledApp, UIImage>()
|
||||
dynamicDataSource.numberOfSectionsHandler = { 1 }
|
||||
dynamicDataSource.numberOfItemsHandler = { _ in self.updatesDataSource.itemCount == 0 ? 1 : 0 }
|
||||
dynamicDataSource.cellIdentifierHandler = { _ in "NoUpdatesCell" }
|
||||
dynamicDataSource.cellConfigurationHandler = { (cell, _, indexPath) in
|
||||
cell.layer.cornerRadius = 20
|
||||
cell.layer.masksToBounds = true
|
||||
cell.contentView.backgroundColor = UIColor.altGreen.withAlphaComponent(0.15)
|
||||
}
|
||||
|
||||
return dynamicDataSource
|
||||
}
|
||||
|
||||
func makeUpdatesDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>
|
||||
{
|
||||
let fetchRequest = InstalledApp.updatesFetchRequest()
|
||||
@@ -232,6 +252,10 @@ private extension MyAppsViewController
|
||||
self.navigationController?.tabBarItem.badgeValue = nil
|
||||
UIApplication.shared.applicationIconBadgeNumber = 0
|
||||
}
|
||||
|
||||
UIView.performWithoutAnimation {
|
||||
self.collectionView.reloadSections(IndexSet(integer: Section.updates.rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
func refresh(_ installedApps: [InstalledApp], completionHandler: @escaping (Result<[String : Result<InstalledApp, Error>], Error>) -> Void)
|
||||
@@ -282,7 +306,9 @@ private extension MyAppsViewController
|
||||
|
||||
self.refreshGroup = group
|
||||
|
||||
self.collectionView.reloadSections(IndexSet(integer: Section.installedApps.rawValue))
|
||||
UIView.performWithoutAnimation {
|
||||
self.collectionView.reloadSections(IndexSet(integer: Section.installedApps.rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
if installedApps.contains(where: { $0.app.identifier == App.altstoreAppID })
|
||||
@@ -382,9 +408,7 @@ private extension MyAppsViewController
|
||||
}
|
||||
|
||||
self.refresh([installedApp]) { (result) in
|
||||
DispatchQueue.main.async {
|
||||
self.collectionView.reloadSections(IndexSet(integer: Section.installedApps.rawValue))
|
||||
}
|
||||
print("Finished refreshing with result:", result.error?.localizedDescription ?? "success")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,6 +457,8 @@ private extension MyAppsViewController
|
||||
print("Updated app:", app.identifier)
|
||||
// No need to reload, since the the update cell is gone now.
|
||||
}
|
||||
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,6 +494,17 @@ extension MyAppsViewController
|
||||
headerView.button.setTitleColor(.altGreen, for: .normal)
|
||||
headerView.button.addTarget(self, action: #selector(MyAppsViewController.toggleAppUpdates), for: .primaryActionTriggered)
|
||||
|
||||
if self.isUpdateSectionCollapsed
|
||||
{
|
||||
headerView.button.titleLabel?.transform = .identity
|
||||
}
|
||||
else
|
||||
{
|
||||
headerView.button.titleLabel?.transform = CGAffineTransform.identity.rotated(by: .pi)
|
||||
}
|
||||
|
||||
headerView.isHidden = (self.updatesDataSource.itemCount <= 2)
|
||||
|
||||
headerView.button.layoutIfNeeded()
|
||||
}
|
||||
|
||||
@@ -498,9 +535,16 @@ extension MyAppsViewController: UICollectionViewDelegateFlowLayout
|
||||
{
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
|
||||
{
|
||||
let padding = 30 as CGFloat
|
||||
let width = collectionView.bounds.width - padding
|
||||
|
||||
let section = Section.allCases[indexPath.section]
|
||||
switch section
|
||||
{
|
||||
case .noUpdates:
|
||||
let size = CGSize(width: width, height: 44)
|
||||
return size
|
||||
|
||||
case .updates:
|
||||
let item = self.dataSource.item(at: indexPath)
|
||||
|
||||
@@ -509,9 +553,6 @@ extension MyAppsViewController: UICollectionViewDelegateFlowLayout
|
||||
return previousHeight
|
||||
}
|
||||
|
||||
let padding = 30 as CGFloat
|
||||
let width = collectionView.bounds.width - padding
|
||||
|
||||
let widthConstraint = self.prototypeUpdateCell.contentView.widthAnchor.constraint(equalToConstant: width)
|
||||
NSLayoutConstraint.activate([widthConstraint])
|
||||
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
|
||||
@@ -532,7 +573,11 @@ extension MyAppsViewController: UICollectionViewDelegateFlowLayout
|
||||
let section = Section.allCases[section]
|
||||
switch section
|
||||
{
|
||||
case .updates: return CGSize(width: collectionView.bounds.width, height: 26)
|
||||
case .noUpdates: return .zero
|
||||
case .updates:
|
||||
let height: CGFloat = self.updatesDataSource.itemCount > maximumCollapsedUpdatesCount ? 26 : 0
|
||||
return CGSize(width: collectionView.bounds.width, height: height)
|
||||
|
||||
case .installedApps: return CGSize(width: collectionView.bounds.width, height: 29)
|
||||
}
|
||||
}
|
||||
@@ -542,8 +587,54 @@ extension MyAppsViewController: UICollectionViewDelegateFlowLayout
|
||||
let section = Section.allCases[section]
|
||||
switch section
|
||||
{
|
||||
case .updates: return UIEdgeInsets(top: 12, left: 15, bottom: 20, right: 15)
|
||||
case .installedApps: return UIEdgeInsets(top: 13, left: 0, bottom: 20, right: 0)
|
||||
case .noUpdates:
|
||||
guard self.updatesDataSource.itemCount == 0 else { return .zero }
|
||||
return UIEdgeInsets(top: 12, left: 15, bottom: 20, right: 15)
|
||||
|
||||
case .updates:
|
||||
guard self.updatesDataSource.itemCount > 0 else { return .zero }
|
||||
return UIEdgeInsets(top: 12, left: 15, bottom: 20, right: 15)
|
||||
|
||||
case .installedApps: return UIEdgeInsets(top: 12, left: 0, bottom: 20, right: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MyAppsViewController: NSFetchedResultsControllerDelegate
|
||||
{
|
||||
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
|
||||
{
|
||||
self.updatesDataSource.controllerWillChangeContent(controller)
|
||||
}
|
||||
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType)
|
||||
{
|
||||
self.updatesDataSource.controller(controller, didChange: sectionInfo, atSectionIndex: UInt(sectionIndex), for: type)
|
||||
}
|
||||
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
|
||||
{
|
||||
self.updatesDataSource.controller(controller, didChange: anObject, at: indexPath, for: type, newIndexPath: newIndexPath)
|
||||
}
|
||||
|
||||
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
|
||||
{
|
||||
let previousUpdateCount = self.collectionView.numberOfItems(inSection: Section.updates.rawValue)
|
||||
let updateCount = Int(self.updatesDataSource.itemCount)
|
||||
|
||||
if previousUpdateCount == 0 && updateCount > 0
|
||||
{
|
||||
// Remove "No Updates Available" cell.
|
||||
let change = RSTCellContentChange(type: .delete, currentIndexPath: IndexPath(item: 0, section: Section.noUpdates.rawValue), destinationIndexPath: nil)
|
||||
self.collectionView.add(change)
|
||||
}
|
||||
else if previousUpdateCount > 0 && updateCount == 0
|
||||
{
|
||||
// Insert "No Updates Available" cell.
|
||||
let change = RSTCellContentChange(type: .insert, currentIndexPath: nil, destinationIndexPath: IndexPath(item: 0, section: Section.noUpdates.rawValue))
|
||||
self.collectionView.add(change)
|
||||
}
|
||||
|
||||
self.updatesDataSource.controllerDidChangeContent(controller)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user