Animate reader view button

This commit is contained in:
Maurice Parker 2019-09-24 16:34:11 -05:00
parent eb69967899
commit 98befac78c
13 changed files with 182 additions and 16 deletions

View File

@ -57,7 +57,7 @@ class ArticleExtractorButton: NSButton {
case isError: case isError:
addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractorError, opacity: opacity) addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractorError, opacity: opacity)
case isInProgress: case isInProgress:
addProgressSublayer(to: hostedLayer) addAnimatedSublayer(to: hostedLayer)
default: default:
addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractor, opacity: opacity) addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractor, opacity: opacity)
} }
@ -77,7 +77,7 @@ class ArticleExtractorButton: NSButton {
hostedLayer.addSublayer(imageLayer) hostedLayer.addSublayer(imageLayer)
} }
private func addProgressSublayer(to hostedLayer: CALayer) { private func addAnimatedSublayer(to hostedLayer: CALayer) {
let imageProgress1 = AppAssets.articleExtractorProgress1 let imageProgress1 = AppAssets.articleExtractorProgress1
let imageProgress2 = AppAssets.articleExtractorProgress2 let imageProgress2 = AppAssets.articleExtractorProgress2
let imageProgress3 = AppAssets.articleExtractorProgress3 let imageProgress3 = AppAssets.articleExtractorProgress3

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
510BD15D232D765D002692E4 /* SettingsReaderAPIAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557EE1A522B6F4E1004206FA /* SettingsReaderAPIAccountView.swift */; }; 510BD15D232D765D002692E4 /* SettingsReaderAPIAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557EE1A522B6F4E1004206FA /* SettingsReaderAPIAccountView.swift */; };
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; };
51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; 51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; };
5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; }; 5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; };
511D43CF231FA62200FB1562 /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; 511D43CF231FA62200FB1562 /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; };
@ -777,6 +778,7 @@
510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLocalAccountView.swift; sourceTree = "<group>"; }; 510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLocalAccountView.swift; sourceTree = "<group>"; };
510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFeedbinAccountView.swift; sourceTree = "<group>"; }; 510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFeedbinAccountView.swift; sourceTree = "<group>"; };
510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountLabelView.swift; sourceTree = "<group>"; }; 510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountLabelView.swift; sourceTree = "<group>"; };
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = "<group>"; };
51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = "<group>"; }; 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = "<group>"; };
51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = "<group>"; }; 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = "<group>"; };
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = "<group>"; }; 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = "<group>"; };
@ -1377,6 +1379,7 @@
children = ( children = (
51C4527E2265092C00C03939 /* ArticleViewController.swift */, 51C4527E2265092C00C03939 /* ArticleViewController.swift */,
517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */, 517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */,
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */,
); );
path = Article; path = Article;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2823,6 +2826,7 @@
51F85BF722749FA100C787DC /* UIFont-Extensions.swift in Sources */, 51F85BF722749FA100C787DC /* UIFont-Extensions.swift in Sources */,
51C452AF2265108300C03939 /* ArticleArray.swift in Sources */, 51C452AF2265108300C03939 /* ArticleArray.swift in Sources */,
51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */, 51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */,
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */,
84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */, 84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */,
51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */, 51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */,
51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */,

View File

@ -10,6 +10,28 @@ import RSCore
struct AppAssets { struct AppAssets {
static var articleExtractorError: UIImage = {
return UIImage(named: "articleExtractorOff")!
}()
static var articleExtractorOff: UIImage = {
return UIImage(named: "articleExtractorOff")!
}()
static var articleExtractorOffTinted: UIImage = {
let image = UIImage(named: "articleExtractorOff")!
return image.maskWithColor(color: AppAssets.primaryAccentColor.cgColor)!
}()
static var articleExtractorOn: UIImage = {
return UIImage(named: "articleExtractorOn")!
}()
static var articleExtractorOnTinted: UIImage = {
let image = UIImage(named: "articleExtractorOn")!
return image.maskWithColor(color: AppAssets.primaryAccentColor.cgColor)!
}()
static var avatarBackgroundColor: UIColor = { static var avatarBackgroundColor: UIColor = {
return UIColor(named: "avatarBackgroundColor")! return UIColor(named: "avatarBackgroundColor")!
}() }()

View File

@ -0,0 +1,78 @@
//
// ArticleExtractorButton.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 9/24/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
enum ArticleExtractorButtonState {
case error
case animated
case on
case off
}
class ArticleExtractorButton: UIButton {
var buttonState: ArticleExtractorButtonState = .off {
didSet {
if buttonState != oldValue {
switch buttonState {
case .error:
stripSublayer()
setImage(AppAssets.articleExtractorError, for: .normal)
case .animated:
setImage(nil, for: .normal)
setNeedsLayout()
case .on:
stripSublayer()
setImage(AppAssets.articleExtractorOn, for: .normal)
case .off:
stripSublayer()
setImage(AppAssets.articleExtractorOff, for: .normal)
}
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
guard case .animated = buttonState else {
return
}
stripSublayer()
addAnimatedSublayer(to: layer)
}
private func stripSublayer() {
if layer.sublayers?.count ?? 0 > 1 {
layer.sublayers?.last?.removeFromSuperlayer()
}
}
private func addAnimatedSublayer(to hostedLayer: CALayer) {
let image1 = AppAssets.articleExtractorOffTinted.cgImage!
let image2 = AppAssets.articleExtractorOnTinted.cgImage!
let images = [image1, image2, image1]
let imageLayer = CALayer()
let imageSize = AppAssets.articleExtractorOff.size
imageLayer.bounds = CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height)
imageLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
hostedLayer.addSublayer(imageLayer)
let animation = CAKeyframeAnimation(keyPath: "contents")
animation.calculationMode = CAAnimationCalculationMode.linear
animation.keyTimes = [0, 0.5, 1]
animation.duration = 2
animation.values = images as [Any]
animation.repeatCount = HUGE
imageLayer.add(animation, forKey: "contents")
}
}

View File

@ -22,7 +22,7 @@ enum ArticleViewState: Equatable {
class ArticleViewController: UIViewController { class ArticleViewController: UIViewController {
@IBOutlet private weak var readerViewBarButtonItem: UIBarButtonItem! @IBOutlet private weak var articleExtractorButton: ArticleExtractorButton!
@IBOutlet private weak var nextUnreadBarButtonItem: UIBarButtonItem! @IBOutlet private weak var nextUnreadBarButtonItem: UIBarButtonItem!
@IBOutlet private weak var prevArticleBarButtonItem: UIBarButtonItem! @IBOutlet private weak var prevArticleBarButtonItem: UIBarButtonItem!
@IBOutlet private weak var nextArticleBarButtonItem: UIBarButtonItem! @IBOutlet private weak var nextArticleBarButtonItem: UIBarButtonItem!
@ -55,6 +55,15 @@ class ArticleViewController: UIViewController {
} }
} }
var articleExtractorButtonState: ArticleExtractorButtonState {
get {
return articleExtractorButton.buttonState
}
set {
articleExtractorButton.buttonState = newValue
}
}
private let keyboardManager = KeyboardManager(type: .detail) private let keyboardManager = KeyboardManager(type: .detail)
override var keyCommands: [UIKeyCommand]? { override var keyCommands: [UIKeyCommand]? {
return keyboardManager.keyCommands return keyboardManager.keyCommands
@ -74,6 +83,9 @@ class ArticleViewController: UIViewController {
NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil)
// For some reason interface builder won't let me set this there.
articleExtractorButton.addTarget(self, action: #selector(toggleArticleExtractor(_:)), for: .touchUpInside)
ArticleViewControllerWebViewProvider.shared.dequeueWebView() { webView in ArticleViewControllerWebViewProvider.shared.dequeueWebView() { webView in
self.webView = webView self.webView = webView
@ -96,6 +108,7 @@ class ArticleViewController: UIViewController {
func updateUI() { func updateUI() {
guard let article = currentArticle else { guard let article = currentArticle else {
articleExtractorButton.isEnabled = false
nextUnreadBarButtonItem.isEnabled = false nextUnreadBarButtonItem.isEnabled = false
prevArticleBarButtonItem.isEnabled = false prevArticleBarButtonItem.isEnabled = false
nextArticleBarButtonItem.isEnabled = false nextArticleBarButtonItem.isEnabled = false
@ -110,6 +123,7 @@ class ArticleViewController: UIViewController {
prevArticleBarButtonItem.isEnabled = coordinator.isPrevArticleAvailable prevArticleBarButtonItem.isEnabled = coordinator.isPrevArticleAvailable
nextArticleBarButtonItem.isEnabled = coordinator.isNextArticleAvailable nextArticleBarButtonItem.isEnabled = coordinator.isNextArticleAvailable
articleExtractorButton.isEnabled = true
readBarButtonItem.isEnabled = true readBarButtonItem.isEnabled = true
starBarButtonItem.isEnabled = true starBarButtonItem.isEnabled = true
browserBarButtonItem.isEnabled = true browserBarButtonItem.isEnabled = true
@ -179,7 +193,7 @@ class ArticleViewController: UIViewController {
// MARK: Actions // MARK: Actions
@IBAction func toggleReaderView(_ sender: Any) { @IBAction func toggleArticleExtractor(_ sender: Any) {
coordinator.toggleArticleExtractor() coordinator.toggleArticleExtractor()
} }

View File

@ -99,25 +99,24 @@
</barButtonItem> </barButtonItem>
</toolbarItems> </toolbarItems>
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="mOI-FS-AaM"> <navigationItem key="navigationItem" largeTitleDisplayMode="never" id="mOI-FS-AaM">
<barButtonItem key="rightBarButtonItem" title="Reader View" image="doc.plaintext" catalog="system" id="Bl3-sa-n84"> <barButtonItem key="rightBarButtonItem" style="plain" id="n8q-YO-ldL">
<userDefinedRuntimeAttributes> <button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="n73-9B-rav" customClass="ArticleExtractorButton" customModule="NetNewsWire" customModuleProvider="target">
<userDefinedRuntimeAttribute type="string" keyPath="accLabelText" value="Reader View"/> <rect key="frame" x="362" y="5" width="32" height="32"/>
</userDefinedRuntimeAttributes> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<connections> <state key="normal" image="articleExtractorOff"/>
<action selector="toggleReaderView:" destination="JEX-9P-axG" id="o5N-Hy-Ma3"/> </button>
</connections>
</barButtonItem> </barButtonItem>
</navigationItem> </navigationItem>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/> <simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/> <simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
<connections> <connections>
<outlet property="actionBarButtonItem" destination="9Ut-5B-JKP" id="9bO-kz-cTz"/> <outlet property="actionBarButtonItem" destination="9Ut-5B-JKP" id="9bO-kz-cTz"/>
<outlet property="articleExtractorButton" destination="n73-9B-rav" id="zud-XU-Kkx"/>
<outlet property="browserBarButtonItem" destination="DMh-3X-ebd" id="PkT-Tn-8kG"/> <outlet property="browserBarButtonItem" destination="DMh-3X-ebd" id="PkT-Tn-8kG"/>
<outlet property="nextArticleBarButtonItem" destination="2qz-M5-Yhk" id="IQd-jx-qEr"/> <outlet property="nextArticleBarButtonItem" destination="2qz-M5-Yhk" id="IQd-jx-qEr"/>
<outlet property="nextUnreadBarButtonItem" destination="2w5-e9-C2V" id="xJr-5y-p1N"/> <outlet property="nextUnreadBarButtonItem" destination="2w5-e9-C2V" id="xJr-5y-p1N"/>
<outlet property="prevArticleBarButtonItem" destination="v4j-fq-23N" id="Gny-Oh-cQa"/> <outlet property="prevArticleBarButtonItem" destination="v4j-fq-23N" id="Gny-Oh-cQa"/>
<outlet property="readBarButtonItem" destination="hy0-LS-MzE" id="BzM-x9-tuj"/> <outlet property="readBarButtonItem" destination="hy0-LS-MzE" id="BzM-x9-tuj"/>
<outlet property="readerViewBarButtonItem" destination="Bl3-sa-n84" id="JhM-7c-nIf"/>
<outlet property="starBarButtonItem" destination="wU4-eH-wC9" id="Z8Q-Lt-dKk"/> <outlet property="starBarButtonItem" destination="wU4-eH-wC9" id="Z8Q-Lt-dKk"/>
<outlet property="webViewContainer" destination="DNb-lt-KzC" id="Fc1-Ae-pWK"/> <outlet property="webViewContainer" destination="DNb-lt-KzC" id="Fc1-Ae-pWK"/>
</connections> </connections>
@ -240,8 +239,8 @@
<image name="arrow.down" catalog="system" width="58" height="64"/> <image name="arrow.down" catalog="system" width="58" height="64"/>
<image name="arrow.down.circle" catalog="system" width="64" height="62"/> <image name="arrow.down.circle" catalog="system" width="64" height="62"/>
<image name="arrow.up" catalog="system" width="58" height="64"/> <image name="arrow.up" catalog="system" width="58" height="64"/>
<image name="articleExtractorOff" width="18" height="23"/>
<image name="circle" catalog="system" width="64" height="62"/> <image name="circle" catalog="system" width="64" height="62"/>
<image name="doc.plaintext" catalog="system" width="56" height="64"/>
<image name="gear" catalog="system" width="64" height="60"/> <image name="gear" catalog="system" width="64" height="60"/>
<image name="safari" catalog="system" width="64" height="62"/> <image name="safari" catalog="system" width="64" height="62"/>
<image name="square.and.arrow.up" catalog="system" width="56" height="64"/> <image name="square.and.arrow.up" catalog="system" width="56" height="64"/>

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ArticleExtractorOff.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ArticleExtractorOff.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ArticleExtractorOn.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -559,7 +559,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
articleExtractor?.cancel() articleExtractor?.cancel()
articleExtractor = nil articleExtractor = nil
isShowingExtractedArticle = false isShowingExtractedArticle = false
// makeToolbarValidate() articleViewController?.articleExtractorButtonState = .off
currentArticle = article currentArticle = article
activityManager.reading(currentArticle) activityManager.reading(currentArticle)
@ -813,12 +813,14 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
articleExtractor?.cancel() articleExtractor?.cancel()
articleExtractor = nil articleExtractor = nil
isShowingExtractedArticle = false isShowingExtractedArticle = false
articleViewController?.articleExtractorButtonState = .off
articleViewController?.state = .article(article) articleViewController?.state = .article(article)
return return
} }
guard !isShowingExtractedArticle else { guard !isShowingExtractedArticle else {
isShowingExtractedArticle = false isShowingExtractedArticle = false
articleViewController?.articleExtractorButtonState = .off
articleViewController?.state = .article(article) articleViewController?.state = .article(article)
return return
} }
@ -826,6 +828,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
if let articleExtractor = articleExtractor, let extractedArticle = articleExtractor.article { if let articleExtractor = articleExtractor, let extractedArticle = articleExtractor.article {
if currentArticle?.preferredLink == articleExtractor.articleLink { if currentArticle?.preferredLink == articleExtractor.articleLink {
isShowingExtractedArticle = true isShowingExtractedArticle = true
articleViewController?.articleExtractorButtonState = .on
articleViewController?.state = .extracted(article, extractedArticle) articleViewController?.state = .extracted(article, extractedArticle)
} }
} else { } else {
@ -924,14 +927,14 @@ extension SceneCoordinator: UINavigationControllerDelegate {
extension SceneCoordinator: ArticleExtractorDelegate { extension SceneCoordinator: ArticleExtractorDelegate {
func articleExtractionDidFail(with: Error) { func articleExtractionDidFail(with: Error) {
// makeToolbarValidate() articleViewController?.articleExtractorButtonState = .error
} }
func articleExtractionDidComplete(extractedArticle: ExtractedArticle) { func articleExtractionDidComplete(extractedArticle: ExtractedArticle) {
if let article = currentArticle, articleExtractor?.state != .cancelled { if let article = currentArticle, articleExtractor?.state != .cancelled {
isShowingExtractedArticle = true isShowingExtractedArticle = true
articleViewController?.state = .extracted(article, extractedArticle) articleViewController?.state = .extracted(article, extractedArticle)
// makeToolbarValidate() articleViewController?.articleExtractorButtonState = .on
} }
} }
@ -1243,6 +1246,7 @@ private extension SceneCoordinator {
extractor.delegate = self extractor.delegate = self
extractor.process() extractor.process()
articleExtractor = extractor articleExtractor = extractor
articleViewController?.articleExtractorButtonState = .animated
} }
} }