forked from shadowfacts/Tusker
Allow pinning instance public timelines
This commit is contained in:
parent
fe32356bce
commit
8bd6f53f01
|
@ -20,6 +20,9 @@
|
||||||
D60088EF2980D8B5005B4D00 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D60088EE2980D8B5005B4D00 /* StoreKit.framework */; };
|
D60088EF2980D8B5005B4D00 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D60088EE2980D8B5005B4D00 /* StoreKit.framework */; };
|
||||||
D60088F22980DAA0005B4D00 /* TipJarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60088F12980DAA0005B4D00 /* TipJarView.swift */; };
|
D60088F22980DAA0005B4D00 /* TipJarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60088F12980DAA0005B4D00 /* TipJarView.swift */; };
|
||||||
D60089192981FEBA005B4D00 /* ConfettiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60089182981FEBA005B4D00 /* ConfettiView.swift */; };
|
D60089192981FEBA005B4D00 /* ConfettiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60089182981FEBA005B4D00 /* ConfettiView.swift */; };
|
||||||
|
D600891B29848289005B4D00 /* PinnedTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = D600891A29848289005B4D00 /* PinnedTimeline.swift */; };
|
||||||
|
D600891D298482F0005B4D00 /* PinnedTimelineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D600891C298482F0005B4D00 /* PinnedTimelineTests.swift */; };
|
||||||
|
D600891F29848DE2005B4D00 /* AddInstancePinnedTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D600891E29848DE2005B4D00 /* AddInstancePinnedTimelineView.swift */; };
|
||||||
D601FA5B29787AB100A8E8B5 /* AccountFollowsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D601FA5A29787AB100A8E8B5 /* AccountFollowsListViewController.swift */; };
|
D601FA5B29787AB100A8E8B5 /* AccountFollowsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D601FA5A29787AB100A8E8B5 /* AccountFollowsListViewController.swift */; };
|
||||||
D601FA5D297B2E6F00A8E8B5 /* ConversationCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D601FA5C297B2E6F00A8E8B5 /* ConversationCollectionViewController.swift */; };
|
D601FA5D297B2E6F00A8E8B5 /* ConversationCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D601FA5C297B2E6F00A8E8B5 /* ConversationCollectionViewController.swift */; };
|
||||||
D601FA5F297B339100A8E8B5 /* ExpandThreadCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D601FA5E297B339100A8E8B5 /* ExpandThreadCollectionViewCell.swift */; };
|
D601FA5F297B339100A8E8B5 /* ExpandThreadCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D601FA5E297B339100A8E8B5 /* ExpandThreadCollectionViewCell.swift */; };
|
||||||
|
@ -422,6 +425,9 @@
|
||||||
D60088F02980D938005B4D00 /* Tusker.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Tusker.storekit; sourceTree = "<group>"; };
|
D60088F02980D938005B4D00 /* Tusker.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Tusker.storekit; sourceTree = "<group>"; };
|
||||||
D60088F12980DAA0005B4D00 /* TipJarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipJarView.swift; sourceTree = "<group>"; };
|
D60088F12980DAA0005B4D00 /* TipJarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipJarView.swift; sourceTree = "<group>"; };
|
||||||
D60089182981FEBA005B4D00 /* ConfettiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfettiView.swift; sourceTree = "<group>"; };
|
D60089182981FEBA005B4D00 /* ConfettiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfettiView.swift; sourceTree = "<group>"; };
|
||||||
|
D600891A29848289005B4D00 /* PinnedTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTimeline.swift; sourceTree = "<group>"; };
|
||||||
|
D600891C298482F0005B4D00 /* PinnedTimelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTimelineTests.swift; sourceTree = "<group>"; };
|
||||||
|
D600891E29848DE2005B4D00 /* AddInstancePinnedTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddInstancePinnedTimelineView.swift; sourceTree = "<group>"; };
|
||||||
D601FA5A29787AB100A8E8B5 /* AccountFollowsListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFollowsListViewController.swift; sourceTree = "<group>"; };
|
D601FA5A29787AB100A8E8B5 /* AccountFollowsListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFollowsListViewController.swift; sourceTree = "<group>"; };
|
||||||
D601FA5C297B2E6F00A8E8B5 /* ConversationCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationCollectionViewController.swift; sourceTree = "<group>"; };
|
D601FA5C297B2E6F00A8E8B5 /* ConversationCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationCollectionViewController.swift; sourceTree = "<group>"; };
|
||||||
D601FA5E297B339100A8E8B5 /* ExpandThreadCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandThreadCollectionViewCell.swift; sourceTree = "<group>"; };
|
D601FA5E297B339100A8E8B5 /* ExpandThreadCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandThreadCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
@ -852,6 +858,7 @@
|
||||||
D627FF75217E923E00CC0648 /* DraftsManager.swift */,
|
D627FF75217E923E00CC0648 /* DraftsManager.swift */,
|
||||||
D61F75AE293AF50C00C0B37F /* EditedFilter.swift */,
|
D61F75AE293AF50C00C0B37F /* EditedFilter.swift */,
|
||||||
D65B4B532971F71D00DABDFB /* EditedReport.swift */,
|
D65B4B532971F71D00DABDFB /* EditedReport.swift */,
|
||||||
|
D600891A29848289005B4D00 /* PinnedTimeline.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -873,6 +880,7 @@
|
||||||
D61F75A4293ABD6F00C0B37F /* EditFilterView.swift */,
|
D61F75A4293ABD6F00C0B37F /* EditFilterView.swift */,
|
||||||
D68A76E729527884001DA1B3 /* PinnedTimelinesView.swift */,
|
D68A76E729527884001DA1B3 /* PinnedTimelinesView.swift */,
|
||||||
D68A76E9295285D0001DA1B3 /* AddHashtagPinnedTimelineView.swift */,
|
D68A76E9295285D0001DA1B3 /* AddHashtagPinnedTimelineView.swift */,
|
||||||
|
D600891E29848DE2005B4D00 /* AddInstancePinnedTimelineView.swift */,
|
||||||
);
|
);
|
||||||
path = "Customize Timelines";
|
path = "Customize Timelines";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1561,6 +1569,7 @@
|
||||||
D6114E1627F8BB210080E273 /* VersionTests.swift */,
|
D6114E1627F8BB210080E273 /* VersionTests.swift */,
|
||||||
D61F75A029396DE200C0B37F /* SemiCaseSensitiveComparatorTests.swift */,
|
D61F75A029396DE200C0B37F /* SemiCaseSensitiveComparatorTests.swift */,
|
||||||
D6CA8CD92962231F0050C433 /* ArrayUniqueTests.swift */,
|
D6CA8CD92962231F0050C433 /* ArrayUniqueTests.swift */,
|
||||||
|
D600891C298482F0005B4D00 /* PinnedTimelineTests.swift */,
|
||||||
D6D4DDE6212518A200E1C4BB /* Info.plist */,
|
D6D4DDE6212518A200E1C4BB /* Info.plist */,
|
||||||
);
|
);
|
||||||
path = TuskerTests;
|
path = TuskerTests;
|
||||||
|
@ -2031,6 +2040,7 @@
|
||||||
D65B4B6629773AE600DABDFB /* DeleteStatusService.swift in Sources */,
|
D65B4B6629773AE600DABDFB /* DeleteStatusService.swift in Sources */,
|
||||||
D61DC84628F498F200B82C6E /* Logging.swift in Sources */,
|
D61DC84628F498F200B82C6E /* Logging.swift in Sources */,
|
||||||
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */,
|
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */,
|
||||||
|
D600891B29848289005B4D00 /* PinnedTimeline.swift in Sources */,
|
||||||
D6A00B1D26379FC900316AD4 /* PollOptionsView.swift in Sources */,
|
D6A00B1D26379FC900316AD4 /* PollOptionsView.swift in Sources */,
|
||||||
D6DF95C12533F5DE0027A9B6 /* RelationshipMO.swift in Sources */,
|
D6DF95C12533F5DE0027A9B6 /* RelationshipMO.swift in Sources */,
|
||||||
D6ADB6EE28EA74E8009924AB /* UIView+Configure.swift in Sources */,
|
D6ADB6EE28EA74E8009924AB /* UIView+Configure.swift in Sources */,
|
||||||
|
@ -2038,6 +2048,7 @@
|
||||||
D61F75B1293BD85300C0B37F /* CreateFilterService.swift in Sources */,
|
D61F75B1293BD85300C0B37F /* CreateFilterService.swift in Sources */,
|
||||||
D65C6BF525478A9C00A6E89C /* BackgroundableViewController.swift in Sources */,
|
D65C6BF525478A9C00A6E89C /* BackgroundableViewController.swift in Sources */,
|
||||||
D61DC84D28F500D200B82C6E /* ProfileViewController.swift in Sources */,
|
D61DC84D28F500D200B82C6E /* ProfileViewController.swift in Sources */,
|
||||||
|
D600891F29848DE2005B4D00 /* AddInstancePinnedTimelineView.swift in Sources */,
|
||||||
D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */,
|
D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */,
|
||||||
D693A72A25CF8C1E003A14E2 /* ProfileDirectoryViewController.swift in Sources */,
|
D693A72A25CF8C1E003A14E2 /* ProfileDirectoryViewController.swift in Sources */,
|
||||||
D693A72F25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift in Sources */,
|
D693A72F25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift in Sources */,
|
||||||
|
@ -2228,6 +2239,7 @@
|
||||||
D61F75A129396DE200C0B37F /* SemiCaseSensitiveComparatorTests.swift in Sources */,
|
D61F75A129396DE200C0B37F /* SemiCaseSensitiveComparatorTests.swift in Sources */,
|
||||||
D62FF04823D7CDD700909D6E /* AttributedStringHelperTests.swift in Sources */,
|
D62FF04823D7CDD700909D6E /* AttributedStringHelperTests.swift in Sources */,
|
||||||
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */,
|
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */,
|
||||||
|
D600891D298482F0005B4D00 /* PinnedTimelineTests.swift in Sources */,
|
||||||
D6114E1727F8BB210080E273 /* VersionTests.swift in Sources */,
|
D6114E1727F8BB210080E273 /* VersionTests.swift in Sources */,
|
||||||
D6CA8CDA2962231F0050C433 /* ArrayUniqueTests.swift in Sources */,
|
D6CA8CDA2962231F0050C433 /* ArrayUniqueTests.swift in Sources */,
|
||||||
D6D4DDE5212518A200E1C4BB /* TuskerTests.swift in Sources */,
|
D6D4DDE5212518A200E1C4BB /* TuskerTests.swift in Sources */,
|
||||||
|
|
|
@ -25,7 +25,7 @@ public final class AccountPreferences: NSManagedObject {
|
||||||
@NSManaged var pinnedTimelinesData: Data?
|
@NSManaged var pinnedTimelinesData: Data?
|
||||||
|
|
||||||
@LazilyDecoding(from: \AccountPreferences.pinnedTimelinesData, fallback: [])
|
@LazilyDecoding(from: \AccountPreferences.pinnedTimelinesData, fallback: [])
|
||||||
var pinnedTimelines: [Timeline]
|
var pinnedTimelines: [PinnedTimeline]
|
||||||
|
|
||||||
static func `default`(account: LocalData.UserAccountInfo, context: NSManagedObjectContext) -> AccountPreferences {
|
static func `default`(account: LocalData.UserAccountInfo, context: NSManagedObjectContext) -> AccountPreferences {
|
||||||
let prefs = AccountPreferences(context: context)
|
let prefs = AccountPreferences(context: context)
|
||||||
|
|
|
@ -25,23 +25,4 @@ extension Timeline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var image: UIImage {
|
|
||||||
switch self {
|
|
||||||
case .home:
|
|
||||||
return UIImage(systemName: "house.fill")!
|
|
||||||
case let .public(local):
|
|
||||||
if local {
|
|
||||||
return UIImage(systemName: "person.and.person.fill")!
|
|
||||||
} else {
|
|
||||||
return UIImage(systemName: "globe")!
|
|
||||||
}
|
|
||||||
case .list(id: _):
|
|
||||||
return UIImage(systemName: "list.bullet")!
|
|
||||||
case .tag(hashtag: _):
|
|
||||||
return UIImage(systemName: "number")!
|
|
||||||
case .direct:
|
|
||||||
return UIImage(systemName: "enveloep.fill")!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
//
|
||||||
|
// PinnedTimeline.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 1/27/23.
|
||||||
|
// Copyright © 2023 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
enum PinnedTimeline: Codable, Equatable, Hashable {
|
||||||
|
case home
|
||||||
|
case `public`(local: Bool)
|
||||||
|
case tag(hashtag: String)
|
||||||
|
case list(id: String)
|
||||||
|
case instance(URL)
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
let type = try container.decode(String.self, forKey: .type)
|
||||||
|
switch type {
|
||||||
|
case "home":
|
||||||
|
self = .home
|
||||||
|
case "public":
|
||||||
|
self = .public(local: try container.decode(Bool.self, forKey: .local))
|
||||||
|
case "tag":
|
||||||
|
self = .tag(hashtag: try container.decode(String.self, forKey: .hashtag))
|
||||||
|
case "list":
|
||||||
|
self = .list(id: try container.decode(String.self, forKey: .listID))
|
||||||
|
case "instance":
|
||||||
|
self = .instance(try container.decode(URL.self, forKey: .instanceURL))
|
||||||
|
default:
|
||||||
|
throw DecodingError.dataCorruptedError(forKey: CodingKeys.type, in: container, debugDescription: "PinnedTimeline type must be one of 'home', 'local', 'tag', 'list', or 'instance'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
switch self {
|
||||||
|
case .home:
|
||||||
|
try container.encode("home", forKey: .type)
|
||||||
|
case .public(let local):
|
||||||
|
try container.encode("public", forKey: .type)
|
||||||
|
try container.encode(local, forKey: .local)
|
||||||
|
case .tag(let hashtag):
|
||||||
|
try container.encode("tag", forKey: .type)
|
||||||
|
try container.encode(hashtag, forKey: .hashtag)
|
||||||
|
case .list(let id):
|
||||||
|
try container.encode("list", forKey: .type)
|
||||||
|
try container.encode(id, forKey: .listID)
|
||||||
|
case .instance(let url):
|
||||||
|
try container.encode("instance", forKey: .type)
|
||||||
|
try container.encode(url, forKey: .instanceURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init?(timeline: Timeline) {
|
||||||
|
switch timeline {
|
||||||
|
case .home:
|
||||||
|
self = .home
|
||||||
|
case .public(let local):
|
||||||
|
self = .public(local: local)
|
||||||
|
case .tag(let hashtag):
|
||||||
|
self = .tag(hashtag: hashtag)
|
||||||
|
case .list(let id):
|
||||||
|
self = .list(id: id)
|
||||||
|
case .direct:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeline: Timeline? {
|
||||||
|
switch self {
|
||||||
|
case .home:
|
||||||
|
return .home
|
||||||
|
case .public(let local):
|
||||||
|
return .public(local: local)
|
||||||
|
case .tag(let hashtag):
|
||||||
|
return .tag(hashtag: hashtag)
|
||||||
|
case .list(let id):
|
||||||
|
return .list(id: id)
|
||||||
|
case .instance(_):
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .home:
|
||||||
|
return "Home"
|
||||||
|
case let .public(local):
|
||||||
|
return local ? "Local" : "Federated"
|
||||||
|
case let .tag(hashtag):
|
||||||
|
return "#\(hashtag)"
|
||||||
|
case .list:
|
||||||
|
return "List"
|
||||||
|
case .instance(let url):
|
||||||
|
return url.host!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var image: UIImage {
|
||||||
|
switch self {
|
||||||
|
case .home:
|
||||||
|
return UIImage(systemName: "house.fill")!
|
||||||
|
case let .public(local):
|
||||||
|
if local {
|
||||||
|
return UIImage(systemName: "person.and.person.fill")!
|
||||||
|
} else {
|
||||||
|
return UIImage(systemName: "globe")!
|
||||||
|
}
|
||||||
|
case .list(id: _):
|
||||||
|
return UIImage(systemName: "list.bullet")!
|
||||||
|
case .tag(hashtag: _):
|
||||||
|
return UIImage(systemName: "number")!
|
||||||
|
case .instance(_):
|
||||||
|
return UIImage(systemName: "globe")!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case type
|
||||||
|
case local
|
||||||
|
case hashtag
|
||||||
|
case listID
|
||||||
|
case instanceURL
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ struct AddHashtagPinnedTimelineView: View {
|
||||||
@EnvironmentObject private var mastodonController: MastodonController
|
@EnvironmentObject private var mastodonController: MastodonController
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@Binding var pinnedTimelines: [Timeline]
|
@Binding var pinnedTimelines: [PinnedTimeline]
|
||||||
@StateObject private var viewModel = SearchViewModel()
|
@StateObject private var viewModel = SearchViewModel()
|
||||||
@State private var searchTask: Task<Void, Never>?
|
@State private var searchTask: Task<Void, Never>?
|
||||||
@State private var isSearching = false
|
@State private var isSearching = false
|
||||||
|
@ -34,7 +34,7 @@ struct AddHashtagPinnedTimelineView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
list
|
list
|
||||||
.navigationTitle("Search")
|
.navigationTitle("Add Hashtag")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.searchable(text: $viewModel.searchQuery, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Search for hashtags"))
|
.searchable(text: $viewModel.searchQuery, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Search for hashtags"))
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// AddInstancePinnedTimelineView.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 1/27/23.
|
||||||
|
// Copyright © 2023 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
struct AddInstancePinnedTimelineView: UIViewControllerRepresentable {
|
||||||
|
typealias UIViewControllerType = UINavigationController
|
||||||
|
|
||||||
|
@Binding var pinnedTimelines: [PinnedTimeline]
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
func makeUIViewController(context: Context) -> UINavigationController {
|
||||||
|
let vc = InstanceSelectorTableViewController()
|
||||||
|
vc.title = "Add Instance"
|
||||||
|
vc.delegate = context.coordinator
|
||||||
|
vc.navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .cancel, primaryAction: UIAction(handler: { _ in
|
||||||
|
dismiss()
|
||||||
|
}))
|
||||||
|
return UINavigationController(rootViewController: vc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
let coordinator = Coordinator()
|
||||||
|
coordinator.didSelect = {
|
||||||
|
pinnedTimelines.append(.instance($0))
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
return coordinator
|
||||||
|
}
|
||||||
|
|
||||||
|
class Coordinator: InstanceSelectorTableViewControllerDelegate {
|
||||||
|
var didSelect: ((URL) -> Void)?
|
||||||
|
|
||||||
|
func didSelectInstance(url: URL) {
|
||||||
|
didSelect?(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,8 +14,9 @@ struct PinnedTimelinesView: View {
|
||||||
@ObservedObject private var accountPreferences: AccountPreferences
|
@ObservedObject private var accountPreferences: AccountPreferences
|
||||||
|
|
||||||
@State private var isShowingAddHashtagSheet = false
|
@State private var isShowingAddHashtagSheet = false
|
||||||
|
@State private var isShowingAddInstanceSheet = false
|
||||||
// store this separately from AccountPreferences in the view, b/c the @LazilyDecoding wrapper breaks animations
|
// store this separately from AccountPreferences in the view, b/c the @LazilyDecoding wrapper breaks animations
|
||||||
@State private var pinnedTimelines: [Timeline]
|
@State private var pinnedTimelines: [PinnedTimeline]
|
||||||
|
|
||||||
init(accountPreferences: AccountPreferences) {
|
init(accountPreferences: AccountPreferences) {
|
||||||
self.accountPreferences = accountPreferences
|
self.accountPreferences = accountPreferences
|
||||||
|
@ -61,7 +62,7 @@ struct PinnedTimelinesView: View {
|
||||||
})
|
})
|
||||||
|
|
||||||
Menu {
|
Menu {
|
||||||
ForEach([Timeline.home, .public(local: true), .public(local: false)], id: \.id) { timeline in
|
ForEach([PinnedTimeline.home, .public(local: true), .public(local: false)], id: \.id) { timeline in
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
pinnedTimelines.append(timeline)
|
pinnedTimelines.append(timeline)
|
||||||
|
@ -80,12 +81,12 @@ struct PinnedTimelinesView: View {
|
||||||
ForEach(mastodonController.lists, id: \.id) { list in
|
ForEach(mastodonController.lists, id: \.id) { list in
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
pinnedTimelines.append(list.timeline)
|
pinnedTimelines.append(.list(id: list.id))
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Text(list.title)
|
Text(list.title)
|
||||||
}
|
}
|
||||||
.disabled(pinnedTimelines.contains(list.timeline))
|
.disabled(pinnedTimelines.contains(.list(id: list.id)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +95,12 @@ struct PinnedTimelinesView: View {
|
||||||
} label: {
|
} label: {
|
||||||
Label("Hashtag…", systemImage: "number")
|
Label("Hashtag…", systemImage: "number")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
isShowingAddInstanceSheet = true
|
||||||
|
} label: {
|
||||||
|
Label("Instance…", systemImage: "globe")
|
||||||
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("Add…", systemImage: "plus")
|
Label("Add…", systemImage: "plus")
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
|
@ -106,6 +113,10 @@ struct PinnedTimelinesView: View {
|
||||||
.sheet(isPresented: $isShowingAddHashtagSheet, content: {
|
.sheet(isPresented: $isShowingAddHashtagSheet, content: {
|
||||||
AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines)
|
AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines)
|
||||||
})
|
})
|
||||||
|
.sheet(isPresented: $isShowingAddInstanceSheet, content: {
|
||||||
|
AddInstancePinnedTimelineView(pinnedTimelines: $pinnedTimelines)
|
||||||
|
.edgesIgnoringSafeArea(.bottom)
|
||||||
|
})
|
||||||
.onReceive(accountPreferences.publisher(for: \.pinnedTimelinesData)) { _ in
|
.onReceive(accountPreferences.publisher(for: \.pinnedTimelinesData)) { _ in
|
||||||
if pinnedTimelines != accountPreferences.pinnedTimelines {
|
if pinnedTimelines != accountPreferences.pinnedTimelines {
|
||||||
pinnedTimelines = accountPreferences.pinnedTimelines
|
pinnedTimelines = accountPreferences.pinnedTimelines
|
||||||
|
@ -119,7 +130,7 @@ struct PinnedTimelinesView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate extension Timeline {
|
fileprivate extension PinnedTimeline {
|
||||||
var id: String {
|
var id: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .home:
|
case .home:
|
||||||
|
@ -130,8 +141,8 @@ fileprivate extension Timeline {
|
||||||
return "list:\(id)"
|
return "list:\(id)"
|
||||||
case .tag(hashtag: let tag):
|
case .tag(hashtag: let tag):
|
||||||
return "tag:\(tag)"
|
return "tag:\(tag)"
|
||||||
case .direct:
|
case .instance(let url):
|
||||||
return "direct"
|
return "instance:\(url.host!)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,12 @@ class TimelinesPageViewController: SegmentedPageViewController<TimelinesPageView
|
||||||
Page(mastodonController: mastodonController, timeline: $0)
|
Page(mastodonController: mastodonController, timeline: $0)
|
||||||
}
|
}
|
||||||
super.init(pages: pages) { page in
|
super.init(pages: pages) { page in
|
||||||
let vc = TimelineViewController(for: page.timeline, mastodonController: page.mastodonController)
|
let vc: TimelineViewController
|
||||||
|
if case .instance(let url) = page.timeline {
|
||||||
|
vc = InstanceTimelineViewController(for: url, parentMastodonController: mastodonController)
|
||||||
|
} else {
|
||||||
|
vc = TimelineViewController(for: page.timeline.timeline!, mastodonController: mastodonController)
|
||||||
|
}
|
||||||
vc.title = page.segmentedControlTitle
|
vc.title = page.segmentedControlTitle
|
||||||
vc.persistsState = true
|
vc.persistsState = true
|
||||||
return vc
|
return vc
|
||||||
|
@ -82,7 +87,7 @@ class TimelinesPageViewController: SegmentedPageViewController<TimelinesPageView
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func selectTimeline(_ timeline: Timeline, animated: Bool) {
|
func selectTimeline(_ timeline: PinnedTimeline, animated: Bool) {
|
||||||
self.selectPage(Page(mastodonController: mastodonController, timeline: timeline), animated: animated)
|
self.selectPage(Page(mastodonController: mastodonController, timeline: timeline), animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,10 +96,11 @@ class TimelinesPageViewController: SegmentedPageViewController<TimelinesPageView
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreActivity(_ activity: NSUserActivity) {
|
func restoreActivity(_ activity: NSUserActivity) {
|
||||||
guard let timeline = UserActivityManager.getTimeline(from: activity) else {
|
guard let timeline = UserActivityManager.getTimeline(from: activity),
|
||||||
|
let pinned = PinnedTimeline(timeline: timeline) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let page = Page(mastodonController: mastodonController, timeline: timeline)
|
let page = Page(mastodonController: mastodonController, timeline: pinned)
|
||||||
// the pinned timelines may have changed after an iCloud sync, in which case don't restore anything
|
// the pinned timelines may have changed after an iCloud sync, in which case don't restore anything
|
||||||
if pages.contains(page) {
|
if pages.contains(page) {
|
||||||
selectPage(page, animated: false)
|
selectPage(page, animated: false)
|
||||||
|
@ -110,7 +116,7 @@ class TimelinesPageViewController: SegmentedPageViewController<TimelinesPageView
|
||||||
extension TimelinesPageViewController {
|
extension TimelinesPageViewController {
|
||||||
struct Page: SegmentedPageViewControllerPage {
|
struct Page: SegmentedPageViewControllerPage {
|
||||||
let mastodonController: MastodonController
|
let mastodonController: MastodonController
|
||||||
let timeline: Timeline
|
let timeline: PinnedTimeline
|
||||||
|
|
||||||
static func ==(lhs: Page, rhs: Page) -> Bool {
|
static func ==(lhs: Page, rhs: Page) -> Bool {
|
||||||
return lhs.timeline == rhs.timeline
|
return lhs.timeline == rhs.timeline
|
||||||
|
|
|
@ -216,10 +216,11 @@ class UserActivityManager {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if mastodonController.accountPreferences.pinnedTimelines.contains(timeline) {
|
if let pinned = PinnedTimeline(timeline: timeline),
|
||||||
|
mastodonController.accountPreferences.pinnedTimelines.contains(pinned) {
|
||||||
navigationController.popToRootViewController(animated: false)
|
navigationController.popToRootViewController(animated: false)
|
||||||
let rootController = navigationController.viewControllers.first! as! TimelinesPageViewController
|
let rootController = navigationController.viewControllers.first! as! TimelinesPageViewController
|
||||||
rootController.selectTimeline(timeline, animated: false)
|
rootController.selectTimeline(pinned, animated: false)
|
||||||
} else {
|
} else {
|
||||||
let timeline = TimelineViewController(for: timeline, mastodonController: mastodonController)
|
let timeline = TimelineViewController(for: timeline, mastodonController: mastodonController)
|
||||||
navigationController.pushViewController(timeline, animated: false)
|
navigationController.pushViewController(timeline, animated: false)
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// PinnedTimelineTests.swift
|
||||||
|
// TuskerTests
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 1/27/23.
|
||||||
|
// Copyright © 2023 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import Tusker
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
final class PinnedTimelineTests: XCTestCase {
|
||||||
|
|
||||||
|
func testDecodeFromTimeline() throws {
|
||||||
|
let timeline = Timeline.public(local: false)
|
||||||
|
let data = try JSONEncoder().encode(timeline)
|
||||||
|
let decoded = try JSONDecoder().decode(PinnedTimeline.self, from: data)
|
||||||
|
switch decoded {
|
||||||
|
case .public(local: false):
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
XCTFail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue