Compare commits
5 Commits
0c0180264e
...
81ac3708a3
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 81ac3708a3 | |
Shadowfacts | 8e9e0fa346 | |
Shadowfacts | b6f32ca6be | |
Shadowfacts | e042754be1 | |
Shadowfacts | 38ac5858a9 |
|
@ -218,9 +218,9 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
|
||||||
} else {
|
} else {
|
||||||
direction = .none
|
direction = .none
|
||||||
}
|
}
|
||||||
container.setRoot(newRoot, animating: direction)
|
container.setRoot(newRoot, for: account, animating: direction)
|
||||||
} else {
|
} else {
|
||||||
window!.rootViewController = AccountSwitchingContainerViewController(root: newRoot)
|
window!.rootViewController = AccountSwitchingContainerViewController(root: newRoot, for: account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,29 +9,9 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
struct MainComposeTextView: View {
|
struct MainComposeTextView: View, PlaceholderViewProvider {
|
||||||
@ObservedObject var draft: Draft
|
@ObservedObject var draft: Draft
|
||||||
@State private var placeholder: Text = {
|
@State private var placeholder: PlaceholderView = Self.placeholderView()
|
||||||
let components = Calendar.current.dateComponents([.month, .day], from: Date())
|
|
||||||
if components.month == 3 && components.day == 14 {
|
|
||||||
if Date().formatted(date: .numeric, time: .omitted).starts(with: "3") {
|
|
||||||
return Text("Happy π day!")
|
|
||||||
}
|
|
||||||
} else if components.month == 9 && components.day == 5 {
|
|
||||||
// https://weirder.earth/@noracodes/109276419847254552
|
|
||||||
// https://retrocomputing.stackexchange.com/questions/14763/what-warning-was-given-on-attempting-to-post-to-usenet-circa-1990
|
|
||||||
return Text("This program posts news to thousands of machines throughout the entire populated world. Please be sure you know what you are doing.").italic()
|
|
||||||
} else if components.month == 9 && components.day == 21 {
|
|
||||||
return Text("Do you remember?")
|
|
||||||
} else if components.month == 10 && components.day == 31 {
|
|
||||||
if .random() {
|
|
||||||
return Text("Post something spooky!")
|
|
||||||
} else {
|
|
||||||
return Text("Any questions?")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Text("What's on your mind?")
|
|
||||||
}()
|
|
||||||
|
|
||||||
let minHeight: CGFloat = 150
|
let minHeight: CGFloat = 150
|
||||||
@State private var height: CGFloat?
|
@State private var height: CGFloat?
|
||||||
|
@ -68,6 +48,38 @@ struct MainComposeTextView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
static func placeholderView() -> some View {
|
||||||
|
let components = Calendar.current.dateComponents([.month, .day], from: Date())
|
||||||
|
if components.month == 3 && components.day == 14,
|
||||||
|
Date().formatted(date: .numeric, time: .omitted).starts(with: "3") {
|
||||||
|
Text("Happy π day!")
|
||||||
|
} else if components.month == 4 && components.day == 1 {
|
||||||
|
Text("April Fool's!").rotationEffect(.radians(.pi), anchor: .center)
|
||||||
|
} else if components.month == 9 && components.day == 5 {
|
||||||
|
// https://weirder.earth/@noracodes/109276419847254552
|
||||||
|
// https://retrocomputing.stackexchange.com/questions/14763/what-warning-was-given-on-attempting-to-post-to-usenet-circa-1990
|
||||||
|
Text("This program posts news to thousands of machines throughout the entire populated world. Please be sure you know what you are doing.").italic()
|
||||||
|
} else if components.month == 9 && components.day == 21 {
|
||||||
|
Text("Do you remember?")
|
||||||
|
} else if components.month == 10 && components.day == 31 {
|
||||||
|
if .random() {
|
||||||
|
Text("Post something spooky!")
|
||||||
|
} else {
|
||||||
|
Text("Any questions?")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("What's on your mind?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exists to provide access to the type alias since the @State property needs it to be explicit
|
||||||
|
private protocol PlaceholderViewProvider {
|
||||||
|
associatedtype PlaceholderView: View
|
||||||
|
@ViewBuilder
|
||||||
|
static func placeholderView() -> PlaceholderView
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MainComposeWrappedTextView: UIViewRepresentable {
|
struct MainComposeWrappedTextView: UIViewRepresentable {
|
||||||
|
|
|
@ -11,9 +11,13 @@ import ScreenCorners
|
||||||
|
|
||||||
class AccountSwitchingContainerViewController: UIViewController {
|
class AccountSwitchingContainerViewController: UIViewController {
|
||||||
|
|
||||||
|
private var currentAccountID: String
|
||||||
private(set) var root: TuskerRootViewController
|
private(set) var root: TuskerRootViewController
|
||||||
|
|
||||||
init(root: TuskerRootViewController) {
|
private var userActivities: [String: NSUserActivity] = [:]
|
||||||
|
|
||||||
|
init(root: TuskerRootViewController, for account: LocalData.UserAccountInfo) {
|
||||||
|
self.currentAccountID = account.id
|
||||||
self.root = root
|
self.root = root
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
@ -29,14 +33,27 @@ class AccountSwitchingContainerViewController: UIViewController {
|
||||||
embedChild(root)
|
embedChild(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setRoot(_ newRoot: TuskerRootViewController, animating direction: AnimationDirection) {
|
func setRoot(_ newRoot: TuskerRootViewController, for account: LocalData.UserAccountInfo, animating direction: AnimationDirection) {
|
||||||
let oldRoot = self.root
|
let oldRoot = self.root
|
||||||
if direction == .none {
|
if direction == .none {
|
||||||
oldRoot.removeViewAndController()
|
oldRoot.removeViewAndController()
|
||||||
}
|
}
|
||||||
|
if let activity = oldRoot.stateRestorationActivity() {
|
||||||
|
stateRestorationLogger.debug("AccountSwitchingContainer: saving \(activity.activityType, privacy: .public) for \(self.currentAccountID, privacy: .public)")
|
||||||
|
userActivities[currentAccountID] = activity
|
||||||
|
}
|
||||||
|
|
||||||
|
self.currentAccountID = account.id
|
||||||
self.root = newRoot
|
self.root = newRoot
|
||||||
embedChild(newRoot)
|
embedChild(newRoot)
|
||||||
|
|
||||||
|
if let activity = userActivities.removeValue(forKey: account.id) {
|
||||||
|
stateRestorationLogger.debug("AccountSwitchingContainer: restoring \(activity.activityType, privacy: .public) for \(account.id, privacy: .public)")
|
||||||
|
let context = StateRestorationUserActivityHandlingContext(root: newRoot)
|
||||||
|
_ = activity.handleResume(manager: UserActivityManager(scene: view.window!.windowScene!, context: context))
|
||||||
|
context.finalize(activity: activity)
|
||||||
|
}
|
||||||
|
|
||||||
if direction != .none {
|
if direction != .none {
|
||||||
if UIAccessibility.prefersCrossFadeTransitions {
|
if UIAccessibility.prefersCrossFadeTransitions {
|
||||||
newRoot.view.alpha = 0
|
newRoot.view.alpha = 0
|
||||||
|
|
|
@ -14,12 +14,13 @@ class TimelineGapCollectionViewCell: UICollectionViewCell {
|
||||||
|
|
||||||
private let indicator = UIActivityIndicatorView(style: .medium)
|
private let indicator = UIActivityIndicatorView(style: .medium)
|
||||||
private let chevronView = AnimatingChevronView()
|
private let chevronView = AnimatingChevronView()
|
||||||
|
private let fillView = UIView()
|
||||||
|
|
||||||
var fillGap: ((TimelineGapDirection) async -> Void)?
|
var fillGap: ((TimelineGapDirection) async -> Void)?
|
||||||
|
|
||||||
override var isHighlighted: Bool {
|
override var isHighlighted: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
backgroundColor = isHighlighted ? .appFill : .appGroupedBackground
|
backgroundColor = isHighlighted ? .appFill : .appSecondaryBackground
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,17 +39,22 @@ class TimelineGapCollectionViewCell: UICollectionViewCell {
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
backgroundColor = .appGroupedBackground
|
backgroundColor = .appSecondaryBackground
|
||||||
|
|
||||||
indicator.isHidden = true
|
fillView.backgroundColor = .tintColor
|
||||||
indicator.color = .tintColor
|
fillView.alpha = 0.75
|
||||||
|
fillView.layer.masksToBounds = true
|
||||||
|
fillView.layer.cornerRadius = 8
|
||||||
|
fillView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
contentView.addSubview(fillView)
|
||||||
|
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = "Load more"
|
label.text = "Load more"
|
||||||
label.font = .preferredFont(forTextStyle: .headline)
|
label.font = .preferredFont(forTextStyle: .headline)
|
||||||
label.adjustsFontForContentSizeCategory = true
|
label.adjustsFontForContentSizeCategory = true
|
||||||
label.textColor = .tintColor
|
label.textColor = .white
|
||||||
|
|
||||||
|
chevronView.tintColor = .white
|
||||||
chevronView.update(direction: .above)
|
chevronView.update(direction: .above)
|
||||||
|
|
||||||
let stack = UIStackView(arrangedSubviews: [
|
let stack = UIStackView(arrangedSubviews: [
|
||||||
|
@ -60,15 +66,25 @@ class TimelineGapCollectionViewCell: UICollectionViewCell {
|
||||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||||
contentView.addSubview(stack)
|
contentView.addSubview(stack)
|
||||||
|
|
||||||
|
indicator.isHidden = true
|
||||||
|
indicator.color = .tintColor
|
||||||
indicator.translatesAutoresizingMaskIntoConstraints = false
|
indicator.translatesAutoresizingMaskIntoConstraints = false
|
||||||
contentView.addSubview(indicator)
|
contentView.addSubview(indicator)
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
stack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
stack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||||||
stack.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
stack.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||||
indicator.trailingAnchor.constraint(equalTo: stack.leadingAnchor, constant: -8),
|
|
||||||
|
fillView.leadingAnchor.constraint(equalTo: stack.leadingAnchor, constant: -8),
|
||||||
|
// optical centering, babey
|
||||||
|
fillView.trailingAnchor.constraint(equalTo: stack.trailingAnchor, constant: 10),
|
||||||
|
fillView.topAnchor.constraint(equalTo: stack.topAnchor, constant: -4),
|
||||||
|
fillView.bottomAnchor.constraint(equalTo: stack.bottomAnchor, constant: 4),
|
||||||
|
|
||||||
|
indicator.trailingAnchor.constraint(equalTo: stack.leadingAnchor, constant: -12),
|
||||||
indicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
indicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||||
contentView.heightAnchor.constraint(equalToConstant: 44),
|
|
||||||
|
contentView.heightAnchor.constraint(equalToConstant: 52),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -718,28 +718,16 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func refresh() {
|
@objc func refresh() {
|
||||||
Task {
|
Task { @MainActor in
|
||||||
if case .notLoadedInitial = controller.state {
|
if case .notLoadedInitial = controller.state {
|
||||||
await controller.loadInitial()
|
await controller.loadInitial()
|
||||||
#if !targetEnvironment(macCatalyst)
|
|
||||||
collectionView.refreshControl?.endRefreshing()
|
|
||||||
#endif
|
|
||||||
} else {
|
} else {
|
||||||
@MainActor
|
|
||||||
func loadNewerAndEndRefreshing() async {
|
|
||||||
await controller.loadNewer()
|
await controller.loadNewer()
|
||||||
|
}
|
||||||
#if !targetEnvironment(macCatalyst)
|
#if !targetEnvironment(macCatalyst)
|
||||||
collectionView.refreshControl?.endRefreshing()
|
collectionView.refreshControl?.endRefreshing()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// I'm not sure whether this should move into TimelineLikeController/TimelineLikeCollectionViewController
|
|
||||||
let (_, presentItems) = await (loadNewerAndEndRefreshing(), try? loadInitial())
|
|
||||||
if let presentItems, !presentItems.isEmpty {
|
|
||||||
insertPresentItemsAndShowJumpToast(presentItems)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func syncAndCheckPresentIfEnoughTimeElapsed() {
|
private func syncAndCheckPresentIfEnoughTimeElapsed() {
|
||||||
|
|
|
@ -32,15 +32,6 @@ class UserActivityManager {
|
||||||
scene.session.mastodonController!
|
scene.session.mastodonController!
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getMainViewController() -> TuskerRootViewController {
|
|
||||||
let window = scene.windows.first { $0.isKeyWindow } ?? scene.windows.first!
|
|
||||||
return window.rootViewController as! TuskerRootViewController
|
|
||||||
}
|
|
||||||
|
|
||||||
private func present(_ vc: UIViewController, animated: Bool = true) {
|
|
||||||
getMainViewController().present(vc, animated: animated)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func getAccount(from activity: NSUserActivity) -> LocalData.UserAccountInfo? {
|
static func getAccount(from activity: NSUserActivity) -> LocalData.UserAccountInfo? {
|
||||||
guard let id = activity.userInfo?["accountID"] as? String else {
|
guard let id = activity.userInfo?["accountID"] as? String else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -213,25 +204,26 @@ class UserActivityManager {
|
||||||
func handleShowTimeline(activity: NSUserActivity) {
|
func handleShowTimeline(activity: NSUserActivity) {
|
||||||
guard let (timeline, positionInfo) = Self.getTimeline(from: activity) else { return }
|
guard let (timeline, positionInfo) = Self.getTimeline(from: activity) else { return }
|
||||||
|
|
||||||
let timelineVC: TimelineViewController
|
var timelineVC: TimelineViewController?
|
||||||
if let pinned = PinnedTimeline(timeline: timeline),
|
if let pinned = PinnedTimeline(timeline: timeline),
|
||||||
mastodonController.accountPreferences.pinnedTimelines.contains(pinned) {
|
mastodonController.accountPreferences.pinnedTimelines.contains(pinned) {
|
||||||
context.select(route: .timelines)
|
context.select(route: .timelines)
|
||||||
context.popToRoot()
|
context.popToRoot()
|
||||||
let pageController = context.topViewController as! TimelinesPageViewController
|
let pageController = context.topViewController as! TimelinesPageViewController
|
||||||
pageController.selectTimeline(pinned, animated: false)
|
pageController.selectTimeline(pinned, animated: false)
|
||||||
timelineVC = pageController.currentViewController as! TimelineViewController
|
timelineVC = pageController.currentViewController as? TimelineViewController
|
||||||
} else if case .list(let id) = timeline {
|
} else if case .list(let id) = timeline {
|
||||||
context.select(route: .list(id: id))
|
context.select(route: .list(id: id))
|
||||||
timelineVC = context.topViewController! as! TimelineViewController
|
timelineVC = context.topViewController as? TimelineViewController
|
||||||
} else {
|
} else {
|
||||||
context.select(route: .explore)
|
context.select(route: .explore)
|
||||||
context.popToRoot()
|
context.popToRoot()
|
||||||
timelineVC = TimelineViewController(for: timeline, mastodonController: mastodonController)
|
timelineVC = TimelineViewController(for: timeline, mastodonController: mastodonController)
|
||||||
context.push(timelineVC)
|
context.push(timelineVC!)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let positionInfo,
|
if let timelineVC,
|
||||||
|
let positionInfo,
|
||||||
context.isHandoff {
|
context.isHandoff {
|
||||||
Task {
|
Task {
|
||||||
await timelineVC.restoreStateFromHandoff(statusIDs: positionInfo.statusIDs, centerStatusID: positionInfo.centerStatusID)
|
await timelineVC.restoreStateFromHandoff(statusIDs: positionInfo.statusIDs, centerStatusID: positionInfo.centerStatusID)
|
||||||
|
|
Loading…
Reference in New Issue