Shows source errors in SourcesViewController

This commit is contained in:
Riley Testut
2020-08-27 16:39:03 -07:00
parent b7564207b3
commit 4c3d33efdc
13 changed files with 158 additions and 17 deletions

View File

@@ -77,6 +77,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
ServerManager.shared.startDiscovering()
SecureValueTransformer.register()
UserDefaults.standard.registerDefaults()
if UserDefaults.standard.firstLaunch == nil

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16092.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wKh-xq-NuP">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wKh-xq-NuP">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16082.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -38,6 +38,7 @@
<segue destination="faz-B4-Sub" kind="relationship" relationship="viewControllers" id="dXz-Tu-hW8"/>
<segue destination="3Ew-ox-i4n" kind="relationship" relationship="viewControllers" id="zii-dF-qEt"/>
<segue destination="p3d-dP-Swg" kind="relationship" relationship="viewControllers" id="4Nf-rL-P4c"/>
<segue destination="Qo4-72-Hmr" kind="presentation" identifier="presentSources" id="Qd6-ba-dIo"/>
</connections>
</tabBarController>
<placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
@@ -1004,7 +1005,7 @@ World</string>
</barButtonItem>
<barButtonItem key="rightBarButtonItem" style="done" systemItem="done" id="NQF-u2-PZv">
<connections>
<segue destination="zjS-Nr-VTw" kind="unwind" unwindAction="unwindToBrowseViewController:" id="VFy-hV-mNV"/>
<segue destination="zjS-Nr-VTw" kind="unwind" unwindAction="unwindFromSourcesViewController:" id="la1-dJ-UhL"/>
</connections>
</barButtonItem>
</navigationItem>
@@ -1034,6 +1035,7 @@ World</string>
</scene>
</scenes>
<inferredMetricsTieBreakers>
<segue reference="Qd6-ba-dIo"/>
<segue reference="cnd-KK-o60"/>
</inferredMetricsTieBreakers>
<color key="tintColor" name="Primary"/>
@@ -1044,7 +1046,7 @@ World</string>
<image name="News" width="19" height="20"/>
<image name="Settings" width="20" height="20"/>
<namedColor name="Background">
<color red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.0040000001899898052" green="0.50199997425079346" blue="0.51800000667572021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="BlurTint">
<color red="1" green="1" blue="1" alpha="0.30000001192092896" colorSpace="custom" customColorSpace="sRGB"/>

View File

@@ -59,7 +59,7 @@ class BrowseViewController: UICollectionViewController
self.updateDataSource()
}
@IBAction private func unwindToBrowseViewController(_ segue: UIStoryboardSegue)
@IBAction private func unwindFromSourcesViewController(_ segue: UIStoryboardSegue)
{
self.fetchSource()
}
@@ -199,6 +199,7 @@ private extension BrowseViewController
if self.dataSource.itemCount > 0
{
let toastView = ToastView(error: error)
toastView.addTarget(nil, action: #selector(TabBarController.presentSources), for: .touchUpInside)
toastView.show(in: self)
}

View File

@@ -10,7 +10,8 @@ import UIKit
class BannerCollectionViewCell: UICollectionViewCell
{
@IBOutlet var bannerView: AppBannerView!
private(set) var errorBadge: UIView?
@IBOutlet private(set) var bannerView: AppBannerView!
override func awakeFromNib()
{
@@ -18,5 +19,36 @@ class BannerCollectionViewCell: UICollectionViewCell
self.contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.contentView.preservesSuperviewLayoutMargins = true
if #available(iOS 13.0, *)
{
let errorBadge = UIView()
errorBadge.translatesAutoresizingMaskIntoConstraints = false
errorBadge.isHidden = true
self.addSubview(errorBadge)
// Solid background to make the X opaque white.
let backgroundView = UIView()
backgroundView.translatesAutoresizingMaskIntoConstraints = false
backgroundView.backgroundColor = .white
errorBadge.addSubview(backgroundView)
let badgeView = UIImageView(image: UIImage(systemName: "exclamationmark.circle.fill"))
badgeView.preferredSymbolConfiguration = UIImage.SymbolConfiguration(scale: .large)
badgeView.tintColor = .systemRed
errorBadge.addSubview(badgeView, pinningEdgesWith: .zero)
NSLayoutConstraint.activate([
errorBadge.centerXAnchor.constraint(equalTo: self.bannerView.trailingAnchor, constant: -5),
errorBadge.centerYAnchor.constraint(equalTo: self.bannerView.topAnchor, constant: 5),
backgroundView.centerXAnchor.constraint(equalTo: badgeView.centerXAnchor),
backgroundView.centerYAnchor.constraint(equalTo: badgeView.centerYAnchor),
backgroundView.widthAnchor.constraint(equalTo: badgeView.widthAnchor, multiplier: 0.5),
backgroundView.heightAnchor.constraint(equalTo: badgeView.heightAnchor, multiplier: 0.5)
])
self.errorBadge = errorBadge
}
}
}

View File

@@ -1,5 +1,5 @@
//
// NSError+LocalizedFailure.swift
// NSError+AltStore.swift
// AltStore
//
// Created by Riley Testut on 3/11/20.
@@ -27,6 +27,21 @@ extension NSError
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
return error
}
func sanitizedForCoreData() -> NSError
{
var userInfo = self.userInfo
userInfo[NSLocalizedFailureErrorKey] = self.localizedFailure
userInfo[NSLocalizedDescriptionKey] = self.localizedDescription
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
// Remove non-ObjC-compliant userInfo values.
userInfo["NSCodingPath"] = nil
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
return error
}
}
protocol ALTLocalizedError: LocalizedError, CustomNSError

View File

@@ -220,6 +220,7 @@ extension AppManager
case .success(let source): fetchedSources.insert(source)
case .failure(let error):
let source = managedObjectContext.object(with: source.objectID) as! Source
source.error = (error as NSError).sanitizedForCoreData()
errors[source] = error
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16117.1" systemVersion="19D76" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19G73" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Account" representedClassName="Account" syncable="YES">
<attribute name="appleID" attributeType="String"/>
<attribute name="firstName" attributeType="String"/>
@@ -104,6 +104,7 @@
</uniquenessConstraints>
</entity>
<entity name="Source" representedClassName="Source" syncable="YES">
<attribute name="error" optional="YES" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="name" attributeType="String"/>
<attribute name="sourceURL" attributeType="URI"/>
@@ -166,7 +167,7 @@
<element name="NewsItem" positionX="-45" positionY="126" width="128" height="238"/>
<element name="PatreonAccount" positionX="-45" positionY="117" width="128" height="105"/>
<element name="RefreshAttempt" positionX="-45" positionY="117" width="128" height="105"/>
<element name="Source" positionX="-45" positionY="99" width="128" height="118"/>
<element name="Source" positionX="-45" positionY="99" width="128" height="133"/>
<element name="StoreApp" positionX="-63" positionY="-18" width="128" height="343"/>
<element name="Team" positionX="-45" positionY="81" width="128" height="148"/>
</elements>

View File

@@ -0,0 +1,26 @@
//
// SecureValueTransformer.swift
// AltStore
//
// Created by Riley Testut on 8/18/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
@objc(ALTSecureValueTransformer)
final class SecureValueTransformer: NSSecureUnarchiveFromDataTransformer
{
static let name = NSValueTransformerName(rawValue: "ALTSecureValueTransformer")
override static var allowedTopLevelClasses: [AnyClass] {
let allowedClasses = super.allowedTopLevelClasses + [NSError.self]
return allowedClasses
}
public static func register()
{
let transformer = SecureValueTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
}

View File

@@ -43,6 +43,8 @@ class Source: NSManagedObject, Fetchable, Decodable
@NSManaged var identifier: String
@NSManaged var sourceURL: URL
@NSManaged var error: NSError?
/* Non-Core Data Properties */
var userInfo: [ALTSourceUserInfoKey: String]?

View File

@@ -99,6 +99,11 @@ class NewsViewController: UICollectionViewController
self.collectionView.contentInset.bottom = 20
}
}
@IBAction private func unwindFromSourcesViewController(_ segue: UIStoryboardSegue)
{
self.fetchSource()
}
}
private extension NewsViewController
@@ -198,6 +203,7 @@ private extension NewsViewController
if self.dataSource.itemCount > 0
{
let toastView = ToastView(error: error)
toastView.addTarget(nil, action: #selector(TabBarController.presentSources), for: .touchUpInside)
toastView.show(in: self)
}

View File

@@ -53,6 +53,8 @@ private extension SourcesViewController
cell.bannerView.subtitleLabel.text = source.sourceURL.absoluteString
cell.bannerView.subtitleLabel.numberOfLines = 2
cell.errorBadge?.isHidden = (source.error == nil)
// Make sure refresh button is correct size.
cell.layoutIfNeeded()
}
@@ -98,6 +100,29 @@ private extension SourcesViewController
self.present(alertController, animated: true, completion: nil)
}
func present(_ error: Error)
{
let nsError = error as NSError
let message = nsError.userInfo[NSDebugDescriptionErrorKey] as? String ?? nsError.localizedRecoverySuggestion
let alertController = UIAlertController(title: error.localizedDescription, message: message, preferredStyle: .alert)
alertController.addAction(.ok)
self.present(alertController, animated: true, completion: nil)
}
}
extension SourcesViewController
{
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
self.collectionView.deselectItem(at: indexPath, animated: true)
let source = self.dataSource.item(at: indexPath)
guard let error = source.error else { return }
self.present(error)
}
}
extension SourcesViewController: UICollectionViewDelegateFlowLayout
@@ -134,9 +159,13 @@ extension SourcesViewController
override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration?
{
let source = self.dataSource.item(at: indexPath)
guard source.identifier != Source.altStoreIdentifier else { return nil }
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) { (suggestedActions) -> UIMenu? in
let viewErrorAction = UIAction(title: NSLocalizedString("View Error", comment: ""), image: UIImage(systemName: "exclamationmark.circle")) { (action) in
guard let error = source.error else { return }
self.present(error)
}
let deleteAction = UIAction(title: NSLocalizedString("Remove", comment: ""), image: UIImage(systemName: "trash"), attributes: [.destructive]) { (action) in
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
@@ -153,8 +182,20 @@ extension SourcesViewController
}
}
}
var actions: [UIAction] = []
if source.error != nil
{
actions.append(viewErrorAction)
}
if source.identifier != Source.altStoreIdentifier
{
actions.append(deleteAction)
}
let menu = UIMenu(title: "", children: [deleteAction])
let menu = UIMenu(title: "", children: actions)
return menu
}
}

View File

@@ -30,6 +30,14 @@ class TabBarController: UITabBarController
}
}
extension TabBarController
{
@objc func presentSources(_ sender: Any)
{
self.performSegue(withIdentifier: "presentSources", sender: sender)
}
}
private extension TabBarController
{
@objc func openPatreonSettings(_ notification: Notification)