parent
6831ab5385
commit
ae6a0513e4
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public class Hashtag: Decodable {
|
public class Hashtag: Codable {
|
||||||
public let name: String
|
public let name: String
|
||||||
public let url: URL
|
public let url: URL
|
||||||
public let history: [History]?
|
public let history: [History]?
|
||||||
|
@ -27,7 +27,7 @@ public class Hashtag: Decodable {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Hashtag {
|
extension Hashtag {
|
||||||
public class History: Decodable {
|
public class History: Codable {
|
||||||
public let day: Date
|
public let day: Date
|
||||||
public let uses: Int
|
public let uses: Int
|
||||||
public let accounts: Int
|
public let accounts: Int
|
||||||
|
@ -42,7 +42,7 @@ extension Hashtag {
|
||||||
|
|
||||||
extension Hashtag: Equatable, Hashable {
|
extension Hashtag: Equatable, Hashable {
|
||||||
public static func ==(lhs: Hashtag, rhs: Hashtag) -> Bool {
|
public static func ==(lhs: Hashtag, rhs: Hashtag) -> Bool {
|
||||||
return lhs.url == rhs.url
|
return lhs.name == rhs.name
|
||||||
}
|
}
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
|
|
@ -164,6 +164,9 @@
|
||||||
D68632AB21ED8319008C716E /* GMImagePickerController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686329121ED8319008C716E /* GMImagePickerController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
D68632AB21ED8319008C716E /* GMImagePickerController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686329121ED8319008C716E /* GMImagePickerController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
D68632AC21ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686329321ED8319008C716E /* GMImagePicker.strings */; };
|
D68632AC21ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686329321ED8319008C716E /* GMImagePicker.strings */; };
|
||||||
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; };
|
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; };
|
||||||
|
D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */; };
|
||||||
|
D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */; };
|
||||||
|
D6945C3423AC6431005C403C /* AddSavedHashtagViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */; };
|
||||||
D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */; };
|
D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */; };
|
||||||
D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */; };
|
D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */; };
|
||||||
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */; };
|
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */; };
|
||||||
|
@ -435,6 +438,9 @@
|
||||||
D686329121ED8319008C716E /* GMImagePickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMImagePickerController.h; sourceTree = "<group>"; };
|
D686329121ED8319008C716E /* GMImagePickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMImagePickerController.h; sourceTree = "<group>"; };
|
||||||
D686329421ED8319008C716E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
D686329421ED8319008C716E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
||||||
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = "<group>"; };
|
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedHashtagsManager.swift; sourceTree = "<group>"; };
|
||||||
|
D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSavedHashtagViewController.swift; sourceTree = "<group>"; };
|
||||||
D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSegment.swift; sourceTree = "<group>"; };
|
D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSegment.swift; sourceTree = "<group>"; };
|
||||||
D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationGroup.swift; sourceTree = "<group>"; };
|
D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationGroup.swift; sourceTree = "<group>"; };
|
||||||
D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionNotificationGroupTableViewCell.swift; sourceTree = "<group>"; };
|
D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionNotificationGroupTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
@ -731,6 +737,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D627943D23A564D400D38C68 /* ExploreViewController.swift */,
|
D627943D23A564D400D38C68 /* ExploreViewController.swift */,
|
||||||
|
D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */,
|
||||||
);
|
);
|
||||||
path = Explore;
|
path = Explore;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -781,6 +788,7 @@
|
||||||
D641C782213DD7F0004B4513 /* Main */,
|
D641C782213DD7F0004B4513 /* Main */,
|
||||||
D641C783213DD7FE004B4513 /* Onboarding */,
|
D641C783213DD7FE004B4513 /* Onboarding */,
|
||||||
D641C781213DD7DD004B4513 /* Timeline */,
|
D641C781213DD7DD004B4513 /* Timeline */,
|
||||||
|
D6945C3023AC4D21005C403C /* Hashtag Timeline */,
|
||||||
D641C784213DD819004B4513 /* Profile */,
|
D641C784213DD819004B4513 /* Profile */,
|
||||||
D641C785213DD83B004B4513 /* Conversation */,
|
D641C785213DD83B004B4513 /* Conversation */,
|
||||||
D641C786213DD852004B4513 /* Notifications */,
|
D641C786213DD852004B4513 /* Notifications */,
|
||||||
|
@ -1057,6 +1065,14 @@
|
||||||
path = de.lproj;
|
path = de.lproj;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D6945C3023AC4D21005C403C /* Hashtag Timeline */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */,
|
||||||
|
);
|
||||||
|
path = "Hashtag Timeline";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D6A3BC7223218C6E00FD64D5 /* Utilities */ = {
|
D6A3BC7223218C6E00FD64D5 /* Utilities */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1209,6 +1225,7 @@
|
||||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
||||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
||||||
D627FF75217E923E00CC0648 /* DraftsManager.swift */,
|
D627FF75217E923E00CC0648 /* DraftsManager.swift */,
|
||||||
|
D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */,
|
||||||
D6028B9A2150811100F223B9 /* MastodonCache.swift */,
|
D6028B9A2150811100F223B9 /* MastodonCache.swift */,
|
||||||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */,
|
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */,
|
||||||
D6F1F84E2193B9BE00F5FE67 /* Caching */,
|
D6F1F84E2193B9BE00F5FE67 /* Caching */,
|
||||||
|
@ -1641,6 +1658,7 @@
|
||||||
0411610022B442870030A9B7 /* AttachmentViewController.swift in Sources */,
|
0411610022B442870030A9B7 /* AttachmentViewController.swift in Sources */,
|
||||||
D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */,
|
D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */,
|
||||||
D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */,
|
D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */,
|
||||||
|
D6945C3423AC6431005C403C /* AddSavedHashtagViewController.swift in Sources */,
|
||||||
D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */,
|
D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */,
|
||||||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
||||||
D66362712136338600C9CBA2 /* ComposeViewController.swift in Sources */,
|
D66362712136338600C9CBA2 /* ComposeViewController.swift in Sources */,
|
||||||
|
@ -1680,6 +1698,7 @@
|
||||||
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
|
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
|
||||||
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */,
|
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */,
|
||||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
||||||
|
D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */,
|
||||||
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
||||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
||||||
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
||||||
|
@ -1703,6 +1722,7 @@
|
||||||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
||||||
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
|
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
|
||||||
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */,
|
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */,
|
||||||
|
D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */,
|
||||||
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */,
|
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */,
|
||||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */,
|
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */,
|
||||||
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */,
|
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
//
|
||||||
|
// SavedHashtagsManager.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 12/19/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class SavedHashtagsManager: Codable {
|
||||||
|
private(set) static var shared: SavedHashtagsManager = load()
|
||||||
|
|
||||||
|
private static var documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
|
private static var archiveURL = SavedHashtagsManager.documentsDirectory.appendingPathComponent("saved_hashtags").appendingPathExtension("plist")
|
||||||
|
|
||||||
|
static func save() {
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
let encoder = PropertyListEncoder()
|
||||||
|
let data = try? encoder.encode(shared)
|
||||||
|
try? data?.write(to: archiveURL, options: .noFileProtection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func load() -> SavedHashtagsManager {
|
||||||
|
let decoder = PropertyListDecoder()
|
||||||
|
if let data = try? Data(contentsOf: archiveURL),
|
||||||
|
let savedHashtagsManager = try? decoder.decode(Self.self, from: data) {
|
||||||
|
return savedHashtagsManager
|
||||||
|
}
|
||||||
|
return SavedHashtagsManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
private var savedHashtags: [Hashtag] = []
|
||||||
|
var sorted: [Hashtag] {
|
||||||
|
return savedHashtags.sorted(by: { $0.name > $1.name })
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSaved(_ hashtag: Hashtag) -> Bool {
|
||||||
|
return savedHashtags.contains(hashtag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(_ hashtag: Hashtag) {
|
||||||
|
if isSaved(hashtag) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
savedHashtags.append(hashtag)
|
||||||
|
SavedHashtagsManager.save()
|
||||||
|
NotificationCenter.default.post(name: .savedHashtagsChanged, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(_ hashtag: Hashtag) {
|
||||||
|
guard isSaved(hashtag) else { return }
|
||||||
|
savedHashtags.removeAll(where: { $0.name == hashtag.name })
|
||||||
|
SavedHashtagsManager.save()
|
||||||
|
NotificationCenter.default.post(name: .savedHashtagsChanged, object: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Foundation.Notification.Name {
|
||||||
|
static let savedHashtagsChanged = Notification.Name("savedHashtagsChanged")
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
//
|
||||||
|
// AddSavedHashtagViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 12/19/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class AddSavedHashtagViewController: SearchResultsViewController {
|
||||||
|
|
||||||
|
var searchController: UISearchController!
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
delegate = self
|
||||||
|
onlySections = [.hashtags]
|
||||||
|
|
||||||
|
searchController = UISearchController(searchResultsController: nil)
|
||||||
|
searchController.obscuresBackgroundDuringPresentation = false
|
||||||
|
searchController.hidesNavigationBarDuringPresentation = false
|
||||||
|
searchController.searchBar.autocapitalizationType = .none
|
||||||
|
searchController.searchBar.placeholder = NSLocalizedString("Search for hashtags to save", comment: "add saved hashtag search field placeholder")
|
||||||
|
searchController.searchBar.delegate = self
|
||||||
|
|
||||||
|
definesPresentationContext = true
|
||||||
|
|
||||||
|
navigationItem.searchController = searchController
|
||||||
|
navigationItem.hidesSearchBarWhenScrolling = false
|
||||||
|
|
||||||
|
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed))
|
||||||
|
}
|
||||||
|
|
||||||
|
override func performSearch(query: String?) {
|
||||||
|
if let query = query, !query.starts(with: "#") {
|
||||||
|
super.performSearch(query: "#\(query)")
|
||||||
|
} else {
|
||||||
|
super.performSearch(query: query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Interaction
|
||||||
|
|
||||||
|
@objc func cancelButtonPressed() {
|
||||||
|
dismiss(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AddSavedHashtagViewController: SearchResultsViewControllerDelegate {
|
||||||
|
func selectedSearchResult(hashtag: Hashtag) {
|
||||||
|
SavedHashtagsManager.shared.add(hashtag)
|
||||||
|
dismiss(animated: true)
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,7 +43,7 @@ class ExploreViewController: EnhancedTableViewController {
|
||||||
cell.accessoryType = .disclosureIndicator
|
cell.accessoryType = .disclosureIndicator
|
||||||
|
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
cell.imageView!.image = nil
|
cell.imageView!.image = UIImage(systemName: "list.bullet")
|
||||||
cell.textLabel!.text = list.title
|
cell.textLabel!.text = list.title
|
||||||
cell.accessoryType = .disclosureIndicator
|
cell.accessoryType = .disclosureIndicator
|
||||||
|
|
||||||
|
@ -51,6 +51,16 @@ class ExploreViewController: EnhancedTableViewController {
|
||||||
cell.imageView!.image = UIImage(systemName: "plus")
|
cell.imageView!.image = UIImage(systemName: "plus")
|
||||||
cell.textLabel!.text = NSLocalizedString("New List...", comment: "new list nav item title")
|
cell.textLabel!.text = NSLocalizedString("New List...", comment: "new list nav item title")
|
||||||
cell.accessoryType = .none
|
cell.accessoryType = .none
|
||||||
|
|
||||||
|
case let .savedHashtag(hashtag):
|
||||||
|
cell.imageView!.image = UIImage(systemName: "number")
|
||||||
|
cell.textLabel!.text = hashtag.name
|
||||||
|
cell.accessoryType = .disclosureIndicator
|
||||||
|
|
||||||
|
case .addSavedHashtag:
|
||||||
|
cell.imageView!.image = UIImage(systemName: "plus")
|
||||||
|
cell.textLabel!.text = NSLocalizedString("Save Hashtag...", comment: "save hashtag nav item title")
|
||||||
|
cell.accessoryType = .none
|
||||||
}
|
}
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
|
@ -58,9 +68,10 @@ class ExploreViewController: EnhancedTableViewController {
|
||||||
dataSource.exploreController = self
|
dataSource.exploreController = self
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
snapshot.appendSections([.bookmarks, .lists])
|
snapshot.appendSections([.bookmarks, .lists, .savedHashtags])
|
||||||
snapshot.appendItems([.bookmarks], toSection: .bookmarks)
|
snapshot.appendItems([.bookmarks], toSection: .bookmarks)
|
||||||
snapshot.appendItems([.addList], toSection: .lists)
|
snapshot.appendItems([.addList], toSection: .lists)
|
||||||
|
snapshot.appendItems(SavedHashtagsManager.shared.sorted.map { .savedHashtag($0) } + [.addSavedHashtag], toSection: .savedHashtags)
|
||||||
// the initial, static items should not be displayed with an animation
|
// the initial, static items should not be displayed with an animation
|
||||||
UIView.performWithoutAnimation {
|
UIView.performWithoutAnimation {
|
||||||
dataSource.apply(snapshot)
|
dataSource.apply(snapshot)
|
||||||
|
@ -77,6 +88,8 @@ class ExploreViewController: EnhancedTableViewController {
|
||||||
navigationItem.searchController = searchController
|
navigationItem.searchController = searchController
|
||||||
navigationItem.hidesSearchBarWhenScrolling = false
|
navigationItem.hidesSearchBarWhenScrolling = false
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(savedHashtagsChanged), name: .savedHashtagsChanged, object: nil)
|
||||||
|
|
||||||
reloadLists()
|
reloadLists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,8 +101,7 @@ class ExploreViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
var snapshot = self.dataSource.snapshot()
|
var snapshot = self.dataSource.snapshot()
|
||||||
snapshot.deleteSections([.lists])
|
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .lists))
|
||||||
snapshot.appendSections([.lists])
|
|
||||||
snapshot.appendItems(lists.map { .list($0) } + [.addList], toSection: .lists)
|
snapshot.appendItems(lists.map { .list($0) } + [.addList], toSection: .lists)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
@ -98,6 +110,39 @@ class ExploreViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func savedHashtagsChanged() {
|
||||||
|
var snapshot = dataSource.snapshot()
|
||||||
|
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .savedHashtags))
|
||||||
|
snapshot.appendItems(SavedHashtagsManager.shared.sorted.map { .savedHashtag($0) } + [.addSavedHashtag], toSection: .savedHashtags)
|
||||||
|
dataSource.apply(snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteList(_ list: List) {
|
||||||
|
let title = String(format: NSLocalizedString("Are you sure want to delete the '%@' list?", comment: "delete list alert title"), list.title)
|
||||||
|
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "delete list alert cancel button"), style: .cancel, handler: nil))
|
||||||
|
alert.addAction(UIAlertAction(title: NSLocalizedString("Delete List", comment: "delete list alert confirm button"), style: .destructive, handler: { (_) in
|
||||||
|
|
||||||
|
let request = List.delete(list)
|
||||||
|
MastodonController.client.run(request) { (response) in
|
||||||
|
guard case .success(_, _) = response else {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
var snapshot = self.dataSource.snapshot()
|
||||||
|
snapshot.deleteItems([.list(list)])
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.dataSource.apply(snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
present(alert, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeSavedHashtag(_ hashtag: Hashtag) {
|
||||||
|
SavedHashtagsManager.shared.remove(hashtag)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Table view delegate
|
// MARK: - Table view delegate
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
@ -133,6 +178,14 @@ class ExploreViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
present(alert, animated: true)
|
present(alert, animated: true)
|
||||||
|
|
||||||
|
case let .savedHashtag(hashtag):
|
||||||
|
show(HashtagTimelineViewController(for: hashtag), sender: nil)
|
||||||
|
|
||||||
|
case .addSavedHashtag:
|
||||||
|
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
|
||||||
|
let navController = UINavigationController(rootViewController: AddSavedHashtagViewController())
|
||||||
|
present(navController, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,11 +199,14 @@ extension ExploreViewController {
|
||||||
enum Section: CaseIterable {
|
enum Section: CaseIterable {
|
||||||
case bookmarks
|
case bookmarks
|
||||||
case lists
|
case lists
|
||||||
|
case savedHashtags
|
||||||
}
|
}
|
||||||
enum Item: Hashable {
|
enum Item: Hashable {
|
||||||
case bookmarks
|
case bookmarks
|
||||||
case list(List)
|
case list(List)
|
||||||
case addList
|
case addList
|
||||||
|
case savedHashtag(Hashtag)
|
||||||
|
case addSavedHashtag
|
||||||
|
|
||||||
static func == (lhs: ExploreViewController.Item, rhs: ExploreViewController.Item) -> Bool {
|
static func == (lhs: ExploreViewController.Item, rhs: ExploreViewController.Item) -> Bool {
|
||||||
switch (lhs, rhs) {
|
switch (lhs, rhs) {
|
||||||
|
@ -160,6 +216,10 @@ extension ExploreViewController {
|
||||||
return a.id == b.id
|
return a.id == b.id
|
||||||
case (.addList, .addList):
|
case (.addList, .addList):
|
||||||
return true
|
return true
|
||||||
|
case let (.savedHashtag(a), .savedHashtag(b)):
|
||||||
|
return a == b
|
||||||
|
case (.addSavedHashtag, .addSavedHashtag):
|
||||||
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -173,6 +233,11 @@ extension ExploreViewController {
|
||||||
hasher.combine(list.id)
|
hasher.combine(list.id)
|
||||||
case .addList:
|
case .addList:
|
||||||
hasher.combine("addList")
|
hasher.combine("addList")
|
||||||
|
case let .savedHashtag(hashtag):
|
||||||
|
hasher.combine("savedHashtag")
|
||||||
|
hasher.combine(hashtag.name)
|
||||||
|
case .addSavedHashtag:
|
||||||
|
hasher.combine("addSavedHashtag")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,43 +250,39 @@ extension ExploreViewController {
|
||||||
switch section {
|
switch section {
|
||||||
case 1:
|
case 1:
|
||||||
return NSLocalizedString("Lists", comment: "explore lists section title")
|
return NSLocalizedString("Lists", comment: "explore lists section title")
|
||||||
|
case 2:
|
||||||
|
return NSLocalizedString("Saved Hashtags", comment: "explore saved hashtags section title")
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||||
if case .list(_) = itemIdentifier(for: indexPath) {
|
switch itemIdentifier(for: indexPath) {
|
||||||
|
case .list(_):
|
||||||
return true
|
return true
|
||||||
}
|
case .savedHashtag(_):
|
||||||
|
return true
|
||||||
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||||
guard editingStyle == .delete,
|
guard editingStyle == .delete,
|
||||||
case let .list(list) = itemIdentifier(for: indexPath) else {
|
let exploreController = exploreController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = String(format: NSLocalizedString("Are you sure want to delete the '%@' list?", comment: "delete list alert title"), list.title)
|
switch itemIdentifier(for: indexPath) {
|
||||||
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
|
case let .list(list):
|
||||||
alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "delete list alert cancel button"), style: .cancel, handler: nil))
|
exploreController.deleteList(list)
|
||||||
alert.addAction(UIAlertAction(title: NSLocalizedString("Delete List", comment: "delete list alert confirm button"), style: .destructive, handler: { (_) in
|
case let .savedHashtag(hashtag):
|
||||||
|
exploreController.removeSavedHashtag(hashtag)
|
||||||
let request = List.delete(list)
|
default:
|
||||||
MastodonController.client.run(request) { (response) in
|
return
|
||||||
guard case .success(_, _) = response else {
|
|
||||||
fatalError()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var snapshot = self.snapshot()
|
|
||||||
snapshot.deleteItems([.list(list)])
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.apply(snapshot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
self.exploreController?.present(alert, animated: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
//
|
||||||
|
// HashtagTimelineViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 12/19/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class HashtagTimelineViewController: TimelineTableViewController {
|
||||||
|
|
||||||
|
let hashtag: Hashtag
|
||||||
|
|
||||||
|
var toggleSaveButton: UIBarButtonItem!
|
||||||
|
var toggleSaveButtonTitle: String {
|
||||||
|
if SavedHashtagsManager.shared.isSaved(hashtag) {
|
||||||
|
return NSLocalizedString("Unsave", comment: "unsave hashtag button")
|
||||||
|
} else {
|
||||||
|
return NSLocalizedString("Save", comment: "save hashtag button")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(for hashtag: Hashtag) {
|
||||||
|
self.hashtag = hashtag
|
||||||
|
|
||||||
|
super.init(for: .tag(hashtag: hashtag.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
toggleSaveButton = UIBarButtonItem(title: toggleSaveButtonTitle, style: .plain, target: self, action: #selector(toggleSaveButtonPressed))
|
||||||
|
navigationItem.rightBarButtonItem = toggleSaveButton
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(savedHashtagsChanged), name: .savedHashtagsChanged, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func savedHashtagsChanged() {
|
||||||
|
toggleSaveButton.title = toggleSaveButtonTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Interaction
|
||||||
|
|
||||||
|
@objc func toggleSaveButtonPressed() {
|
||||||
|
if SavedHashtagsManager.shared.isSaved(hashtag) {
|
||||||
|
SavedHashtagsManager.shared.remove(hashtag)
|
||||||
|
} else {
|
||||||
|
SavedHashtagsManager.shared.add(hashtag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -76,7 +76,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func selected(tag: Hashtag) {
|
func selected(tag: Hashtag) {
|
||||||
show(TimelineTableViewController(for: .tag(hashtag: tag.name)), sender: self)
|
show(HashtagTimelineViewController(for: tag), sender: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func selected(url: URL) {
|
func selected(url: URL) {
|
||||||
|
|
|
@ -181,7 +181,7 @@ class ContentLabel: LinkLabel {
|
||||||
if let mention = getMention(for: url, text: text) {
|
if let mention = getMention(for: url, text: text) {
|
||||||
return ProfileTableViewController(accountID: mention.id)
|
return ProfileTableViewController(accountID: mention.id)
|
||||||
} else if let tag = getHashtag(for: url, text: text) {
|
} else if let tag = getHashtag(for: url, text: text) {
|
||||||
return TimelineTableViewController(for: .tag(hashtag: tag.name))
|
return HashtagTimelineViewController(for: tag)
|
||||||
} else {
|
} else {
|
||||||
return SFSafariViewController(url: url)
|
return SFSafariViewController(url: url)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue