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

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
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 */; };
5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; };
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>"; };
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>"; };
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>"; };
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>"; };
@ -1377,6 +1379,7 @@
children = (
51C4527E2265092C00C03939 /* ArticleViewController.swift */,
517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */,
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */,
);
path = Article;
sourceTree = "<group>";
@ -2823,6 +2826,7 @@
51F85BF722749FA100C787DC /* UIFont-Extensions.swift in Sources */,
51C452AF2265108300C03939 /* ArticleArray.swift in Sources */,
51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */,
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */,
84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */,
51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */,
51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */,

View File

@ -10,6 +10,28 @@ import RSCore
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 = {
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 {
@IBOutlet private weak var readerViewBarButtonItem: UIBarButtonItem!
@IBOutlet private weak var articleExtractorButton: ArticleExtractorButton!
@IBOutlet private weak var nextUnreadBarButtonItem: UIBarButtonItem!
@IBOutlet private weak var prevArticleBarButtonItem: 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)
override var keyCommands: [UIKeyCommand]? {
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(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
self.webView = webView
@ -96,6 +108,7 @@ class ArticleViewController: UIViewController {
func updateUI() {
guard let article = currentArticle else {
articleExtractorButton.isEnabled = false
nextUnreadBarButtonItem.isEnabled = false
prevArticleBarButtonItem.isEnabled = false
nextArticleBarButtonItem.isEnabled = false
@ -110,6 +123,7 @@ class ArticleViewController: UIViewController {
prevArticleBarButtonItem.isEnabled = coordinator.isPrevArticleAvailable
nextArticleBarButtonItem.isEnabled = coordinator.isNextArticleAvailable
articleExtractorButton.isEnabled = true
readBarButtonItem.isEnabled = true
starBarButtonItem.isEnabled = true
browserBarButtonItem.isEnabled = true
@ -179,7 +193,7 @@ class ArticleViewController: UIViewController {
// MARK: Actions
@IBAction func toggleReaderView(_ sender: Any) {
@IBAction func toggleArticleExtractor(_ sender: Any) {
coordinator.toggleArticleExtractor()
}

View File

@ -99,25 +99,24 @@
</barButtonItem>
</toolbarItems>
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="mOI-FS-AaM">
<barButtonItem key="rightBarButtonItem" title="Reader View" image="doc.plaintext" catalog="system" id="Bl3-sa-n84">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="accLabelText" value="Reader View"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="toggleReaderView:" destination="JEX-9P-axG" id="o5N-Hy-Ma3"/>
</connections>
<barButtonItem key="rightBarButtonItem" style="plain" id="n8q-YO-ldL">
<button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="n73-9B-rav" customClass="ArticleExtractorButton" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="362" y="5" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" image="articleExtractorOff"/>
</button>
</barButtonItem>
</navigationItem>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
<connections>
<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="nextArticleBarButtonItem" destination="2qz-M5-Yhk" id="IQd-jx-qEr"/>
<outlet property="nextUnreadBarButtonItem" destination="2w5-e9-C2V" id="xJr-5y-p1N"/>
<outlet property="prevArticleBarButtonItem" destination="v4j-fq-23N" id="Gny-Oh-cQa"/>
<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="webViewContainer" destination="DNb-lt-KzC" id="Fc1-Ae-pWK"/>
</connections>
@ -240,8 +239,8 @@
<image name="arrow.down" catalog="system" width="58" height="64"/>
<image name="arrow.down.circle" catalog="system" width="64" height="62"/>
<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="doc.plaintext" catalog="system" width="56" height="64"/>
<image name="gear" catalog="system" width="64" height="60"/>
<image name="safari" catalog="system" width="64" height="62"/>
<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 = nil
isShowingExtractedArticle = false
// makeToolbarValidate()
articleViewController?.articleExtractorButtonState = .off
currentArticle = article
activityManager.reading(currentArticle)
@ -813,12 +813,14 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
articleExtractor?.cancel()
articleExtractor = nil
isShowingExtractedArticle = false
articleViewController?.articleExtractorButtonState = .off
articleViewController?.state = .article(article)
return
}
guard !isShowingExtractedArticle else {
isShowingExtractedArticle = false
articleViewController?.articleExtractorButtonState = .off
articleViewController?.state = .article(article)
return
}
@ -826,6 +828,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
if let articleExtractor = articleExtractor, let extractedArticle = articleExtractor.article {
if currentArticle?.preferredLink == articleExtractor.articleLink {
isShowingExtractedArticle = true
articleViewController?.articleExtractorButtonState = .on
articleViewController?.state = .extracted(article, extractedArticle)
}
} else {
@ -924,14 +927,14 @@ extension SceneCoordinator: UINavigationControllerDelegate {
extension SceneCoordinator: ArticleExtractorDelegate {
func articleExtractionDidFail(with: Error) {
// makeToolbarValidate()
articleViewController?.articleExtractorButtonState = .error
}
func articleExtractionDidComplete(extractedArticle: ExtractedArticle) {
if let article = currentArticle, articleExtractor?.state != .cancelled {
isShowingExtractedArticle = true
articleViewController?.state = .extracted(article, extractedArticle)
// makeToolbarValidate()
articleViewController?.articleExtractorButtonState = .on
}
}
@ -1243,6 +1246,7 @@ private extension SceneCoordinator {
extractor.delegate = self
extractor.process()
articleExtractor = extractor
articleViewController?.articleExtractorButtonState = .animated
}
}