This commit is contained in:
Brent Simmons 2019-06-08 12:03:39 -07:00
commit 95b96b9df9
14 changed files with 266 additions and 90 deletions

View File

@ -64,7 +64,7 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
set {
let oldNameForDisplay = nameForDisplay
metadata.name = newValue
if oldNameForDisplay != nameForDisplay {
if oldNameForDisplay != newValue {
postDisplayNameDidChangeNotification()
}
}

View File

@ -46,7 +46,7 @@ struct FeedSpecifier: Hashable {
return feedSpecifiers.anyObject()
}
var currentHighScore = 0
var currentHighScore = Int.min
var currentBestFeed: FeedSpecifier? = nil
for oneFeedSpecifier in feedSpecifiers {

View File

@ -88,11 +88,13 @@ final class FeedbinAccountDelegate: AccountDelegate {
case .success():
self.refreshArticles(account) {
self.refreshArticleStatus(for: account) {
self.refreshMissingArticles(account) {
self.refreshProgress.clear()
DispatchQueue.main.async {
completion(.success(()))
self.sendArticleStatus(for: account) {
self.refreshArticleStatus(for: account) {
self.refreshMissingArticles(account) {
self.refreshProgress.clear()
DispatchQueue.main.async {
completion(.success(()))
}
}
}
}
@ -241,10 +243,16 @@ final class FeedbinAccountDelegate: AccountDelegate {
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
guard folder.hasAtLeastOneFeed() else {
folder.name = name
return
}
caller.renameTag(oldName: folder.name ?? "", newName: name) { result in
switch result {
case .success:
DispatchQueue.main.async {
self.renameFolderRelationship(for: account, fromName: folder.name ?? "", toName: name)
folder.name = name
completion(.success(()))
}
@ -269,16 +277,44 @@ final class FeedbinAccountDelegate: AccountDelegate {
let group = DispatchGroup()
for feed in folder.topLevelFeeds {
group.enter()
removeFeed(for: account, with: feed, from: folder) { result in
group.leave()
switch result {
case .success:
break
case .failure(let error):
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
if feed.folderRelationship?.count ?? 0 > 1 {
if let feedTaggingID = feed.folderRelationship?[folder.name ?? ""] {
group.enter()
caller.deleteTagging(taggingID: feedTaggingID) { result in
group.leave()
switch result {
case .success:
DispatchQueue.main.async {
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
}
case .failure(let error):
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
}
}
}
} else {
if let subscriptionID = feed.subscriptionID {
group.enter()
caller.deleteSubscription(subscriptionID: subscriptionID) { result in
group.leave()
switch result {
case .success:
DispatchQueue.main.async {
account.clearFeedMetadata(feed)
}
case .failure(let error):
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
}
}
}
}
}
group.notify(queue: DispatchQueue.main) {
@ -347,7 +383,6 @@ final class FeedbinAccountDelegate: AccountDelegate {
if feed.folderRelationship?.count ?? 0 > 1 {
deleteTagging(for: account, with: feed, from: container, completion: completion)
} else {
account.clearFeedMetadata(feed)
deleteSubscription(for: account, with: feed, from: container, completion: completion)
}
}
@ -399,12 +434,23 @@ final class FeedbinAccountDelegate: AccountDelegate {
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
switch result {
case .success:
completion(.success(()))
case .failure(let error):
completion(.failure(error))
if let existingFeed = account.existingFeed(withURL: feed.url) {
account.addFeed(existingFeed, to: container) { result in
switch result {
case .success:
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
} else {
createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
switch result {
case .success:
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
}
@ -412,22 +458,27 @@ final class FeedbinAccountDelegate: AccountDelegate {
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
account.addFolder(folder)
let group = DispatchGroup()
for feed in folder.topLevelFeeds {
folder.topLevelFeeds.remove(feed)
group.enter()
addFeed(for: account, with: feed, to: folder) { result in
if account.topLevelFeeds.contains(feed) {
account.removeFeed(feed)
}
restoreFeed(for: account, feed: feed, container: folder) { result in
group.leave()
switch result {
case .success:
break
case .failure(let error):
os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription)
}
}
}
group.notify(queue: DispatchQueue.main) {
account.addFolder(folder)
completion(.success(()))
}
@ -647,7 +698,10 @@ private extension FeedbinAccountDelegate {
DispatchQueue.main.sync {
if let feed = account.idToFeedDictionary[subFeedId] {
feed.name = subscription.name
// If the name has been changed on the server remove the locally edited name
feed.editedName = nil
feed.homePageURL = subscription.homePageURL
feed.subscriptionID = String(subscription.subscriptionID)
} else {
let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: subFeedId, homePageURL: subscription.homePageURL)
feed.subscriptionID = String(subscription.subscriptionID)
@ -792,6 +846,17 @@ private extension FeedbinAccountDelegate {
}
func renameFolderRelationship(for account: Account, fromName: String, toName: String) {
for feed in account.flattenedFeeds() {
if var folderRelationship = feed.folderRelationship {
let relationship = folderRelationship[fromName]
folderRelationship[fromName] = nil
folderRelationship[toName] = relationship
feed.folderRelationship = folderRelationship
}
}
}
func clearFolderRelationship(for feed: Feed, withFolderName folderName: String) {
if var folderRelationship = feed.folderRelationship {
folderRelationship[folderName] = nil
@ -1158,6 +1223,7 @@ private extension FeedbinAccountDelegate {
switch result {
case .success:
DispatchQueue.main.async {
account.clearFeedMetadata(feed)
account.removeFeed(feed)
if let folders = account.folders {
for folder in folders {

View File

@ -93,41 +93,53 @@ final class LocalAccountDelegate: AccountDelegate {
return
}
FeedFinder.find(url: url) { result in
DispatchQueue.global(qos: .userInitiated).async {
switch result {
case .success(let feedSpecifiers):
FeedFinder.find(url: url) { result in
guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers),
let url = URL(string: bestFeedSpecifier.urlString) else {
completion(.failure(AccountError.createErrorNotFound))
return
}
if account.hasFeed(withURL: bestFeedSpecifier.urlString) {
completion(.failure(AccountError.createErrorAlreadySubscribed))
return
}
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
InitialFeedDownloader.download(url) { parsedFeed in
switch result {
case .success(let feedSpecifiers):
if let parsedFeed = parsedFeed {
account.update(feed, with: parsedFeed, {})
guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers),
let url = URL(string: bestFeedSpecifier.urlString) else {
DispatchQueue.main.async {
completion(.failure(AccountError.createErrorNotFound))
}
return
}
feed.editedName = name
if account.hasFeed(withURL: bestFeedSpecifier.urlString) {
DispatchQueue.main.async {
completion(.failure(AccountError.createErrorAlreadySubscribed))
}
return
}
container.addFeed(feed)
completion(.success(feed))
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
InitialFeedDownloader.download(url) { parsedFeed in
if let parsedFeed = parsedFeed {
account.update(feed, with: parsedFeed, {})
}
feed.editedName = name
container.addFeed(feed)
DispatchQueue.main.async {
completion(.success(feed))
}
}
case .failure:
DispatchQueue.main.async {
completion(.failure(AccountError.createErrorNotFound))
}
}
case .failure(let error):
completion(.failure(error))
}
}
}

View File

@ -413,7 +413,7 @@
<items>
<menuItem title="Mark as Read" keyEquivalent="U" id="Fc9-c7-2AY">
<connections>
<action selector="markRead:" target="Ady-hI-5gd" id="RQv-jl-2Nv"/>
<action selector="toggleRead:" target="Ady-hI-5gd" id="jLQ-ZF-xye"/>
</connections>
</menuItem>
<menuItem title="Mark All as Read" keyEquivalent="k" id="HdN-Ks-cwh">

View File

@ -86,10 +86,11 @@ private extension ArticleRenderer {
}
func titleOrTitleLink() -> String {
let escapedTitle = title.escapeHTML()
if let link = article?.preferredLink {
return title.htmlByAddingLink(link)
return escapedTitle.htmlByAddingLink(link)
}
return title
return escapedTitle
}
func substitutions() -> [String: String] {

View File

@ -46,12 +46,30 @@ final class DeleteCommand: UndoableCommand {
func perform() {
BatchUpdate.shared.perform {
itemSpecifiers.forEach { $0.delete() }
itemSpecifiers.forEach { $0.delete() {} }
treeController.rebuild()
}
registerUndo()
}
func perform(completion: @escaping () -> Void) {
let group = DispatchGroup()
group.enter()
itemSpecifiers.forEach {
$0.delete() {
group.leave()
}
}
treeController.rebuild()
group.notify(queue: DispatchQueue.main) {
self.registerUndo()
completion()
}
}
func undo() {
BatchUpdate.shared.perform {
@ -132,18 +150,20 @@ private struct SidebarItemSpecifier {
self.path = ContainerPath(account: account!, folders: node.containingFolders())
}
func delete() {
func delete(completion: @escaping () -> Void) {
if let feed = feed {
BatchUpdate.shared.start()
account?.removeFeed(feed, from: path.resolveContainer()) { result in
BatchUpdate.shared.end()
completion()
self.checkResult(result)
}
} else if let folder = folder {
BatchUpdate.shared.start()
account?.removeFolder(folder) { result in
BatchUpdate.shared.end()
completion()
self.checkResult(result)
}
}

View File

@ -526,14 +526,23 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn
else {
return
}
navState.beginUpdates()
runCommand(deleteCommand)
navState.rebuildShadowTable()
tableView.deleteRows(at: [indexPath], with: .automatic)
var deleteIndexPaths = [indexPath]
if navState.isExpanded(deleteNode) {
for i in 0..<deleteNode.numberOfChildNodes {
deleteIndexPaths.append(IndexPath(row: indexPath.row + 1 + i, section: indexPath.section))
}
}
navState.endUpdates()
pushUndoableCommand(deleteCommand)
navState.beginUpdates()
deleteCommand.perform {
self.navState.treeController.rebuild()
self.navState.rebuildShadowTable()
self.tableView.deleteRows(at: deleteIndexPaths, with: .automatic)
self.navState.endUpdates()
}
}

View File

@ -10,7 +10,7 @@ import Account
import UIKit
protocol AddAccountDismissDelegate: UIViewController {
func dismiss(_ viewController: UIViewController)
func dismiss()
}
class AddAccountViewController: UITableViewController, AddAccountDismissDelegate {
@ -27,11 +27,13 @@ class AddAccountViewController: UITableViewController, AddAccountDismissDelegate
switch indexPath.row {
case 0:
let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "AddLocalAccountNavigationViewController") as! UINavigationController
navController.modalPresentationStyle = .currentContext
let addViewController = navController.topViewController as! AddLocalAccountViewController
addViewController.delegate = self
present(navController, animated: true)
case 1:
let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "FeedbinAccountNavigationViewController") as! UINavigationController
navController.modalPresentationStyle = .currentContext
let addViewController = navController.topViewController as! FeedbinAccountViewController
addViewController.delegate = self
present(navController, animated: true)
@ -40,8 +42,8 @@ class AddAccountViewController: UITableViewController, AddAccountDismissDelegate
}
}
func dismiss(_ viewController: UIViewController) {
viewController.dismiss(animated: true, completion: nil)
func dismiss() {
navigationController?.popViewController(animated: false)
}
}

View File

@ -25,13 +25,15 @@ class AddLocalAccountViewController: UIViewController {
}
@IBAction func cancel(_ sender: Any) {
delegate?.dismiss(self)
dismiss(animated: true, completion: nil)
delegate?.dismiss()
}
@IBAction func addAccountTapped(_ sender: Any) {
let account = AccountManager.shared.createAccount(type: .onMyMac)
account.name = nameTextField.text
delegate?.dismiss(self)
dismiss(animated: true, completion: nil)
delegate?.dismiss()
}
}

View File

@ -37,15 +37,26 @@ class DetailAccountViewController: UITableViewController {
extension DetailAccountViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
guard let account = account else { return 0 }
if account == AccountManager.shared.defaultAccount {
return 1
} else if account.type == .onMyMac {
return 2
} else {
return super.numberOfSections(in: tableView)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
let cell: UITableViewCell
if indexPath.section == 1, let account = account, account.type == .onMyMac {
cell = super.tableView(tableView, cellForRowAt: IndexPath(row: 0, section: 2))
} else {
cell = super.tableView(tableView, cellForRowAt: indexPath)
}
let bgView = UIView()
bgView.backgroundColor = AppAssets.selectionBackgroundColor
@ -54,7 +65,7 @@ extension DetailAccountViewController {
}
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
if indexPath.section == 1 {
if indexPath.section > 0 {
return true
}
@ -62,8 +73,19 @@ extension DetailAccountViewController {
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 1 {
deleteAccount()
if let account = account, account.type == .onMyMac {
if indexPath.section == 1 {
deleteAccount()
}
} else {
switch indexPath.section {
case 1:
credentials()
case 2:
deleteAccount()
default:
break
}
}
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
@ -73,6 +95,19 @@ extension DetailAccountViewController {
private extension DetailAccountViewController {
func credentials() {
guard let account = account else { return }
switch account.type {
case .feedbin:
let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "FeedbinAccountNavigationViewController") as! UINavigationController
let addViewController = navController.topViewController as! FeedbinAccountViewController
addViewController.account = account
present(navController, animated: true)
default:
break
}
}
func deleteAccount() {
let title = NSLocalizedString("Delete Account", comment: "Delete Account")
let message = NSLocalizedString("Are you sure you want to delete this account? This can not be undone.", comment: "Delete Account")

View File

@ -16,7 +16,7 @@ class FeedbinAccountViewController: UIViewController {
@IBOutlet weak var cancelBarButtonItem: UIBarButtonItem!
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var addAccountButton: UIButton!
@IBOutlet weak var actionButton: UIButton!
@IBOutlet weak var errorMessageLabel: UILabel!
@ -31,18 +31,22 @@ class FeedbinAccountViewController: UIViewController {
passwordTextField.delegate = self
if let account = account, let credentials = try? account.retrieveBasicCredentials() {
actionButton.setTitle(NSLocalizedString("Update Credentials", comment: "Update Credentials"), for: .normal)
if case .basic(let username, let password) = credentials {
emailTextField.text = username
passwordTextField.text = password
}
} else {
actionButton.setTitle(NSLocalizedString("Add Account", comment: "Update Credentials"), for: .normal)
}
}
@IBAction func cancel(_ sender: Any) {
delegate?.dismiss(self)
dismiss(animated: true, completion: nil)
delegate?.dismiss()
}
@IBAction func addAccountTapped(_ sender: Any) {
@IBAction func action(_ sender: Any) {
self.errorMessageLabel.text = nil
guard emailTextField.text != nil && passwordTextField.text != nil else {
@ -56,8 +60,7 @@ class FeedbinAccountViewController: UIViewController {
// When you fill in the email address via auto-complete it adds extra whitespace
let emailAddress = emailTextField.text?.trimmingCharacters(in: .whitespaces)
let credentials = Credentials.basic(username: emailAddress ?? "", password: passwordTextField.text ?? "")
Account.validateCredentials(type: .feedbin, credentials: credentials) { [weak self] result in
guard let self = self else { return }
Account.validateCredentials(type: .feedbin, credentials: credentials) { result in
self.stopAnimtatingActivityIndicator()
self.enableNavigation()
@ -73,7 +76,9 @@ class FeedbinAccountViewController: UIViewController {
do {
try self.account?.removeBasicCredentials()
do {
try self.account?.removeBasicCredentials()
} catch {}
try self.account?.storeCredentials(credentials)
if newAccount {
@ -87,7 +92,8 @@ class FeedbinAccountViewController: UIViewController {
}
}
self.delegate?.dismiss(self)
self.dismiss(animated: true, completion: nil)
self.delegate?.dismiss()
} catch {
self.errorMessageLabel.text = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")
}
@ -103,12 +109,12 @@ class FeedbinAccountViewController: UIViewController {
private func enableNavigation() {
self.cancelBarButtonItem.isEnabled = true
self.addAccountButton.isEnabled = true
self.actionButton.isEnabled = true
}
private func disableNavigation() {
cancelBarButtonItem.isEnabled = false
addAccountButton.isEnabled = false
actionButton.isEnabled = false
}
private func startAnimatingActivityIndicator() {

View File

@ -368,7 +368,31 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Delete Account" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OKd-Ps-a1K">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Credentials" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OKd-Ps-a1K">
<rect key="frame" x="163.5" y="11.5" width="87" height="21"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="OKd-Ps-a1K" firstAttribute="centerY" secondItem="mV2-iL-ltS" secondAttribute="centerY" id="ix2-SF-I7U"/>
<constraint firstItem="OKd-Ps-a1K" firstAttribute="centerX" secondItem="mV2-iL-ltS" secondAttribute="centerX" id="nZf-Eh-VXu"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection id="sVG-jo-N8H">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="5r9-pq-th4">
<rect key="frame" x="0.0" y="239" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="5r9-pq-th4" id="Z5N-KD-L3U">
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Delete Account" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="a8v-SL-W9q">
<rect key="frame" x="148" y="11.5" width="118" height="21"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<color key="textColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -376,8 +400,8 @@
</label>
</subviews>
<constraints>
<constraint firstItem="OKd-Ps-a1K" firstAttribute="centerY" secondItem="mV2-iL-ltS" secondAttribute="centerY" id="ix2-SF-I7U"/>
<constraint firstItem="OKd-Ps-a1K" firstAttribute="centerX" secondItem="mV2-iL-ltS" secondAttribute="centerX" id="nZf-Eh-VXu"/>
<constraint firstItem="a8v-SL-W9q" firstAttribute="centerX" secondItem="Z5N-KD-L3U" secondAttribute="centerX" id="F0E-Pt-dt3"/>
<constraint firstItem="a8v-SL-W9q" firstAttribute="centerY" secondItem="Z5N-KD-L3U" secondAttribute="centerY" id="TIC-BM-f0j"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
@ -658,10 +682,9 @@
<constraint firstAttribute="height" constant="48" id="8Vt-l1-eL1"/>
</constraints>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<state key="normal" title="Add Account"/>
<state key="normal" title="Action"/>
<connections>
<action selector="addAccountTapped:" destination="byh-sg-6p5" eventType="touchUpInside" id="BFR-MI-1qW"/>
<action selector="addAccountTapped:" destination="lkT-rF-XV3" eventType="touchUpInside" id="YKl-dE-pVK"/>
<action selector="action:" destination="byh-sg-6p5" eventType="touchUpInside" id="ZQy-9g-TeU"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9QD-Wz-fqW">
@ -712,8 +735,8 @@
</barButtonItem>
</navigationItem>
<connections>
<outlet property="actionButton" destination="pv5-O6-P6Z" id="6Fm-3l-zj1"/>
<outlet property="activityIndicator" destination="Pl1-lc-sIl" id="hqg-mX-Yns"/>
<outlet property="addAccountButton" destination="pv5-O6-P6Z" id="DEh-oq-rnD"/>
<outlet property="cancelBarButtonItem" destination="xVt-VC-XFV" id="yBm-px-sgt"/>
<outlet property="emailTextField" destination="UiV-th-dQb" id="fCb-hg-AXa"/>
<outlet property="errorMessageLabel" destination="9QD-Wz-fqW" id="Kjo-73-Pgh"/>
@ -823,7 +846,7 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="61.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" usesAttributedText="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5fQ-qz-qbW">
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" usesAttributedText="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5fQ-qz-qbW">
<rect key="frame" x="16" y="0.0" width="382" height="61.5"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<attributedString key="attributedText"/>

@ -1 +1 @@
Subproject commit 59685e50640cd4629294bf2c0d63193ffa4ccc74
Subproject commit f6bfc2bc74923d800c1e8c8e997009c81aec8f20