Revises Patreon UI

This commit is contained in:
Riley Testut
2019-09-05 15:37:58 -07:00
parent 6635565a1c
commit e6bfdfdaee
6 changed files with 387 additions and 80 deletions

View File

@@ -180,7 +180,6 @@
BFD52C2222A1A9EC000B7ED1 /* cnary.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1F22A1A9EC000B7ED1 /* cnary.c */; }; BFD52C2222A1A9EC000B7ED1 /* cnary.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1F22A1A9EC000B7ED1 /* cnary.c */; };
BFD5D6E8230CC961007955AB /* PatreonAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6E7230CC961007955AB /* PatreonAPI.swift */; }; BFD5D6E8230CC961007955AB /* PatreonAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6E7230CC961007955AB /* PatreonAPI.swift */; };
BFD5D6EA230CCAE5007955AB /* PatreonAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6E9230CCAE5007955AB /* PatreonAccount.swift */; }; BFD5D6EA230CCAE5007955AB /* PatreonAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6E9230CCAE5007955AB /* PatreonAccount.swift */; };
BFD5D6EC230CCDA1007955AB /* PatreonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6EB230CCDA1007955AB /* PatreonViewController.swift */; };
BFD5D6EE230D8A86007955AB /* Patron.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6ED230D8A86007955AB /* Patron.swift */; }; BFD5D6EE230D8A86007955AB /* Patron.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6ED230D8A86007955AB /* Patron.swift */; };
BFD5D6F2230DD974007955AB /* Benefit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6F1230DD974007955AB /* Benefit.swift */; }; BFD5D6F2230DD974007955AB /* Benefit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6F1230DD974007955AB /* Benefit.swift */; };
BFD5D6F4230DDB0A007955AB /* Campaign.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6F3230DDB0A007955AB /* Campaign.swift */; }; BFD5D6F4230DDB0A007955AB /* Campaign.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6F3230DDB0A007955AB /* Campaign.swift */; };
@@ -208,6 +207,9 @@
BFE6326822A858F300F30809 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326722A858F300F30809 /* Account.swift */; }; BFE6326822A858F300F30809 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326722A858F300F30809 /* Account.swift */; };
BFE6326A22A85DAF00F30809 /* ReplaceCertificateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326922A85DAF00F30809 /* ReplaceCertificateViewController.swift */; }; BFE6326A22A85DAF00F30809 /* ReplaceCertificateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326922A85DAF00F30809 /* ReplaceCertificateViewController.swift */; };
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */; }; BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */; };
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B68D23219520007A79E1 /* PatreonViewController.swift */; };
BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B68F23219C6D007A79E1 /* PatreonComponents.swift */; };
BFF0B6922321A305007A79E1 /* AboutPatreonHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */; };
DBAC68F8EC03F4A41D62EDE1 /* Pods_AltStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1039C07E517311FC499A0B64 /* Pods_AltStore.framework */; }; DBAC68F8EC03F4A41D62EDE1 /* Pods_AltStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1039C07E517311FC499A0B64 /* Pods_AltStore.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -456,7 +458,6 @@
BFD52C1F22A1A9EC000B7ED1 /* cnary.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = cnary.c; path = Dependencies/libplist/libcnary/cnary.c; sourceTree = SOURCE_ROOT; }; BFD52C1F22A1A9EC000B7ED1 /* cnary.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = cnary.c; path = Dependencies/libplist/libcnary/cnary.c; sourceTree = SOURCE_ROOT; };
BFD5D6E7230CC961007955AB /* PatreonAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonAPI.swift; sourceTree = "<group>"; }; BFD5D6E7230CC961007955AB /* PatreonAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonAPI.swift; sourceTree = "<group>"; };
BFD5D6E9230CCAE5007955AB /* PatreonAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonAccount.swift; sourceTree = "<group>"; }; BFD5D6E9230CCAE5007955AB /* PatreonAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonAccount.swift; sourceTree = "<group>"; };
BFD5D6EB230CCDA1007955AB /* PatreonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonViewController.swift; sourceTree = "<group>"; };
BFD5D6ED230D8A86007955AB /* Patron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Patron.swift; sourceTree = "<group>"; }; BFD5D6ED230D8A86007955AB /* Patron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Patron.swift; sourceTree = "<group>"; };
BFD5D6F1230DD974007955AB /* Benefit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Benefit.swift; sourceTree = "<group>"; }; BFD5D6F1230DD974007955AB /* Benefit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Benefit.swift; sourceTree = "<group>"; };
BFD5D6F3230DDB0A007955AB /* Campaign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Campaign.swift; sourceTree = "<group>"; }; BFD5D6F3230DDB0A007955AB /* Campaign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Campaign.swift; sourceTree = "<group>"; };
@@ -484,6 +485,9 @@
BFE6326722A858F300F30809 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; }; BFE6326722A858F300F30809 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
BFE6326922A85DAF00F30809 /* ReplaceCertificateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplaceCertificateViewController.swift; sourceTree = "<group>"; }; BFE6326922A85DAF00F30809 /* ReplaceCertificateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplaceCertificateViewController.swift; sourceTree = "<group>"; };
BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationOperation.swift; sourceTree = "<group>"; }; BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationOperation.swift; sourceTree = "<group>"; };
BFF0B68D23219520007A79E1 /* PatreonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonViewController.swift; sourceTree = "<group>"; };
BFF0B68F23219C6D007A79E1 /* PatreonComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonComponents.swift; sourceTree = "<group>"; };
BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AboutPatreonHeaderView.xib; sourceTree = "<group>"; };
EA79A60285C6AF5848AA16E9 /* Pods-AltStore.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltStore.debug.xcconfig"; path = "Target Support Files/Pods-AltStore/Pods-AltStore.debug.xcconfig"; sourceTree = "<group>"; }; EA79A60285C6AF5848AA16E9 /* Pods-AltStore.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltStore.debug.xcconfig"; path = "Target Support Files/Pods-AltStore/Pods-AltStore.debug.xcconfig"; sourceTree = "<group>"; };
FC3822AB1C4CF1D4CDF7445D /* Pods_AltServer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltServer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FC3822AB1C4CF1D4CDF7445D /* Pods_AltServer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltServer.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -971,7 +975,9 @@
BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */, BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */,
BFE6073B231AE1E7002B0E8E /* SettingsHeaderFooterView.xib */, BFE6073B231AE1E7002B0E8E /* SettingsHeaderFooterView.xib */,
BF02419522F2199300129732 /* RefreshAttemptsViewController.swift */, BF02419522F2199300129732 /* RefreshAttemptsViewController.swift */,
BFD5D6EB230CCDA1007955AB /* PatreonViewController.swift */, BFF0B68D23219520007A79E1 /* PatreonViewController.swift */,
BFF0B68F23219C6D007A79E1 /* PatreonComponents.swift */,
BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */,
); );
path = Settings; path = Settings;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1223,6 +1229,7 @@
BFD2477A2284B9A700981D42 /* LaunchScreen.storyboard in Resources */, BFD2477A2284B9A700981D42 /* LaunchScreen.storyboard in Resources */,
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */, BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */,
BFD247772284B9A700981D42 /* Assets.xcassets in Resources */, BFD247772284B9A700981D42 /* Assets.xcassets in Resources */,
BFF0B6922321A305007A79E1 /* AboutPatreonHeaderView.xib in Resources */,
BFB6B22423187A3D0022A802 /* NewsCollectionViewCell.xib in Resources */, BFB6B22423187A3D0022A802 /* NewsCollectionViewCell.xib in Resources */,
BFD247752284B9A500981D42 /* Main.storyboard in Resources */, BFD247752284B9A500981D42 /* Main.storyboard in Resources */,
BFDB5B2622EFBBEA00F74113 /* BrowseCollectionViewCell.xib in Resources */, BFDB5B2622EFBBEA00F74113 /* BrowseCollectionViewCell.xib in Resources */,
@@ -1393,7 +1400,6 @@
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */, BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */,
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */, BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */,
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */, BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */,
BFD5D6EC230CCDA1007955AB /* PatreonViewController.swift in Sources */,
BFE6325E22A8497000F30809 /* SelectTeamViewController.swift in Sources */, BFE6325E22A8497000F30809 /* SelectTeamViewController.swift in Sources */,
BFD2478F2284C8F900981D42 /* Button.swift in Sources */, BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
BFD5D6F6230DDB12007955AB /* Tier.swift in Sources */, BFD5D6F6230DDB12007955AB /* Tier.swift in Sources */,
@@ -1416,6 +1422,7 @@
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */, BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */,
BFE338DD22F0E7F3002E24B9 /* Source.swift in Sources */, BFE338DD22F0E7F3002E24B9 /* Source.swift in Sources */,
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */, BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */,
BFD5D6EA230CCAE5007955AB /* PatreonAccount.swift in Sources */, BFD5D6EA230CCAE5007955AB /* PatreonAccount.swift in Sources */,
BFE6326822A858F300F30809 /* Account.swift in Sources */, BFE6326822A858F300F30809 /* Account.swift in Sources */,
BFE6326622A857C200F30809 /* Team.swift in Sources */, BFE6326622A857C200F30809 /* Team.swift in Sources */,
@@ -1458,6 +1465,7 @@
BFDB6A0522A9AFB2007EA6D6 /* Fetchable.swift in Sources */, BFDB6A0522A9AFB2007EA6D6 /* Fetchable.swift in Sources */,
BFD2479F2284FBD000981D42 /* UIColor+AltStore.swift in Sources */, BFD2479F2284FBD000981D42 /* UIColor+AltStore.swift in Sources */,
BFDB5B1622EE90D300F74113 /* Date+RelativeDate.swift in Sources */, BFDB5B1622EE90D300F74113 /* Date+RelativeDate.swift in Sources */,
BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */,
BF770E5622BC3C03002A40FE /* Server.swift in Sources */, BF770E5622BC3C03002A40FE /* Server.swift in Sources */,
BF43003022A71C960051E2BC /* UserDefaults+AltStore.swift in Sources */, BF43003022A71C960051E2BC /* UserDefaults+AltStore.swift in Sources */,
); );

View File

@@ -12,6 +12,8 @@ import Roxas
class NavigationBar: UINavigationBar class NavigationBar: UINavigationBar
{ {
@IBInspectable var automaticallyAdjustsItemPositions: Bool = true
private let backgroundColorView = UIView() private let backgroundColorView = UIView()
override init(frame: CGRect) override init(frame: CGRect)
@@ -55,11 +57,14 @@ class NavigationBar: UINavigationBar
self.insertSubview(self.backgroundColorView, at: 1) self.insertSubview(self.backgroundColorView, at: 1)
} }
// We can't easily shift just the back button up, so we shift the entire content view slightly. if self.automaticallyAdjustsItemPositions
for contentView in self.subviews
{ {
guard NSStringFromClass(type(of: contentView)).contains("ContentView") else { continue } // We can't easily shift just the back button up, so we shift the entire content view slightly.
contentView.center.y -= 2 for contentView in self.subviews
{
guard NSStringFromClass(type(of: contentView)).contains("ContentView") else { continue }
contentView.center.y -= 2
}
} }
} }
} }

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionReusableView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="AboutHeader" id="xq2-Pl-zaG" customClass="AboutPatreonHeaderView" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="284"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="XiA-Jf-XMp">
<rect key="frame" x="20" y="0.0" width="335" height="254"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bju-6l-3iA">
<rect key="frame" x="0.0" y="0.0" width="335" height="183"/>
<string key="text">Hey y'all!
If you'd like to support my work, you can donate here. In return, you'll gain access to beta versions of all of my apps and be among the first to try the latest features.
Thanks for all of your support 💜
Riley</string>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="0.75" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yEi-L6-kQ8">
<rect key="frame" x="0.0" y="203" width="335" height="51"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="51" id="l4o-vb-cMy"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="19"/>
<state key="normal" title="Support me">
<color key="titleColor" name="Green"/>
</state>
</button>
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstItem="XiA-Jf-XMp" firstAttribute="leading" secondItem="xq2-Pl-zaG" secondAttribute="leading" constant="20" id="Ac6-gO-UtF"/>
<constraint firstAttribute="trailing" secondItem="XiA-Jf-XMp" secondAttribute="trailing" constant="20" id="iAM-Q4-hP4"/>
<constraint firstItem="XiA-Jf-XMp" firstAttribute="top" secondItem="xq2-Pl-zaG" secondAttribute="top" id="j8p-JX-Dcz"/>
<constraint firstAttribute="bottom" secondItem="XiA-Jf-XMp" secondAttribute="bottom" constant="30" id="qlR-EG-83F"/>
</constraints>
<connections>
<outlet property="supportButton" destination="yEi-L6-kQ8" id="Dzo-vd-SnD"/>
</connections>
</collectionReusableView>
</objects>
<resources>
<namedColor name="Green">
<color red="0.22352941176470589" green="0.49411764705882355" blue="0.396078431372549" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>

View File

@@ -0,0 +1,68 @@
//
// PatreonComponents.swift
// AltStore
//
// Created by Riley Testut on 9/5/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
class PatronCollectionViewCell: UICollectionViewCell
{
@IBOutlet var textLabel: UILabel!
}
class PatronsHeaderView: UICollectionReusableView
{
let textLabel = UILabel()
override init(frame: CGRect)
{
super.init(frame: frame)
self.textLabel.font = UIFont.boldSystemFont(ofSize: 17)
self.textLabel.textColor = .white
self.addSubview(self.textLabel, pinningEdgesWith: UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class PatronsFooterView: UICollectionReusableView
{
let button = UIButton(type: .system)
override init(frame: CGRect)
{
super.init(frame: frame)
self.button.translatesAutoresizingMaskIntoConstraints = false
self.button.activityIndicatorView.style = .white
self.button.titleLabel?.textColor = .white
self.addSubview(self.button)
NSLayoutConstraint.activate([self.button.centerXAnchor.constraint(equalTo: self.centerXAnchor),
self.button.centerYAnchor.constraint(equalTo: self.centerYAnchor)])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class AboutPatreonHeaderView: UICollectionReusableView
{
@IBOutlet var supportButton: UIButton!
override func awakeFromNib()
{
super.awakeFromNib()
self.supportButton.clipsToBounds = true
self.supportButton.layer.cornerRadius = 16
}
}

View File

@@ -2,18 +2,33 @@
// PatreonViewController.swift // PatreonViewController.swift
// AltStore // AltStore
// //
// Created by Riley Testut on 8/20/19. // Created by Riley Testut on 9/5/19.
// Copyright © 2019 Riley Testut. All rights reserved. // Copyright © 2019 Riley Testut. All rights reserved.
// //
import UIKit import UIKit
import SafariServices
import AuthenticationServices import AuthenticationServices
import Roxas import Roxas
class PatreonViewController: UITableViewController extension PatreonViewController
{
private enum Section: Int, CaseIterable
{
case about
case patrons
}
}
class PatreonViewController: UICollectionViewController
{ {
private lazy var dataSource = self.makeDataSource() private lazy var dataSource = self.makeDataSource()
private lazy var patronsDataSource = self.makePatronsDataSource()
private var prototypeAboutHeader: AboutPatreonHeaderView!
private var patronsResult: Result<[Patron], Error>?
@IBOutlet private var signInButton: UIBarButtonItem! @IBOutlet private var signInButton: UIBarButtonItem!
@IBOutlet private var signOutButton: UIBarButtonItem! @IBOutlet private var signOutButton: UIBarButtonItem!
@@ -22,7 +37,14 @@ class PatreonViewController: UITableViewController
{ {
super.viewDidLoad() super.viewDidLoad()
self.tableView.dataSource = self.dataSource let aboutHeaderNib = UINib(nibName: "AboutPatreonHeaderView", bundle: nil)
self.prototypeAboutHeader = aboutHeaderNib.instantiate(withOwner: nil, options: nil)[0] as? AboutPatreonHeaderView
self.collectionView.dataSource = self.dataSource
self.collectionView.register(aboutHeaderNib, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "AboutHeader")
self.collectionView.register(PatronsHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "PatronsHeader")
self.collectionView.register(PatronsFooterView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "PatronsFooter")
self.update() self.update()
} }
@@ -32,19 +54,45 @@ class PatreonViewController: UITableViewController
super.viewWillAppear(animated) super.viewWillAppear(animated)
self.fetchPatrons() self.fetchPatrons()
self.update()
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
let layout = self.collectionViewLayout as! UICollectionViewFlowLayout
var itemWidth = (self.collectionView.bounds.width - (layout.sectionInset.left + layout.sectionInset.right + layout.minimumInteritemSpacing)) / 2
itemWidth.round(.down)
layout.itemSize = CGSize(width: itemWidth, height: layout.itemSize.height)
} }
} }
private extension PatreonViewController private extension PatreonViewController
{ {
func makeDataSource() -> RSTArrayTableViewDataSource<Patron> func makeDataSource() -> RSTCompositeCollectionViewDataSource<Patron>
{ {
let dataSource = RSTArrayTableViewDataSource<Patron>(items: []) let aboutDataSource = RSTDynamicCollectionViewDataSource<Patron>()
dataSource.cellConfigurationHandler = { (cell, patron, indexPath) in aboutDataSource.numberOfSectionsHandler = { 1 }
cell.textLabel?.text = patron.name aboutDataSource.numberOfItemsHandler = { _ in 0 }
let dataSource = RSTCompositeCollectionViewDataSource<Patron>(dataSources: [aboutDataSource, self.patronsDataSource])
dataSource.proxy = self
return dataSource
}
func makePatronsDataSource() -> RSTArrayCollectionViewDataSource<Patron>
{
let patronsDataSource = RSTArrayCollectionViewDataSource<Patron>(items: [])
patronsDataSource.cellConfigurationHandler = { (cell, patron, indexPath) in
let cell = cell as! PatronCollectionViewCell
cell.textLabel.text = patron.name
} }
return dataSource return patronsDataSource
} }
func update() func update()
@@ -58,28 +106,48 @@ private extension PatreonViewController
self.navigationItem.rightBarButtonItem = self.signInButton self.navigationItem.rightBarButtonItem = self.signInButton
} }
} }
func fetchPatrons()
{
PatreonAPI.shared.fetchPatrons { (result) in
do
{
let patrons = try result.get()
self.dataSource.items = patrons
}
catch
{
DispatchQueue.main.async {
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
}
}
}
}
} }
private extension PatreonViewController private extension PatreonViewController
{ {
@objc func fetchPatrons()
{
if let result = self.patronsResult, case .failure = result
{
self.patronsResult = nil
self.collectionView.reloadData()
}
PatreonAPI.shared.fetchPatrons { (result) in
self.patronsResult = result
do
{
let patrons = try result.get()
let sortedPatrons = patrons.sorted { $0.name < $1.name }
self.patronsDataSource.items = sortedPatrons
}
catch
{
print("Failed to fetch patrons:", error)
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
}
@objc func openPatreonURL(_ sender: UIButton)
{
let patreonURL = URL(string: "https://www.patreon.com/rileytestut")!
let safariViewController = SFSafariViewController(url: patreonURL)
safariViewController.preferredControlTintColor = self.view.tintColor
self.present(safariViewController, animated: true, completion: nil)
}
@IBAction func authenticate(_ sender: UIBarButtonItem) @IBAction func authenticate(_ sender: UIBarButtonItem)
{ {
PatreonAPI.shared.authenticate { (result) in PatreonAPI.shared.authenticate { (result) in
@@ -108,22 +176,100 @@ private extension PatreonViewController
@IBAction func signOut(_ sender: UIBarButtonItem) @IBAction func signOut(_ sender: UIBarButtonItem)
{ {
PatreonAPI.shared.signOut { (result) in func signOut()
do {
{ PatreonAPI.shared.signOut { (result) in
try result.get() do
{
DispatchQueue.main.async { try result.get()
self.update()
DispatchQueue.main.async {
self.update()
}
}
catch
{
DispatchQueue.main.async {
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
}
} }
} }
catch }
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to sign out?", comment: ""), message: NSLocalizedString("You will no longer have access to beta versions of apps once you sign out.", comment: ""), preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Sign Out", comment: ""), style: .destructive) { _ in signOut() })
alertController.addAction(.cancel)
self.present(alertController, animated: true, completion: nil)
}
}
extension PatreonViewController
{
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
{
let section = Section.allCases[indexPath.section]
switch section
{
case .about:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "AboutHeader", for: indexPath) as! AboutPatreonHeaderView
headerView.supportButton.addTarget(self, action: #selector(PatreonViewController.openPatreonURL(_:)), for: .primaryActionTriggered)
return headerView
case .patrons:
if kind == UICollectionView.elementKindSectionHeader
{ {
DispatchQueue.main.async { let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "PatronsHeader", for: indexPath) as! PatronsHeaderView
let toastView = ToastView(text: error.localizedDescription, detailText: nil) headerView.textLabel.text = NSLocalizedString("Special thanks to...", comment: "")
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0) return headerView
}
else
{
let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "PatronsFooter", for: indexPath) as! PatronsFooterView
footerView.button.isIndicatingActivity = false
footerView.button.isHidden = false
footerView.button.addTarget(self, action: #selector(PatreonViewController.fetchPatrons), for: .primaryActionTriggered)
switch self.patronsResult
{
case .none: footerView.button.isIndicatingActivity = true
case .success?: footerView.button.isHidden = true
case .failure?: footerView.button.setTitle(NSLocalizedString("Error Loading Patrons", comment: ""), for: .normal)
} }
return footerView
} }
} }
} }
} }
extension PatreonViewController: UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize
{
let section = Section.allCases[section]
switch section
{
case .about:
let widthConstraint = self.prototypeAboutHeader.widthAnchor.constraint(equalToConstant: collectionView.bounds.width)
NSLayoutConstraint.activate([widthConstraint])
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
let size = self.prototypeAboutHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
return size
case .patrons:
return CGSize(width: 320, height: 20)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize
{
let section = Section.allCases[section]
switch section
{
case .about: return .zero
case .patrons: return CGSize(width: 320, height: 20)
}
}
}

View File

@@ -186,7 +186,7 @@
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/> <userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
<connections> <connections>
<segue destination="SMU-vR-jsh" kind="show" identifier="showPatreon" id="SQ1-Kp-L45"/> <segue destination="dp8-8j-vt9" kind="show" identifier="showPatreon" id="gCj-C6-hPm"/>
</connections> </connections>
</tableViewCell> </tableViewCell>
</cells> </cells>
@@ -300,7 +300,11 @@
<rect key="frame" x="0.0" y="20" width="375" height="96"/> <rect key="frame" x="0.0" y="20" width="375" height="96"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" name="Orange"/> <color key="backgroundColor" name="Orange"/>
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="barTintColor" name="Orange"/> <color key="barTintColor" name="Orange"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="automaticallyAdjustsItemPositions" value="NO"/>
</userDefinedRuntimeAttributes>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
<connections> <connections>
@@ -381,56 +385,71 @@
<point key="canvasLocation" x="1697" y="797"/> <point key="canvasLocation" x="1697" y="797"/>
</scene> </scene>
<!--Patreon--> <!--Patreon-->
<scene sceneID="h2z-Zg-r0l"> <scene sceneID="Lnh-9P-HnL">
<objects> <objects>
<tableViewController id="SMU-vR-jsh" customClass="PatreonViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController"> <collectionViewController id="dp8-8j-vt9" customClass="PatreonViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="KIK-07-O36"> <collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="OTF-Qv-Z5w">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" name="Orange"/>
<prototypes> <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="kKJ-b1-Ewh" style="IBUITableViewCellStyleDefault" id="fdL-CS-Ig8"> <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="20" id="5Ex-oN-7dE">
<rect key="frame" x="0.0" y="28" width="375" height="44"/> <size key="itemSize" width="157" height="20"/>
<autoresizingMask key="autoresizingMask"/> <size key="headerReferenceSize" width="0.0" height="0.0"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fdL-CS-Ig8" id="Vcq-Uu-TK4"> <size key="footerReferenceSize" width="0.0" height="0.0"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/> <inset key="sectionInset" minX="20" minY="8" maxX="20" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="T6v-Rq-ntX" customClass="PatronCollectionViewCell" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="20" y="8" width="157" height="20"/>
<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="157" height="20"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="kKJ-b1-Ewh"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Caroline Moore" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="ahr-fF-k3e">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/> <rect key="frame" x="0.0" y="0.0" width="157" height="20"/>
<autoresizingMask key="autoresizingMask"/> <fontDescription key="fontDescription" type="system" pointSize="16"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <color key="textColor" white="1" alpha="0.75" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
</subviews> </subviews>
</tableViewCellContentView> </view>
</tableViewCell> <constraints>
</prototypes> <constraint firstAttribute="trailing" secondItem="ahr-fF-k3e" secondAttribute="trailing" id="9aF-2y-sZf"/>
<constraint firstItem="ahr-fF-k3e" firstAttribute="top" secondItem="T6v-Rq-ntX" secondAttribute="top" id="M89-x2-VnS"/>
<constraint firstItem="ahr-fF-k3e" firstAttribute="leading" secondItem="T6v-Rq-ntX" secondAttribute="leading" id="THC-sX-gVq"/>
<constraint firstAttribute="bottom" secondItem="ahr-fF-k3e" secondAttribute="bottom" id="loA-GD-3td"/>
</constraints>
<connections>
<outlet property="textLabel" destination="ahr-fF-k3e" id="xql-Ch-bfh"/>
</connections>
</collectionViewCell>
</cells>
<connections> <connections>
<outlet property="dataSource" destination="SMU-vR-jsh" id="Hj4-Km-9nk"/> <outlet property="dataSource" destination="dp8-8j-vt9" id="ONG-kb-M7N"/>
<outlet property="delegate" destination="SMU-vR-jsh" id="ASl-Da-Ah0"/> <outlet property="delegate" destination="dp8-8j-vt9" id="790-Kr-6l7"/>
</connections> </connections>
</tableView> </collectionView>
<navigationItem key="navigationItem" title="Patreon" largeTitleDisplayMode="never" id="dK7-R5-OHU"> <navigationItem key="navigationItem" title="Patreon" largeTitleDisplayMode="always" id="uUV-1f-xEq">
<barButtonItem key="rightBarButtonItem" title="Sign In" style="done" id="5jH-Bg-xV0"> <barButtonItem key="rightBarButtonItem" title="Sign In" style="done" id="E5h-CT-k6v">
<connections> <connections>
<action selector="authenticate:" destination="SMU-vR-jsh" id="idl-h4-lM8"/> <action selector="authenticate:" destination="dp8-8j-vt9" id="pFp-yX-Tqi"/>
</connections> </connections>
</barButtonItem> </barButtonItem>
</navigationItem> </navigationItem>
<connections> <connections>
<outlet property="signInButton" destination="5jH-Bg-xV0" id="iwr-Yd-iv5"/> <outlet property="signInButton" destination="E5h-CT-k6v" id="G3Y-ZY-lVk"/>
<outlet property="signOutButton" destination="OLU-TR-ZrX" id="N7y-lz-yJu"/> <outlet property="signOutButton" destination="gsd-cI-bVF" id="cEC-IE-vcJ"/>
</connections> </connections>
</tableViewController> </collectionViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="AQT-F8-lsD" userLabel="First Responder" sceneMemberID="firstResponder"/> <barButtonItem title="Sign Out" style="done" id="gsd-cI-bVF">
<barButtonItem title="Sign Out" style="done" id="OLU-TR-ZrX"> <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="tintColor" name="RefreshRed"/>
<connections> <connections>
<action selector="signOut:" destination="SMU-vR-jsh" id="LIj-5B-xIs"/> <action selector="signOut:" destination="dp8-8j-vt9" id="BvZ-yR-Sa3"/>
</connections> </connections>
</barButtonItem> </barButtonItem>
<placeholder placeholderIdentifier="IBFirstResponder" id="qq3-Hj-S9f" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="1697" y="44"/> <point key="canvasLocation" x="1697" y="44"/>
</scene> </scene>
@@ -441,8 +460,5 @@
<namedColor name="Orange"> <namedColor name="Orange">
<color red="0.94509803921568625" green="0.67450980392156867" blue="0.24313725490196078" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color red="0.94509803921568625" green="0.67450980392156867" blue="0.24313725490196078" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor> </namedColor>
<namedColor name="RefreshRed">
<color red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources> </resources>
</document> </document>