mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-20 12:13:26 +01:00
Supports updating apps from (almost) all AppBannerViews
Previously, you could only update apps from MyAppsViewController and AppViewController.
This commit is contained in:
@@ -87,8 +87,6 @@ class AppViewController: UIViewController
|
|||||||
self.bannerView.iconImageView.tintColor = self.app.tintColor
|
self.bannerView.iconImageView.tintColor = self.app.tintColor
|
||||||
self.bannerView.button.tintColor = self.app.tintColor
|
self.bannerView.button.tintColor = self.app.tintColor
|
||||||
self.bannerView.tintColor = self.app.tintColor
|
self.bannerView.tintColor = self.app.tintColor
|
||||||
|
|
||||||
self.bannerView.configure(for: self.app)
|
|
||||||
self.bannerView.accessibilityTraits.remove(.button)
|
self.bannerView.accessibilityTraits.remove(.button)
|
||||||
|
|
||||||
self.bannerView.button.addTarget(self, action: #selector(AppViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
self.bannerView.button.addTarget(self, action: #selector(AppViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
||||||
@@ -366,37 +364,14 @@ private extension AppViewController
|
|||||||
{
|
{
|
||||||
button.tintColor = self.app.tintColor
|
button.tintColor = self.app.tintColor
|
||||||
button.isIndicatingActivity = false
|
button.isIndicatingActivity = false
|
||||||
|
|
||||||
if let installedApp = self.app.installedApp
|
|
||||||
{
|
|
||||||
if let latestVersion = self.app.latestSupportedVersion, !installedApp.matches(latestVersion)
|
|
||||||
{
|
|
||||||
button.setTitle(NSLocalizedString("UPDATE", comment: ""), for: .normal)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
button.setTitle(NSLocalizedString("OPEN", comment: ""), for: .normal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
button.setTitle(NSLocalizedString("FREE", comment: ""), for: .normal)
|
|
||||||
}
|
|
||||||
|
|
||||||
let progress = AppManager.shared.installationProgress(for: self.app)
|
|
||||||
button.progress = progress
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let versionDate = self.app.latestSupportedVersion?.date, versionDate > Date()
|
self.bannerView.configure(for: self.app)
|
||||||
{
|
|
||||||
self.bannerView.button.countdownDate = versionDate
|
let title = self.bannerView.button.title(for: .normal)
|
||||||
self.navigationBarDownloadButton.countdownDate = versionDate
|
self.navigationBarDownloadButton.setTitle(title, for: .normal)
|
||||||
}
|
self.navigationBarDownloadButton.progress = self.bannerView.button.progress
|
||||||
else
|
self.navigationBarDownloadButton.countdownDate = self.bannerView.button.countdownDate
|
||||||
{
|
|
||||||
self.bannerView.button.countdownDate = nil
|
|
||||||
self.navigationBarDownloadButton.countdownDate = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let barButtonItem = self.navigationItem.rightBarButtonItem
|
let barButtonItem = self.navigationItem.rightBarButtonItem
|
||||||
self.navigationItem.rightBarButtonItem = nil
|
self.navigationItem.rightBarButtonItem = nil
|
||||||
|
|||||||
@@ -136,40 +136,8 @@ private extension BrowseViewController
|
|||||||
cell.bannerView.button.activityIndicatorView.style = .medium
|
cell.bannerView.button.activityIndicatorView.style = .medium
|
||||||
cell.bannerView.button.activityIndicatorView.color = .white
|
cell.bannerView.button.activityIndicatorView.color = .white
|
||||||
|
|
||||||
// Explicitly set to false to ensure we're starting from a non-activity indicating state.
|
|
||||||
// Otherwise, cell reuse can mess up some cached values.
|
|
||||||
cell.bannerView.button.isIndicatingActivity = false
|
|
||||||
|
|
||||||
let tintColor = app.tintColor ?? .altPrimary
|
let tintColor = app.tintColor ?? .altPrimary
|
||||||
cell.tintColor = tintColor
|
cell.tintColor = tintColor
|
||||||
|
|
||||||
if app.installedApp == nil
|
|
||||||
{
|
|
||||||
let buttonTitle = NSLocalizedString("Free", comment: "")
|
|
||||||
cell.bannerView.button.setTitle(buttonTitle.uppercased(), for: .normal)
|
|
||||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Download %@", comment: ""), app.name)
|
|
||||||
cell.bannerView.button.accessibilityValue = buttonTitle
|
|
||||||
|
|
||||||
let progress = AppManager.shared.installationProgress(for: app)
|
|
||||||
cell.bannerView.button.progress = progress
|
|
||||||
|
|
||||||
if let versionDate = app.latestSupportedVersion?.date, versionDate > Date()
|
|
||||||
{
|
|
||||||
cell.bannerView.button.countdownDate = versionDate
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cell.bannerView.button.countdownDate = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cell.bannerView.button.setTitle(NSLocalizedString("OPEN", comment: ""), for: .normal)
|
|
||||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Open %@", comment: ""), app.name)
|
|
||||||
cell.bannerView.button.accessibilityValue = nil
|
|
||||||
cell.bannerView.button.progress = nil
|
|
||||||
cell.bannerView.button.countdownDate = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
dataSource.prefetchHandler = { (storeApp, indexPath, completionHandler) -> Foundation.Operation? in
|
dataSource.prefetchHandler = { (storeApp, indexPath, completionHandler) -> Foundation.Operation? in
|
||||||
let iconURL = storeApp.iconURL
|
let iconURL = storeApp.iconURL
|
||||||
@@ -305,7 +273,7 @@ private extension BrowseViewController
|
|||||||
|
|
||||||
let app = self.dataSource.item(at: indexPath)
|
let app = self.dataSource.item(at: indexPath)
|
||||||
|
|
||||||
if let installedApp = app.installedApp
|
if let installedApp = app.installedApp, !installedApp.isUpdateAvailable
|
||||||
{
|
{
|
||||||
self.open(installedApp)
|
self.open(installedApp)
|
||||||
}
|
}
|
||||||
@@ -323,7 +291,21 @@ private extension BrowseViewController
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
|
if let installedApp = app.installedApp, installedApp.isUpdateAvailable
|
||||||
|
{
|
||||||
|
AppManager.shared.update(installedApp, presentingViewController: self, completionHandler: finish(_:))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AppManager.shared.install(app, presentingViewController: self, completionHandler: finish(_:))
|
||||||
|
}
|
||||||
|
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.collectionView.reloadItems(at: [indexPath])
|
||||||
|
}
|
||||||
|
|
||||||
|
func finish(_ result: Result<InstalledApp, Error>)
|
||||||
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
@@ -332,15 +314,22 @@ private extension BrowseViewController
|
|||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
toastView.opensErrorLog = true
|
toastView.opensErrorLog = true
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
|
|
||||||
case .success: print("Installed app:", app.bundleIdentifier)
|
case .success: print("Installed app:", app.bundleIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.collectionView.reloadItems(at: [indexPath])
|
UIView.performWithoutAnimation {
|
||||||
|
if let indexPath = self.dataSource.fetchedResultsController.indexPath(forObject: app)
|
||||||
|
{
|
||||||
|
self.collectionView.reloadItems(at: [indexPath])
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.collectionView.reloadSections(IndexSet(integer: indexPath.section))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.collectionView.reloadItems(at: [indexPath])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func open(_ installedApp: InstalledApp)
|
func open(_ installedApp: InstalledApp)
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ extension AppBannerView
|
|||||||
case app
|
case app
|
||||||
case source
|
case source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum AppAction
|
||||||
|
{
|
||||||
|
case install
|
||||||
|
case open
|
||||||
|
case update
|
||||||
|
case custom(String)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppBannerView: RSTNibView
|
class AppBannerView: RSTNibView
|
||||||
@@ -111,7 +119,7 @@ class AppBannerView: RSTNibView
|
|||||||
|
|
||||||
extension AppBannerView
|
extension AppBannerView
|
||||||
{
|
{
|
||||||
func configure(for app: AppProtocol)
|
func configure(for app: AppProtocol, action: AppAction? = nil)
|
||||||
{
|
{
|
||||||
struct AppValues
|
struct AppValues
|
||||||
{
|
{
|
||||||
@@ -150,6 +158,94 @@ extension AppBannerView
|
|||||||
self.subtitleLabel.text = NSLocalizedString("Sideloaded", comment: "")
|
self.subtitleLabel.text = NSLocalizedString("Sideloaded", comment: "")
|
||||||
self.accessibilityLabel = values.name
|
self.accessibilityLabel = values.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.buttonLabel.isHidden = true
|
||||||
|
|
||||||
|
let buttonAction: AppAction
|
||||||
|
|
||||||
|
if let action
|
||||||
|
{
|
||||||
|
buttonAction = action
|
||||||
|
}
|
||||||
|
else if let storeApp = app.storeApp
|
||||||
|
{
|
||||||
|
if let installedApp = storeApp.installedApp
|
||||||
|
{
|
||||||
|
// App is installed
|
||||||
|
|
||||||
|
if installedApp.isUpdateAvailable
|
||||||
|
{
|
||||||
|
buttonAction = .update
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buttonAction = .open
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// App is not installed
|
||||||
|
buttonAction = .install
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// App is not from a source, fall back to .open
|
||||||
|
buttonAction = .open
|
||||||
|
}
|
||||||
|
|
||||||
|
switch buttonAction
|
||||||
|
{
|
||||||
|
case .open:
|
||||||
|
let buttonTitle = NSLocalizedString("Open", comment: "")
|
||||||
|
self.button.setTitle(buttonTitle.uppercased(), for: .normal)
|
||||||
|
self.button.accessibilityLabel = String(format: NSLocalizedString("Open %@", comment: ""), values.name)
|
||||||
|
self.button.accessibilityValue = buttonTitle
|
||||||
|
|
||||||
|
self.button.countdownDate = nil
|
||||||
|
|
||||||
|
case .update:
|
||||||
|
let buttonTitle = NSLocalizedString("Update", comment: "")
|
||||||
|
self.button.setTitle(buttonTitle.uppercased(), for: .normal)
|
||||||
|
self.button.accessibilityLabel = String(format: NSLocalizedString("Update %@", comment: ""), values.name)
|
||||||
|
self.button.accessibilityValue = buttonTitle
|
||||||
|
|
||||||
|
self.button.countdownDate = nil
|
||||||
|
|
||||||
|
case .custom(let buttonTitle):
|
||||||
|
self.button.setTitle(buttonTitle, for: .normal)
|
||||||
|
self.button.accessibilityLabel = buttonTitle
|
||||||
|
self.button.accessibilityValue = buttonTitle
|
||||||
|
|
||||||
|
self.button.countdownDate = nil
|
||||||
|
|
||||||
|
case .install:
|
||||||
|
let buttonTitle = NSLocalizedString("Free", comment: "")
|
||||||
|
self.button.setTitle(buttonTitle.uppercased(), for: .normal)
|
||||||
|
self.button.accessibilityLabel = String(format: NSLocalizedString("Download %@", comment: ""), app.name)
|
||||||
|
self.button.accessibilityValue = buttonTitle
|
||||||
|
|
||||||
|
if let versionDate = app.storeApp?.latestSupportedVersion?.date, versionDate > Date()
|
||||||
|
{
|
||||||
|
self.button.countdownDate = versionDate
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.button.countdownDate = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure PillButton is correct size before assigning progress.
|
||||||
|
self.layoutIfNeeded()
|
||||||
|
|
||||||
|
if let progress = AppManager.shared.installationProgress(for: app), progress.fractionCompleted < 1.0
|
||||||
|
{
|
||||||
|
self.button.progress = progress
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.button.progress = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func configure(for source: Source)
|
func configure(for source: Source)
|
||||||
|
|||||||
@@ -289,6 +289,10 @@ extension AppCardCollectionViewCell
|
|||||||
{
|
{
|
||||||
self.screenshots = storeApp.preferredScreenshots()
|
self.screenshots = storeApp.preferredScreenshots()
|
||||||
|
|
||||||
|
// Explicitly set to false to ensure we're starting from a non-activity indicating state.
|
||||||
|
// Otherwise, cell reuse can mess up some cached values.
|
||||||
|
self.bannerView.button.isIndicatingActivity = false
|
||||||
|
|
||||||
self.bannerView.tintColor = storeApp.tintColor
|
self.bannerView.tintColor = storeApp.tintColor
|
||||||
self.bannerView.configure(for: storeApp)
|
self.bannerView.configure(for: storeApp)
|
||||||
|
|
||||||
|
|||||||
@@ -229,7 +229,8 @@ private extension MyAppsViewController
|
|||||||
cell.bannerView.iconImageView.image = nil
|
cell.bannerView.iconImageView.image = nil
|
||||||
cell.bannerView.iconImageView.isIndicatingActivity = true
|
cell.bannerView.iconImageView.isIndicatingActivity = true
|
||||||
|
|
||||||
cell.bannerView.configure(for: app)
|
cell.bannerView.button.isIndicatingActivity = false
|
||||||
|
cell.bannerView.configure(for: app, action: .update)
|
||||||
|
|
||||||
let versionDate = Date().relativeDateString(since: latestSupportedVersion.date)
|
let versionDate = Date().relativeDateString(since: latestSupportedVersion.date)
|
||||||
cell.bannerView.subtitleLabel.text = versionDate
|
cell.bannerView.subtitleLabel.text = versionDate
|
||||||
@@ -247,7 +248,6 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, latestSupportedVersion.localizedVersion, versionDate)
|
cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, latestSupportedVersion.localizedVersion, versionDate)
|
||||||
|
|
||||||
cell.bannerView.button.isIndicatingActivity = false
|
|
||||||
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.updateApp(_:)), for: .primaryActionTriggered)
|
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.updateApp(_:)), for: .primaryActionTriggered)
|
||||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Update %@", comment: ""), installedApp.name)
|
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Update %@", comment: ""), installedApp.name)
|
||||||
|
|
||||||
@@ -262,9 +262,6 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
cell.versionDescriptionTextView.moreButton.addTarget(self, action: #selector(MyAppsViewController.toggleUpdateCellMode(_:)), for: .primaryActionTriggered)
|
cell.versionDescriptionTextView.moreButton.addTarget(self, action: #selector(MyAppsViewController.toggleUpdateCellMode(_:)), for: .primaryActionTriggered)
|
||||||
|
|
||||||
let progress = AppManager.shared.installationProgress(for: app)
|
|
||||||
cell.bannerView.button.progress = progress
|
|
||||||
|
|
||||||
cell.setNeedsLayout()
|
cell.setNeedsLayout()
|
||||||
}
|
}
|
||||||
dataSource.prefetchHandler = { (installedApp, indexPath, completionHandler) in
|
dataSource.prefetchHandler = { (installedApp, indexPath, completionHandler) in
|
||||||
@@ -332,17 +329,6 @@ private extension MyAppsViewController
|
|||||||
cell.deactivateBadge?.transform = CGAffineTransform.identity.scaledBy(x: 0.33, y: 0.33)
|
cell.deactivateBadge?.transform = CGAffineTransform.identity.scaledBy(x: 0.33, y: 0.33)
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.bannerView.configure(for: installedApp)
|
|
||||||
|
|
||||||
cell.bannerView.iconImageView.isIndicatingActivity = true
|
|
||||||
|
|
||||||
cell.bannerView.buttonLabel.isHidden = false
|
|
||||||
cell.bannerView.buttonLabel.text = NSLocalizedString("Expires in", comment: "")
|
|
||||||
|
|
||||||
cell.bannerView.button.isIndicatingActivity = false
|
|
||||||
cell.bannerView.button.removeTarget(self, action: nil, for: .primaryActionTriggered)
|
|
||||||
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.refreshApp(_:)), for: .primaryActionTriggered)
|
|
||||||
|
|
||||||
let currentDate = Date()
|
let currentDate = Date()
|
||||||
|
|
||||||
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
||||||
@@ -357,7 +343,17 @@ private extension MyAppsViewController
|
|||||||
numberOfDaysText = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
numberOfDaysText = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
cell.bannerView.button.isIndicatingActivity = false
|
||||||
|
cell.bannerView.configure(for: installedApp, action: .custom(numberOfDaysText.uppercased()))
|
||||||
|
|
||||||
|
cell.bannerView.iconImageView.isIndicatingActivity = true
|
||||||
|
|
||||||
|
cell.bannerView.buttonLabel.isHidden = false
|
||||||
|
cell.bannerView.buttonLabel.text = NSLocalizedString("Expires in", comment: "")
|
||||||
|
|
||||||
|
cell.bannerView.button.removeTarget(self, action: nil, for: .primaryActionTriggered)
|
||||||
|
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.refreshApp(_:)), for: .primaryActionTriggered)
|
||||||
|
|
||||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
|
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
|
||||||
|
|
||||||
if let storeApp = installedApp.storeApp, storeApp.isPledgeRequired, !storeApp.isPledged
|
if let storeApp = installedApp.storeApp, storeApp.isPledgeRequired, !storeApp.isPledged
|
||||||
@@ -443,11 +439,10 @@ private extension MyAppsViewController
|
|||||||
cell.deactivateBadge?.alpha = 0.0
|
cell.deactivateBadge?.alpha = 0.0
|
||||||
cell.deactivateBadge?.transform = CGAffineTransform.identity.scaledBy(x: 0.5, y: 0.5)
|
cell.deactivateBadge?.transform = CGAffineTransform.identity.scaledBy(x: 0.5, y: 0.5)
|
||||||
|
|
||||||
cell.bannerView.configure(for: installedApp)
|
|
||||||
|
|
||||||
cell.bannerView.button.isIndicatingActivity = false
|
cell.bannerView.button.isIndicatingActivity = false
|
||||||
|
cell.bannerView.configure(for: installedApp, action: .custom(NSLocalizedString("ACTIVATE", comment: "")))
|
||||||
|
|
||||||
cell.bannerView.button.tintColor = tintColor
|
cell.bannerView.button.tintColor = tintColor
|
||||||
cell.bannerView.button.setTitle(NSLocalizedString("ACTIVATE", comment: ""), for: .normal)
|
|
||||||
cell.bannerView.button.removeTarget(self, action: nil, for: .primaryActionTriggered)
|
cell.bannerView.button.removeTarget(self, action: nil, for: .primaryActionTriggered)
|
||||||
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.activateApp(_:)), for: .primaryActionTriggered)
|
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.activateApp(_:)), for: .primaryActionTriggered)
|
||||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Activate %@", comment: ""), installedApp.name)
|
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Activate %@", comment: ""), installedApp.name)
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ extension UpdateCollectionViewCell
|
|||||||
self.contentView.preservesSuperviewLayoutMargins = true
|
self.contentView.preservesSuperviewLayoutMargins = true
|
||||||
|
|
||||||
self.bannerView.backgroundEffectView.isHidden = true
|
self.bannerView.backgroundEffectView.isHidden = true
|
||||||
self.bannerView.button.setTitle(NSLocalizedString("UPDATE", comment: ""), for: .normal)
|
|
||||||
|
|
||||||
self.blurView.layer.cornerRadius = 20
|
self.blurView.layer.cornerRadius = 20
|
||||||
self.blurView.layer.masksToBounds = true
|
self.blurView.layer.masksToBounds = true
|
||||||
|
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ private extension NewsViewController
|
|||||||
let app = self.dataSource.item(at: indexPath)
|
let app = self.dataSource.item(at: indexPath)
|
||||||
guard let storeApp = app.storeApp else { return }
|
guard let storeApp = app.storeApp else { return }
|
||||||
|
|
||||||
if let installedApp = app.storeApp?.installedApp
|
if let installedApp = storeApp.installedApp, !installedApp.isUpdateAvailable
|
||||||
{
|
{
|
||||||
self.open(installedApp)
|
self.open(installedApp)
|
||||||
}
|
}
|
||||||
@@ -359,7 +359,21 @@ private extension NewsViewController
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = AppManager.shared.install(storeApp, presentingViewController: self) { (result) in
|
if let installedApp = storeApp.installedApp, installedApp.isUpdateAvailable
|
||||||
|
{
|
||||||
|
AppManager.shared.update(installedApp, presentingViewController: self, completionHandler: finish(_:))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AppManager.shared.install(storeApp, presentingViewController: self, completionHandler: finish(_:))
|
||||||
|
}
|
||||||
|
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.collectionView.reloadSections(IndexSet(integer: indexPath.section))
|
||||||
|
}
|
||||||
|
|
||||||
|
func finish(_ result: Result<InstalledApp, Error>)
|
||||||
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
@@ -377,10 +391,6 @@ private extension NewsViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UIView.performWithoutAnimation {
|
|
||||||
self.collectionView.reloadSections(IndexSet(integer: indexPath.section))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func open(_ installedApp: InstalledApp)
|
func open(_ installedApp: InstalledApp)
|
||||||
@@ -426,42 +436,13 @@ extension NewsViewController
|
|||||||
footerView.layoutMargins.left = self.view.layoutMargins.left
|
footerView.layoutMargins.left = self.view.layoutMargins.left
|
||||||
footerView.layoutMargins.right = self.view.layoutMargins.right
|
footerView.layoutMargins.right = self.view.layoutMargins.right
|
||||||
|
|
||||||
|
footerView.bannerView.button.isIndicatingActivity = false
|
||||||
footerView.bannerView.configure(for: storeApp)
|
footerView.bannerView.configure(for: storeApp)
|
||||||
|
|
||||||
footerView.bannerView.tintColor = storeApp.tintColor
|
footerView.bannerView.tintColor = storeApp.tintColor
|
||||||
footerView.bannerView.button.addTarget(self, action: #selector(NewsViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
footerView.bannerView.button.addTarget(self, action: #selector(NewsViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
||||||
footerView.tapGestureRecognizer.addTarget(self, action: #selector(NewsViewController.handleTapGesture(_:)))
|
footerView.tapGestureRecognizer.addTarget(self, action: #selector(NewsViewController.handleTapGesture(_:)))
|
||||||
|
|
||||||
footerView.bannerView.button.isIndicatingActivity = false
|
|
||||||
|
|
||||||
if storeApp.installedApp == nil
|
|
||||||
{
|
|
||||||
let buttonTitle = NSLocalizedString("Free", comment: "")
|
|
||||||
footerView.bannerView.button.setTitle(buttonTitle.uppercased(), for: .normal)
|
|
||||||
footerView.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Download %@", comment: ""), storeApp.name)
|
|
||||||
footerView.bannerView.button.accessibilityValue = buttonTitle
|
|
||||||
|
|
||||||
let progress = AppManager.shared.installationProgress(for: storeApp)
|
|
||||||
footerView.bannerView.button.progress = progress
|
|
||||||
|
|
||||||
if let versionDate = storeApp.latestSupportedVersion?.date, versionDate > Date()
|
|
||||||
{
|
|
||||||
footerView.bannerView.button.countdownDate = versionDate
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
footerView.bannerView.button.countdownDate = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
footerView.bannerView.button.setTitle(NSLocalizedString("OPEN", comment: ""), for: .normal)
|
|
||||||
footerView.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Open %@", comment: ""), storeApp.name)
|
|
||||||
footerView.bannerView.button.accessibilityValue = nil
|
|
||||||
footerView.bannerView.button.progress = nil
|
|
||||||
footerView.bannerView.button.countdownDate = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
Nuke.loadImage(with: storeApp.iconURL, into: footerView.bannerView.iconImageView)
|
Nuke.loadImage(with: storeApp.iconURL, into: footerView.bannerView.iconImageView)
|
||||||
|
|
||||||
return footerView
|
return footerView
|
||||||
|
|||||||
@@ -225,43 +225,13 @@ private extension SourceDetailContentViewController
|
|||||||
cell.contentView.layoutMargins = .zero
|
cell.contentView.layoutMargins = .zero
|
||||||
cell.contentView.backgroundColor = .altBackground
|
cell.contentView.backgroundColor = .altBackground
|
||||||
|
|
||||||
|
cell.bannerView.button.isIndicatingActivity = false
|
||||||
cell.bannerView.configure(for: storeApp)
|
cell.bannerView.configure(for: storeApp)
|
||||||
|
|
||||||
cell.bannerView.iconImageView.isIndicatingActivity = true
|
|
||||||
cell.bannerView.buttonLabel.isHidden = true
|
|
||||||
|
|
||||||
cell.bannerView.button.isIndicatingActivity = false
|
|
||||||
cell.bannerView.button.tintColor = storeApp.tintColor
|
cell.bannerView.button.tintColor = storeApp.tintColor
|
||||||
|
cell.bannerView.button.addTarget(self, action: #selector(SourceDetailContentViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
||||||
|
|
||||||
let buttonTitle = NSLocalizedString("Free", comment: "")
|
cell.bannerView.iconImageView.isIndicatingActivity = true
|
||||||
cell.bannerView.button.setTitle(buttonTitle.uppercased(), for: .normal)
|
|
||||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Download %@", comment: ""), storeApp.name)
|
|
||||||
cell.bannerView.button.accessibilityValue = buttonTitle
|
|
||||||
cell.bannerView.button.addTarget(self, action: #selector(SourceDetailContentViewController.addSourceThenDownloadApp(_:)), for: .primaryActionTriggered)
|
|
||||||
|
|
||||||
let progress = AppManager.shared.installationProgress(for: storeApp)
|
|
||||||
cell.bannerView.button.progress = progress
|
|
||||||
|
|
||||||
if let versionDate = storeApp.latestSupportedVersion?.date, versionDate > Date()
|
|
||||||
{
|
|
||||||
cell.bannerView.button.countdownDate = versionDate
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cell.bannerView.button.countdownDate = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure refresh button is correct size.
|
|
||||||
cell.layoutIfNeeded()
|
|
||||||
|
|
||||||
if let progress = AppManager.shared.installationProgress(for: storeApp), progress.fractionCompleted < 1.0
|
|
||||||
{
|
|
||||||
cell.bannerView.button.progress = progress
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cell.bannerView.button.progress = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
dataSource.prefetchHandler = { (storeApp, indexPath, completion) -> Foundation.Operation? in
|
dataSource.prefetchHandler = { (storeApp, indexPath, completion) -> Foundation.Operation? in
|
||||||
return RSTAsyncBlockOperation { (operation) in
|
return RSTAsyncBlockOperation { (operation) in
|
||||||
@@ -404,64 +374,93 @@ extension SourceDetailContentViewController
|
|||||||
|
|
||||||
private extension SourceDetailContentViewController
|
private extension SourceDetailContentViewController
|
||||||
{
|
{
|
||||||
@objc func addSourceThenDownloadApp(_ sender: UIButton)
|
@objc func performAppAction(_ sender: PillButton)
|
||||||
{
|
{
|
||||||
let point = self.collectionView.convert(sender.center, from: sender.superview)
|
let point = self.collectionView.convert(sender.center, from: sender.superview)
|
||||||
guard let indexPath = self.collectionView.indexPathForItem(at: point) else { return }
|
guard let indexPath = self.collectionView.indexPathForItem(at: point) else { return }
|
||||||
|
|
||||||
sender.isIndicatingActivity = true
|
|
||||||
|
|
||||||
let storeApp = self.dataSource.item(at: indexPath) as! StoreApp
|
let storeApp = self.dataSource.item(at: indexPath) as! StoreApp
|
||||||
|
|
||||||
Task<Void, Never> {
|
if let installedApp = storeApp.installedApp, !installedApp.isUpdateAvailable
|
||||||
|
{
|
||||||
|
self.open(installedApp)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sender.isIndicatingActivity = true
|
||||||
|
|
||||||
|
Task<Void, Never> {
|
||||||
|
await self.addSourceThenDownloadApp(storeApp)
|
||||||
|
sender.isIndicatingActivity = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addSourceThenDownloadApp(_ storeApp: StoreApp) async
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let isAdded = try await self.source.isAdded
|
||||||
|
if !isAdded
|
||||||
|
{
|
||||||
|
let message = String(format: NSLocalizedString("You must add this source before you can install apps from it.\n\n“%@” will begin downloading once it has been added.", comment: ""), storeApp.name)
|
||||||
|
try await AppManager.shared.add(self.source, message: message, presentingViewController: self)
|
||||||
|
}
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let isAdded = try await self.source.isAdded
|
try await self.downloadApp(storeApp)
|
||||||
if !isAdded
|
|
||||||
{
|
|
||||||
let message = String(format: NSLocalizedString("You must add this source before you can install apps from it.\n\n“%@” will begin downloading once it has been added.", comment: ""), storeApp.name)
|
|
||||||
try await AppManager.shared.add(self.source, message: message, presentingViewController: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
try await self.downloadApp(storeApp)
|
|
||||||
}
|
|
||||||
catch OperationError.cancelled {}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
let toastView = ToastView(error: error)
|
|
||||||
toastView.opensErrorLog = true
|
|
||||||
toastView.show(in: self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch is CancellationError {}
|
catch is CancellationError {}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
await self.presentAlert(title: NSLocalizedString("Unable to Add Source", comment: ""), message: error.localizedDescription)
|
let toastView = ToastView(error: error)
|
||||||
|
toastView.opensErrorLog = true
|
||||||
|
toastView.show(in: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
sender.isIndicatingActivity = false
|
|
||||||
self.collectionView.reloadSections([Section.featuredApps.rawValue])
|
|
||||||
}
|
}
|
||||||
|
catch is CancellationError {}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await self.presentAlert(title: NSLocalizedString("Unable to Add Source", comment: ""), message: error.localizedDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.collectionView.reloadSections([Section.featuredApps.rawValue])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func downloadApp(_ storeApp: StoreApp) async throws
|
func downloadApp(_ storeApp: StoreApp) async throws
|
||||||
{
|
{
|
||||||
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
||||||
AppManager.shared.install(storeApp, presentingViewController: self) { result in
|
if let installedApp = storeApp.installedApp, installedApp.isUpdateAvailable
|
||||||
continuation.resume(with: result.map { _ in })
|
{
|
||||||
|
AppManager.shared.update(installedApp, presentingViewController: self) { result in
|
||||||
|
continuation.resume(with: result.map { _ in () })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AppManager.shared.install(storeApp, presentingViewController: self) { result in
|
||||||
|
continuation.resume(with: result.map { _ in () })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let index = self.appsDataSource.items.firstIndex(of: storeApp) else {
|
UIView.performWithoutAnimation {
|
||||||
self.collectionView.reloadSections([Section.featuredApps.rawValue])
|
guard let index = self.appsDataSource.items.firstIndex(of: storeApp) else {
|
||||||
return
|
self.collectionView.reloadSections([Section.featuredApps.rawValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let indexPath = IndexPath(item: index, section: Section.featuredApps.rawValue)
|
||||||
|
self.collectionView.reloadItems(at: [indexPath])
|
||||||
}
|
}
|
||||||
|
|
||||||
let indexPath = IndexPath(item: index, section: Section.featuredApps.rawValue)
|
|
||||||
self.collectionView.reloadItems(at: [indexPath])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func open(_ installedApp: InstalledApp)
|
||||||
|
{
|
||||||
|
UIApplication.shared.open(installedApp.openAppURL)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SourceDetailContentViewController: ScrollableContentViewController
|
extension SourceDetailContentViewController: ScrollableContentViewController
|
||||||
|
|||||||
@@ -317,6 +317,13 @@ public extension InstalledApp
|
|||||||
let openAppURL = URL(string: "altstore-" + app.bundleIdentifier + "://")!
|
let openAppURL = URL(string: "altstore-" + app.bundleIdentifier + "://")!
|
||||||
return openAppURL
|
return openAppURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isUpdateAvailable: Bool {
|
||||||
|
guard let storeApp = self.storeApp, let latestVersion = storeApp.latestSupportedVersion else { return false }
|
||||||
|
|
||||||
|
let isUpdateAvailable = !self.matches(latestVersion)
|
||||||
|
return isUpdateAvailable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension InstalledApp
|
public extension InstalledApp
|
||||||
|
|||||||
Reference in New Issue
Block a user