Merge pull request #66 from tootsuite/feature/navigationBarState
Feature/navigation bar state
This commit is contained in:
commit
9e7e042127
|
@ -169,10 +169,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"home_timeline": {
|
"home_timeline": {
|
||||||
"title": "Home"
|
"title": "Home",
|
||||||
|
"navigation_bar_state": {
|
||||||
|
"offline": "Offline",
|
||||||
|
"new_posts": "See new posts",
|
||||||
|
"published": "Published!",
|
||||||
|
"Publishing": "Publishing post..."
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"public_timeline": {
|
"public_timeline": {
|
||||||
"title": "Public"
|
"title": "Public"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8E25C8228A004A627A /* UIButton.swift */; };
|
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8E25C8228A004A627A /* UIButton.swift */; };
|
||||||
2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D45E5BE25C9549700A6D639 /* PublicTimelineViewModel+State.swift */; };
|
2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D45E5BE25C9549700A6D639 /* PublicTimelineViewModel+State.swift */; };
|
||||||
2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */; };
|
2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */; };
|
||||||
|
2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D571B2E26004EC000540450 /* NavigationBarProgressView.swift */; };
|
||||||
2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */; };
|
2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */; };
|
||||||
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */; };
|
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */; };
|
||||||
2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */ = {isa = PBXBuildFile; productRef = 2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */; };
|
2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */ = {isa = PBXBuildFile; productRef = 2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */; };
|
||||||
|
@ -72,6 +73,9 @@
|
||||||
2D7631B325C159F700929FB9 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631B225C159F700929FB9 /* Item.swift */; };
|
2D7631B325C159F700929FB9 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631B225C159F700929FB9 /* Item.swift */; };
|
||||||
2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */; };
|
2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */; };
|
||||||
2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D82BA0425E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift */; };
|
2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D82BA0425E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift */; };
|
||||||
|
2D8434F525FF465D00EECE90 /* HomeTimelineNavigationBarState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarState.swift */; };
|
||||||
|
2D8434FB25FF46B300EECE90 /* HomeTimelineNavigationBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8434FA25FF46B300EECE90 /* HomeTimelineNavigationBarView.swift */; };
|
||||||
|
2D84350525FF858100EECE90 /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D84350425FF858100EECE90 /* UIScrollView.swift */; };
|
||||||
2D927F0225C7E4F2004F19B8 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0125C7E4F2004F19B8 /* Mention.swift */; };
|
2D927F0225C7E4F2004F19B8 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0125C7E4F2004F19B8 /* Mention.swift */; };
|
||||||
2D927F0825C7E9A8004F19B8 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0725C7E9A8004F19B8 /* Tag.swift */; };
|
2D927F0825C7E9A8004F19B8 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0725C7E9A8004F19B8 /* Tag.swift */; };
|
||||||
2D927F0E25C7E9C9004F19B8 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0D25C7E9C9004F19B8 /* History.swift */; };
|
2D927F0E25C7E9C9004F19B8 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0D25C7E9C9004F19B8 /* History.swift */; };
|
||||||
|
@ -297,6 +301,7 @@
|
||||||
2D42FF8E25C8228A004A627A /* UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = "<group>"; };
|
2D42FF8E25C8228A004A627A /* UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = "<group>"; };
|
||||||
2D45E5BE25C9549700A6D639 /* PublicTimelineViewModel+State.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+State.swift"; sourceTree = "<group>"; };
|
2D45E5BE25C9549700A6D639 /* PublicTimelineViewModel+State.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+State.swift"; sourceTree = "<group>"; };
|
||||||
2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraint.swift; sourceTree = "<group>"; };
|
2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraint.swift; sourceTree = "<group>"; };
|
||||||
|
2D571B2E26004EC000540450 /* NavigationBarProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarProgressView.swift; sourceTree = "<group>"; };
|
||||||
2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonConfirmEmailViewController.swift; sourceTree = "<group>"; };
|
2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonConfirmEmailViewController.swift; sourceTree = "<group>"; };
|
||||||
2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonConfirmEmailViewModel.swift; sourceTree = "<group>"; };
|
2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonConfirmEmailViewModel.swift; sourceTree = "<group>"; };
|
||||||
2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlContainableScrollViews.swift; sourceTree = "<group>"; };
|
2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlContainableScrollViews.swift; sourceTree = "<group>"; };
|
||||||
|
@ -318,6 +323,9 @@
|
||||||
2D7631B225C159F700929FB9 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = "<group>"; };
|
2D7631B225C159F700929FB9 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = "<group>"; };
|
||||||
2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewControllerAppearance.swift; sourceTree = "<group>"; };
|
2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewControllerAppearance.swift; sourceTree = "<group>"; };
|
||||||
2D82BA0425E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModelNavigationDelegateShim.swift; sourceTree = "<group>"; };
|
2D82BA0425E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModelNavigationDelegateShim.swift; sourceTree = "<group>"; };
|
||||||
|
2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineNavigationBarState.swift; sourceTree = "<group>"; };
|
||||||
|
2D8434FA25FF46B300EECE90 /* HomeTimelineNavigationBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineNavigationBarView.swift; sourceTree = "<group>"; };
|
||||||
|
2D84350425FF858100EECE90 /* UIScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollView.swift; sourceTree = "<group>"; };
|
||||||
2D927F0125C7E4F2004F19B8 /* Mention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = "<group>"; };
|
2D927F0125C7E4F2004F19B8 /* Mention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = "<group>"; };
|
||||||
2D927F0725C7E9A8004F19B8 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
2D927F0725C7E9A8004F19B8 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
||||||
2D927F0D25C7E9C9004F19B8 /* History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = History.swift; sourceTree = "<group>"; };
|
2D927F0D25C7E9C9004F19B8 /* History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = History.swift; sourceTree = "<group>"; };
|
||||||
|
@ -587,6 +595,7 @@
|
||||||
children = (
|
children = (
|
||||||
2D152A8B25C295CC009AA50C /* StatusView.swift */,
|
2D152A8B25C295CC009AA50C /* StatusView.swift */,
|
||||||
2D694A7325F9EB4E0038ADDC /* ContentWarningOverlayView.swift */,
|
2D694A7325F9EB4E0038ADDC /* ContentWarningOverlayView.swift */,
|
||||||
|
2D571B2E26004EC000540450 /* NavigationBarProgressView.swift */,
|
||||||
);
|
);
|
||||||
path = Content;
|
path = Content;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -612,6 +621,8 @@
|
||||||
2D38F1EA25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift */,
|
2D38F1EA25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift */,
|
||||||
2D38F1F025CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift */,
|
2D38F1F025CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift */,
|
||||||
2D38F1F625CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift */,
|
2D38F1F625CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift */,
|
||||||
|
2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarState.swift */,
|
||||||
|
2D8434FA25FF46B300EECE90 /* HomeTimelineNavigationBarView.swift */,
|
||||||
);
|
);
|
||||||
path = HomeTimeline;
|
path = HomeTimeline;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1139,6 +1150,7 @@
|
||||||
2D206B9125F60EA700143C56 /* UIControl.swift */,
|
2D206B9125F60EA700143C56 /* UIControl.swift */,
|
||||||
5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */,
|
5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */,
|
||||||
DB47229625F9EFAD00DA7F53 /* NSManagedObjectContext.swift */,
|
DB47229625F9EFAD00DA7F53 /* NSManagedObjectContext.swift */,
|
||||||
|
2D84350425FF858100EECE90 /* UIScrollView.swift */,
|
||||||
);
|
);
|
||||||
path = Extension;
|
path = Extension;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1596,6 +1608,7 @@
|
||||||
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
||||||
DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */,
|
DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */,
|
||||||
DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */,
|
DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */,
|
||||||
|
2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */,
|
||||||
0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */,
|
0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */,
|
||||||
DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */,
|
DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */,
|
||||||
DB92CF7225E7BB98002C1017 /* PollOptionTableViewCell.swift in Sources */,
|
DB92CF7225E7BB98002C1017 /* PollOptionTableViewCell.swift in Sources */,
|
||||||
|
@ -1603,7 +1616,9 @@
|
||||||
2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */,
|
2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */,
|
||||||
2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */,
|
2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */,
|
||||||
DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */,
|
DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */,
|
||||||
|
2D8434FB25FF46B300EECE90 /* HomeTimelineNavigationBarView.swift in Sources */,
|
||||||
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */,
|
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */,
|
||||||
|
2D8434F525FF465D00EECE90 /* HomeTimelineNavigationBarState.swift in Sources */,
|
||||||
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */,
|
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */,
|
||||||
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */,
|
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */,
|
||||||
DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */,
|
DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */,
|
||||||
|
@ -1638,6 +1653,7 @@
|
||||||
2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */,
|
2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */,
|
||||||
5DF1054725F8870E00D6C0D4 /* VideoPlayerViewModel.swift in Sources */,
|
5DF1054725F8870E00D6C0D4 /* VideoPlayerViewModel.swift in Sources */,
|
||||||
2D38F1E525CD46C100561493 /* HomeTimelineViewModel.swift in Sources */,
|
2D38F1E525CD46C100561493 /* HomeTimelineViewModel.swift in Sources */,
|
||||||
|
2D84350525FF858100EECE90 /* UIScrollView.swift in Sources */,
|
||||||
2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */,
|
2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */,
|
||||||
2DA6055125F74407006356F9 /* AudioContainerViewModel.swift in Sources */,
|
2DA6055125F74407006356F9 /* AudioContainerViewModel.swift in Sources */,
|
||||||
0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */,
|
0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// UIScrollView.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by sxiaojian on 2021/3/15.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension UIScrollView {
|
||||||
|
public enum ScrollDirection {
|
||||||
|
case top
|
||||||
|
case bottom
|
||||||
|
case left
|
||||||
|
case right
|
||||||
|
}
|
||||||
|
|
||||||
|
public func scroll(to direction: ScrollDirection, animated: Bool) {
|
||||||
|
let offset: CGPoint
|
||||||
|
switch direction {
|
||||||
|
case .top:
|
||||||
|
offset = CGPoint(x: contentOffset.x, y: -adjustedContentInset.top)
|
||||||
|
case .bottom:
|
||||||
|
offset = CGPoint(x: contentOffset.x, y: max(-adjustedContentInset.top, contentSize.height - frame.height + adjustedContentInset.bottom))
|
||||||
|
case .left:
|
||||||
|
offset = CGPoint(x: -adjustedContentInset.left, y: contentOffset.y)
|
||||||
|
case .right:
|
||||||
|
offset = CGPoint(x: max(-adjustedContentInset.left, contentSize.width - frame.width + adjustedContentInset.right), y: contentOffset.y)
|
||||||
|
}
|
||||||
|
setContentOffset(offset, animated: animated)
|
||||||
|
}
|
||||||
|
}
|
|
@ -67,6 +67,10 @@ internal enum Asset {
|
||||||
internal static let invalid = ColorAsset(name: "Colors/TextField/invalid")
|
internal static let invalid = ColorAsset(name: "Colors/TextField/invalid")
|
||||||
internal static let valid = ColorAsset(name: "Colors/TextField/valid")
|
internal static let valid = ColorAsset(name: "Colors/TextField/valid")
|
||||||
}
|
}
|
||||||
|
internal static let backgroundLight = ColorAsset(name: "Colors/backgroundLight")
|
||||||
|
internal static let buttonDefault = ColorAsset(name: "Colors/buttonDefault")
|
||||||
|
internal static let buttonDisabled = ColorAsset(name: "Colors/buttonDisabled")
|
||||||
|
internal static let buttonInactive = ColorAsset(name: "Colors/buttonInactive")
|
||||||
internal static let lightAlertYellow = ColorAsset(name: "Colors/lightAlertYellow")
|
internal static let lightAlertYellow = ColorAsset(name: "Colors/lightAlertYellow")
|
||||||
internal static let lightBackground = ColorAsset(name: "Colors/lightBackground")
|
internal static let lightBackground = ColorAsset(name: "Colors/lightBackground")
|
||||||
internal static let lightBrandBlue = ColorAsset(name: "Colors/lightBrandBlue")
|
internal static let lightBrandBlue = ColorAsset(name: "Colors/lightBrandBlue")
|
||||||
|
|
|
@ -162,6 +162,16 @@ internal enum L10n {
|
||||||
internal enum HomeTimeline {
|
internal enum HomeTimeline {
|
||||||
/// Home
|
/// Home
|
||||||
internal static let title = L10n.tr("Localizable", "Scene.HomeTimeline.Title")
|
internal static let title = L10n.tr("Localizable", "Scene.HomeTimeline.Title")
|
||||||
|
internal enum NavigationBarState {
|
||||||
|
/// See new posts
|
||||||
|
internal static let newPosts = L10n.tr("Localizable", "Scene.HomeTimeline.NavigationBarState.NewPosts")
|
||||||
|
/// Offline
|
||||||
|
internal static let offline = L10n.tr("Localizable", "Scene.HomeTimeline.NavigationBarState.Offline")
|
||||||
|
/// Published!
|
||||||
|
internal static let published = L10n.tr("Localizable", "Scene.HomeTimeline.NavigationBarState.Published")
|
||||||
|
/// Publishing post...
|
||||||
|
internal static let publishing = L10n.tr("Localizable", "Scene.HomeTimeline.NavigationBarState.Publishing")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
internal enum PublicTimeline {
|
internal enum PublicTimeline {
|
||||||
/// Public
|
/// Public
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.910",
|
||||||
|
"green" : "0.882",
|
||||||
|
"red" : "0.851"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.851",
|
||||||
|
"green" : "0.565",
|
||||||
|
"red" : "0.169"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.784",
|
||||||
|
"green" : "0.682",
|
||||||
|
"red" : "0.608"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.549",
|
||||||
|
"green" : "0.510",
|
||||||
|
"red" : "0.431"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,20 @@
|
||||||
{
|
{
|
||||||
"colors" : [
|
"colors" : [
|
||||||
{
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
"color" : {
|
"color" : {
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"red" : "0.792",
|
||||||
"blue" : "0.016",
|
"blue" : "0.016",
|
||||||
"green" : "0.561",
|
"green" : "0.561",
|
||||||
"red" : "0.792"
|
"alpha" : "1.000"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"version" : 1,
|
||||||
"version" : 1
|
"author" : "xcode"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,20 +1,20 @@
|
||||||
{
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
},
|
||||||
"colors" : [
|
"colors" : [
|
||||||
{
|
{
|
||||||
"color" : {
|
"color" : {
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
|
"red" : "0.875",
|
||||||
"blue" : "0.353",
|
"blue" : "0.353",
|
||||||
"green" : "0.251",
|
"green" : "0.251"
|
||||||
"red" : "0.875"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"info" : {
|
}
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,11 @@
|
||||||
{
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
},
|
||||||
"colors" : [
|
"colors" : [
|
||||||
{
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
"color" : {
|
"color" : {
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
|
@ -9,12 +14,7 @@
|
||||||
"green" : "0.137",
|
"green" : "0.137",
|
||||||
"red" : "0.122"
|
"red" : "0.122"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"info" : {
|
}
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +1,20 @@
|
||||||
{
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
},
|
||||||
"colors" : [
|
"colors" : [
|
||||||
{
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
"color" : {
|
"color" : {
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
"components" : {
|
||||||
|
"blue" : "0.263",
|
||||||
|
"green" : "0.235",
|
||||||
"alpha" : "0.600",
|
"alpha" : "0.600",
|
||||||
"blue" : "67",
|
"red" : "0.235"
|
||||||
"green" : "60",
|
},
|
||||||
"red" : "60"
|
"color-space" : "srgb"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"info" : {
|
}
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +1,20 @@
|
||||||
{
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
},
|
||||||
"colors" : [
|
"colors" : [
|
||||||
{
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
"color" : {
|
"color" : {
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.604",
|
|
||||||
"green" : "0.741",
|
"green" : "0.741",
|
||||||
"red" : "0.475"
|
"red" : "0.475",
|
||||||
|
"blue" : "0.604"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"info" : {
|
}
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +1,20 @@
|
||||||
{
|
{
|
||||||
"colors" : [
|
"colors" : [
|
||||||
{
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
"color" : {
|
"color" : {
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
"components" : {
|
||||||
|
"red" : "0.996",
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.996",
|
"blue" : "0.996",
|
||||||
"green" : "1.000",
|
"green" : "1.000"
|
||||||
"red" : "0.996"
|
},
|
||||||
}
|
"color-space" : "srgb"
|
||||||
},
|
}
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"version" : 1,
|
||||||
"version" : 1
|
"author" : "xcode"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -46,6 +46,10 @@
|
||||||
"Scene.ConfirmEmail.Subtitle" = "We just sent an email to %@,
|
"Scene.ConfirmEmail.Subtitle" = "We just sent an email to %@,
|
||||||
tap the link to confirm your account.";
|
tap the link to confirm your account.";
|
||||||
"Scene.ConfirmEmail.Title" = "One last thing.";
|
"Scene.ConfirmEmail.Title" = "One last thing.";
|
||||||
|
"Scene.HomeTimeline.NavigationBarState.NewPosts" = "See new posts";
|
||||||
|
"Scene.HomeTimeline.NavigationBarState.Offline" = "Offline";
|
||||||
|
"Scene.HomeTimeline.NavigationBarState.Published" = "Published!";
|
||||||
|
"Scene.HomeTimeline.NavigationBarState.Publishing" = "Publishing post...";
|
||||||
"Scene.HomeTimeline.Title" = "Home";
|
"Scene.HomeTimeline.Title" = "Home";
|
||||||
"Scene.PublicTimeline.Title" = "Public";
|
"Scene.PublicTimeline.Title" = "Public";
|
||||||
"Scene.Register.Error.Item.Agreement" = "Agreement";
|
"Scene.Register.Error.Item.Agreement" = "Agreement";
|
||||||
|
@ -92,4 +96,4 @@ any server.";
|
||||||
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
||||||
"Scene.ServerRules.Title" = "Some ground rules.";
|
"Scene.ServerRules.Title" = "Some ground rules.";
|
||||||
"Scene.Welcome.Slogan" = "Social networking
|
"Scene.Welcome.Slogan" = "Social networking
|
||||||
back in your hands.";
|
back in your hands.";
|
|
@ -0,0 +1,156 @@
|
||||||
|
//
|
||||||
|
// HomeTimelineNavigationBarState.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by sxiaojian on 2021/3/15.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
final class HomeTimelineNavigationBarState {
|
||||||
|
static let errorCountMax: Int = 3
|
||||||
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
var errorCountDownDispose: AnyCancellable?
|
||||||
|
var timerDispose: AnyCancellable?
|
||||||
|
var networkErrorCountSubject = PassthroughSubject<Bool, Never>()
|
||||||
|
|
||||||
|
var newTopContent = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
var hasContentBeforeFetching: Bool = true
|
||||||
|
|
||||||
|
weak var viewController: HomeTimelineViewController?
|
||||||
|
|
||||||
|
let timestampUpdatePublisher = Timer.publish(every: NavigationBarProgressView.progressAnimationDuration, on: .main, in: .common)
|
||||||
|
.autoconnect()
|
||||||
|
.share()
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
reCountdown()
|
||||||
|
subscribeNewContent()
|
||||||
|
addGesture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension HomeTimelineNavigationBarState {
|
||||||
|
func showOfflineInNavigationBar() {
|
||||||
|
HomeTimelineNavigationBarView.progressView.removeFromSuperview()
|
||||||
|
viewController?.navigationItem.titleView = HomeTimelineNavigationBarView.offlineView
|
||||||
|
}
|
||||||
|
|
||||||
|
func showNewPostsInNavigationBar() {
|
||||||
|
HomeTimelineNavigationBarView.progressView.removeFromSuperview()
|
||||||
|
viewController?.navigationItem.titleView = HomeTimelineNavigationBarView.newPostsView
|
||||||
|
}
|
||||||
|
|
||||||
|
func showPublishingNewPostInNavigationBar() {
|
||||||
|
let progressView = HomeTimelineNavigationBarView.progressView
|
||||||
|
if let navigationBar = viewController?.navigationBar(), progressView.superview == nil {
|
||||||
|
navigationBar.addSubview(progressView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
progressView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor),
|
||||||
|
progressView.leadingAnchor.constraint(equalTo: navigationBar.leadingAnchor),
|
||||||
|
progressView.trailingAnchor.constraint(equalTo: navigationBar.trailingAnchor),
|
||||||
|
progressView.heightAnchor.constraint(equalToConstant: 3)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
progressView.layoutIfNeeded()
|
||||||
|
progressView.progress = 0
|
||||||
|
viewController?.navigationItem.titleView = HomeTimelineNavigationBarView.publishingLabel
|
||||||
|
|
||||||
|
var times: Int = 0
|
||||||
|
timerDispose = timestampUpdatePublisher
|
||||||
|
.map { _ in
|
||||||
|
times += 1
|
||||||
|
return Double(times)
|
||||||
|
}
|
||||||
|
.scan(0) { value, count in
|
||||||
|
value + 1 / pow(Double(2), count)
|
||||||
|
}
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { value in
|
||||||
|
print(value)
|
||||||
|
progressView.progress = CGFloat(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showPublishedInNavigationBar() {
|
||||||
|
timerDispose = nil
|
||||||
|
HomeTimelineNavigationBarView.progressView.removeFromSuperview()
|
||||||
|
viewController?.navigationItem.titleView = HomeTimelineNavigationBarView.publishedView
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
|
||||||
|
self.showMastodonLogoInNavigationBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showMastodonLogoInNavigationBar() {
|
||||||
|
HomeTimelineNavigationBarView.progressView.removeFromSuperview()
|
||||||
|
viewController?.navigationItem.titleView = HomeTimelineNavigationBarView.mastodonLogoTitleView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension HomeTimelineNavigationBarState {
|
||||||
|
func handleScrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
let contentOffsetY = scrollView.contentOffset.y
|
||||||
|
let isShowingNewPostsNew = viewController?.navigationItem.titleView === HomeTimelineNavigationBarView.newPostsView
|
||||||
|
if !isShowingNewPostsNew {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let isTop = contentOffsetY < -scrollView.contentInset.top
|
||||||
|
if isTop {
|
||||||
|
newTopContent.value = false
|
||||||
|
showMastodonLogoInNavigationBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addGesture() {
|
||||||
|
let tapGesture = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||||
|
tapGesture.addTarget(self, action: #selector(HomeTimelineNavigationBarState.newPostsNewDidPressed(_:)))
|
||||||
|
HomeTimelineNavigationBarView.newPostsView.addGestureRecognizer(tapGesture)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func newPostsNewDidPressed(_ sender: UITapGestureRecognizer) {
|
||||||
|
if newTopContent.value == true {
|
||||||
|
viewController?.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension HomeTimelineNavigationBarState {
|
||||||
|
func subscribeNewContent() {
|
||||||
|
newTopContent
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] newContent in
|
||||||
|
guard let self = self else { return }
|
||||||
|
if self.hasContentBeforeFetching, newContent {
|
||||||
|
self.showNewPostsInNavigationBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reCountdown() {
|
||||||
|
errorCountDownDispose = networkErrorCountSubject
|
||||||
|
.scan(0) { value, _ in value + 1 }
|
||||||
|
.sink(receiveValue: { [weak self] errorCount in
|
||||||
|
guard let self = self else { return }
|
||||||
|
if errorCount >= HomeTimelineNavigationBarState.errorCountMax {
|
||||||
|
self.showOfflineInNavigationBar()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiveCompletion(completion: Subscribers.Completion<Error>) {
|
||||||
|
switch completion {
|
||||||
|
case .failure:
|
||||||
|
networkErrorCountSubject.send(false)
|
||||||
|
case .finished:
|
||||||
|
reCountdown()
|
||||||
|
let isShowingOfflineView = viewController?.navigationItem.titleView === HomeTimelineNavigationBarView.offlineView
|
||||||
|
if isShowingOfflineView {
|
||||||
|
showMastodonLogoInNavigationBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
//
|
||||||
|
// HomeTimelineNavigationBarView.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by sxiaojian on 2021/3/15.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
final class HomeTimelineNavigationBarView {
|
||||||
|
static let mastodonLogoTitleView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: Asset.Asset.mastodonTextLogo.image.withRenderingMode(.alwaysTemplate))
|
||||||
|
imageView.tintColor = Asset.Colors.Label.primary.color
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
static let offlineView: UIView = {
|
||||||
|
let view = HomeTimelineNavigationBarView.backgroundViewWithColor(color: Asset.Colors.lightDangerRed.color)
|
||||||
|
let label = HomeTimelineNavigationBarView.contentLabel(text: L10n.Scene.HomeTimeline.NavigationBarState.offline)
|
||||||
|
HomeTimelineNavigationBarView.addLabelToView(label: label, view: view)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
static let newPostsView: UIView = {
|
||||||
|
let view = HomeTimelineNavigationBarView.backgroundViewWithColor(color: Asset.Colors.Button.highlight.color)
|
||||||
|
let label = HomeTimelineNavigationBarView.contentLabel(text: L10n.Scene.HomeTimeline.NavigationBarState.newPosts)
|
||||||
|
HomeTimelineNavigationBarView.addLabelToView(label: label, view: view)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
static var publishedView: UIView = {
|
||||||
|
let view = HomeTimelineNavigationBarView.backgroundViewWithColor(color: Asset.Colors.lightSuccessGreen.color)
|
||||||
|
let label = HomeTimelineNavigationBarView.contentLabel(text: L10n.Scene.HomeTimeline.NavigationBarState.published)
|
||||||
|
HomeTimelineNavigationBarView.addLabelToView(label: label, view: view)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
static var progressView: NavigationBarProgressView = {
|
||||||
|
let view = NavigationBarProgressView()
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
static var publishingLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.textColor = .black
|
||||||
|
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))
|
||||||
|
label.text = L10n.Scene.HomeTimeline.NavigationBarState.publishing
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
static func addLabelToView(label: UILabel, view: UIView) {
|
||||||
|
view.addSubview(label)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
|
||||||
|
view.trailingAnchor.constraint(equalTo: label.trailingAnchor, constant: 16),
|
||||||
|
label.topAnchor.constraint(equalTo: view.topAnchor, constant: 1),
|
||||||
|
view.bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: 1),
|
||||||
|
view.heightAnchor.constraint(equalToConstant: 24),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
static func backgroundViewWithColor(color: UIColor) -> UIView {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = color
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.layer.cornerRadius = 12
|
||||||
|
view.clipsToBounds = true
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
static func contentLabel(text: String) -> UILabel {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.textColor = .white
|
||||||
|
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .bold))
|
||||||
|
label.text = text
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,11 +64,7 @@ extension HomeTimelineViewController {
|
||||||
|
|
||||||
title = L10n.Scene.HomeTimeline.title
|
title = L10n.Scene.HomeTimeline.title
|
||||||
view.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
view.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
||||||
navigationItem.titleView = {
|
navigationItem.titleView = HomeTimelineNavigationBarView.mastodonLogoTitleView
|
||||||
let imageView = UIImageView(image: Asset.Asset.mastodonTextLogo.image.withRenderingMode(.alwaysTemplate))
|
|
||||||
imageView.tintColor = Asset.Colors.Label.primary.color
|
|
||||||
return imageView
|
|
||||||
}()
|
|
||||||
navigationItem.leftBarButtonItem = settingBarButtonItem
|
navigationItem.leftBarButtonItem = settingBarButtonItem
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// long press to trigger debug menu
|
// long press to trigger debug menu
|
||||||
|
@ -101,6 +97,7 @@ extension HomeTimelineViewController {
|
||||||
])
|
])
|
||||||
|
|
||||||
viewModel.tableView = tableView
|
viewModel.tableView = tableView
|
||||||
|
viewModel.viewController = self
|
||||||
viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self
|
viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
viewModel.setupDiffableDataSource(
|
viewModel.setupDiffableDataSource(
|
||||||
|
@ -208,6 +205,7 @@ extension HomeTimelineViewController {
|
||||||
extension HomeTimelineViewController {
|
extension HomeTimelineViewController {
|
||||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
handleScrollViewDidScroll(scrollView)
|
handleScrollViewDidScroll(scrollView)
|
||||||
|
self.viewModel.homeTimelineNavigationBarState.handleScrollViewDidScroll(scrollView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,7 @@ extension HomeTimelineViewModel.LoadLatestState {
|
||||||
stateMachine.enter(Fail.self)
|
stateMachine.enter(Fail.self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
viewModel.homeTimelineNavigationBarState.hasContentBeforeFetching = !latestTootIDs.isEmpty
|
||||||
let end = CACurrentMediaTime()
|
let end = CACurrentMediaTime()
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: collect toots id cost: %.2fs", ((#file as NSString).lastPathComponent), #line, #function, end - start)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: collect toots id cost: %.2fs", ((#file as NSString).lastPathComponent), #line, #function, end - start)
|
||||||
|
|
||||||
|
@ -80,6 +81,7 @@ extension HomeTimelineViewModel.LoadLatestState {
|
||||||
viewModel.context.apiService.homeTimeline(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox)
|
viewModel.context.apiService.homeTimeline(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
|
viewModel.homeTimelineNavigationBarState.receiveCompletion(completion: completion)
|
||||||
switch completion {
|
switch completion {
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
// TODO: handle error
|
// TODO: handle error
|
||||||
|
@ -97,9 +99,12 @@ extension HomeTimelineViewModel.LoadLatestState {
|
||||||
let toots = response.value
|
let toots = response.value
|
||||||
let newToots = toots.filter { !latestTootIDs.contains($0.id) }
|
let newToots = toots.filter { !latestTootIDs.contains($0.id) }
|
||||||
os_log("%{public}s[%{public}ld], %{public}s: load %{public}ld new toots", ((#file as NSString).lastPathComponent), #line, #function, newToots.count)
|
os_log("%{public}s[%{public}ld], %{public}s: load %{public}ld new toots", ((#file as NSString).lastPathComponent), #line, #function, newToots.count)
|
||||||
|
|
||||||
if newToots.isEmpty {
|
if newToots.isEmpty {
|
||||||
viewModel.isFetchingLatestTimeline.value = false
|
viewModel.isFetchingLatestTimeline.value = false
|
||||||
|
viewModel.homeTimelineNavigationBarState.newTopContent.value = false
|
||||||
|
} else {
|
||||||
|
viewModel.homeTimelineNavigationBarState.newTopContent.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &viewModel.disposeBag)
|
.store(in: &viewModel.disposeBag)
|
||||||
|
|
|
@ -68,6 +68,7 @@ extension HomeTimelineViewModel.LoadMiddleState {
|
||||||
.delay(for: .seconds(1), scheduler: DispatchQueue.main)
|
.delay(for: .seconds(1), scheduler: DispatchQueue.main)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
|
viewModel.homeTimelineNavigationBarState.receiveCompletion(completion: completion)
|
||||||
switch completion {
|
switch completion {
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
// TODO: handle error
|
// TODO: handle error
|
||||||
|
|
|
@ -58,6 +58,7 @@ extension HomeTimelineViewModel.LoadOldestState {
|
||||||
.delay(for: .seconds(1), scheduler: DispatchQueue.main)
|
.delay(for: .seconds(1), scheduler: DispatchQueue.main)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
|
viewModel.homeTimelineNavigationBarState.receiveCompletion(completion: completion)
|
||||||
switch completion {
|
switch completion {
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
os_log("%{public}s[%{public}ld], %{public}s: fetch toots failed. %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
os_log("%{public}s[%{public}ld], %{public}s: fetch toots failed. %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||||
|
|
|
@ -29,9 +29,16 @@ final class HomeTimelineViewModel: NSObject {
|
||||||
let isFetchingLatestTimeline = CurrentValueSubject<Bool, Never>(false)
|
let isFetchingLatestTimeline = CurrentValueSubject<Bool, Never>(false)
|
||||||
let viewDidAppear = PassthroughSubject<Void, Never>()
|
let viewDidAppear = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
|
let homeTimelineNavigationBarState = HomeTimelineNavigationBarState()
|
||||||
|
|
||||||
weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate?
|
weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate?
|
||||||
weak var tableView: UITableView?
|
weak var tableView: UITableView?
|
||||||
weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
|
weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
|
||||||
|
weak var viewController: HomeTimelineViewController? {
|
||||||
|
willSet(value) {
|
||||||
|
self.homeTimelineNavigationBarState.viewController = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// output
|
// output
|
||||||
// top loader
|
// top loader
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
//
|
||||||
|
// NavigationBarProgressView.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by sxiaojian on 2021/3/16.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class NavigationBarProgressView: UIView {
|
||||||
|
|
||||||
|
static let progressAnimationDuration: TimeInterval = 0.3
|
||||||
|
|
||||||
|
let sliderView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = Asset.Colors.buttonDefault.color
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
var sliderTrailingAnchor: NSLayoutConstraint!
|
||||||
|
|
||||||
|
var progress: CGFloat = 0 {
|
||||||
|
willSet(value) {
|
||||||
|
sliderTrailingAnchor.constant = (1 - progress) * bounds.width
|
||||||
|
UIView.animate(withDuration: NavigationBarProgressView.progressAnimationDuration) {
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NavigationBarProgressView {
|
||||||
|
func _init() {
|
||||||
|
self.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
self.backgroundColor = .clear
|
||||||
|
addSubview(sliderView)
|
||||||
|
sliderTrailingAnchor = trailingAnchor.constraint(equalTo: sliderView.trailingAnchor)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
sliderView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
sliderView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
sliderView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
sliderTrailingAnchor
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue