Compare commits

...

13 Commits

29 changed files with 164 additions and 283 deletions

View File

@ -173,8 +173,6 @@
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67B506C250B291200FAECFB /* BlurHashDecode.swift */; }; D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67B506C250B291200FAECFB /* BlurHashDecode.swift */; };
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */; }; D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */; };
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */; }; D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */; };
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */; };
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */; };
D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */; }; D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */; };
D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68015412401A74600D6103B /* MediaPrefsView.swift */; }; D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68015412401A74600D6103B /* MediaPrefsView.swift */; };
D681A29A249AD62D0085E54E /* LargeImageContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681A299249AD62D0085E54E /* LargeImageContentView.swift */; }; D681A29A249AD62D0085E54E /* LargeImageContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681A299249AD62D0085E54E /* LargeImageContentView.swift */; };
@ -502,8 +500,6 @@
D67B506C250B291200FAECFB /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; }; D67B506C250B291200FAECFB /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeAccountDetailView.swift; sourceTree = "<group>"; }; D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeAccountDetailView.swift; sourceTree = "<group>"; };
D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Uniques.swift"; sourceTree = "<group>"; }; D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Uniques.swift"; sourceTree = "<group>"; };
D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeStatusReplyView.xib; sourceTree = "<group>"; };
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; sourceTree = "<group>"; };
D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposingPrefsView.swift; sourceTree = "<group>"; }; D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposingPrefsView.swift; sourceTree = "<group>"; };
D68015412401A74600D6103B /* MediaPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPrefsView.swift; sourceTree = "<group>"; }; D68015412401A74600D6103B /* MediaPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPrefsView.swift; sourceTree = "<group>"; };
D681A299249AD62D0085E54E /* LargeImageContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageContentView.swift; sourceTree = "<group>"; }; D681A299249AD62D0085E54E /* LargeImageContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageContentView.swift; sourceTree = "<group>"; };
@ -1145,15 +1141,6 @@
path = "Account Detail"; path = "Account Detail";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D67C57B021E28F9400C3118B /* Compose Status Reply */ = {
isa = PBXGroup;
children = (
D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */,
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */,
);
path = "Compose Status Reply";
sourceTree = "<group>";
};
D6A3BC7223218C6E00FD64D5 /* Utilities */ = { D6A3BC7223218C6E00FD64D5 /* Utilities */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1273,7 +1260,6 @@
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */, D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */, D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */,
D67C57A721E2649B00C3118B /* Account Detail */, D67C57A721E2649B00C3118B /* Account Detail */,
D67C57B021E28F9400C3118B /* Compose Status Reply */,
D626494023C122C800612E6E /* Asset Picker */, D626494023C122C800612E6E /* Asset Picker */,
D61959D0241E842400A37B8E /* Draft Cell */, D61959D0241E842400A37B8E /* Draft Cell */,
D641C78A213DD926004B4513 /* Status */, D641C78A213DD926004B4513 /* Status */,
@ -1645,7 +1631,6 @@
D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */, D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */,
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */, D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */,
D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */, D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */,
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */,
D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */, D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */,
D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */, D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */,
D6F2E966249E8BFD005846BB /* CrashReporterViewController.xib in Resources */, D6F2E966249E8BFD005846BB /* CrashReporterViewController.xib in Resources */,
@ -1818,7 +1803,6 @@
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */, D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
D681E4D5246E2BC30053414F /* UnmuteConversationActivity.swift in Sources */, D681E4D5246E2BC30053414F /* UnmuteConversationActivity.swift in Sources */,
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameLabel.swift in Sources */, D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameLabel.swift in Sources */,
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */,
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */, D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */,
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */, D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */,
D62275A024F1677200B82A16 /* ComposeHostingController.swift in Sources */, D62275A024F1677200B82A16 /* ComposeHostingController.swift in Sources */,

View File

@ -89,6 +89,13 @@
isEnabled = "YES"> isEnabled = "YES">
</CommandLineArgument> </CommandLineArgument>
</CommandLineArguments> </CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "DISABLE_IMAGE_CACHE"
value = "1"
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release" buildConfiguration = "Release"

View File

@ -15,6 +15,7 @@ enum Cache<T> {
case disk(DiskStorage<T>) case disk(DiskStorage<T>)
case hybrid(HybridStorage<T>) case hybrid(HybridStorage<T>)
@available(*, deprecated, message: "disk-based caches synchronously interact with the file system. Avoid using if possible.")
func existsObject(forKey key: String) throws -> Bool { func existsObject(forKey key: String) throws -> Bool {
switch self { switch self {
case let .memory(memory): case let .memory(memory):

View File

@ -16,6 +16,12 @@ class ImageCache {
static let attachments = ImageCache(name: "Attachments", memoryExpiry: .seconds(60 * 2)) static let attachments = ImageCache(name: "Attachments", memoryExpiry: .seconds(60 * 2))
static let emojis = ImageCache(name: "Emojis", memoryExpiry: .seconds(60 * 5), diskExpiry: .seconds(60 * 60)) static let emojis = ImageCache(name: "Emojis", memoryExpiry: .seconds(60 * 5), diskExpiry: .seconds(60 * 60))
#if DEBUG
private static let disableCaching = ProcessInfo.processInfo.environment.keys.contains("DISABLE_IMAGE_CACHE")
#else
private static let disableCaching = false
#endif
private let cache: Cache<Data> private let cache: Cache<Data>
private var groups = [URL: RequestGroup]() private var groups = [URL: RequestGroup]()
@ -38,8 +44,8 @@ class ImageCache {
func get(_ url: URL, completion: ((Data?) -> Void)?) -> Request? { func get(_ url: URL, completion: ((Data?) -> Void)?) -> Request? {
let key = url.absoluteString let key = url.absoluteString
if (try? cache.existsObject(forKey: key)) ?? false, if !ImageCache.disableCaching,
let data = try? cache.object(forKey: key) { let data = try? cache.object(forKey: key) {
completion?(data) completion?(data)
return nil return nil
} else { } else {

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
import Pachyderm import Pachyderm
class MastodonController { class MastodonController: ObservableObject {
static private(set) var all = [LocalData.UserAccountInfo: MastodonController]() static private(set) var all = [LocalData.UserAccountInfo: MastodonController]()
@ -42,8 +42,8 @@ class MastodonController {
let client: Client! let client: Client!
var account: Account! @Published private(set) var account: Account!
var instance: Instance! @Published private(set) var instance: Instance!
var loggedIn: Bool { var loggedIn: Bool {
accountInfo != nil accountInfo != nil
@ -95,7 +95,9 @@ class MastodonController {
completion?(.failure(error)) completion?(.failure(error))
case let .success(account, _): case let .success(account, _):
self.account = account DispatchQueue.main.async {
self.account = account
}
self.persistentContainer.backgroundContext.perform { self.persistentContainer.backgroundContext.perform {
if let accountMO = self.persistentContainer.account(for: account.id, in: self.persistentContainer.backgroundContext) { if let accountMO = self.persistentContainer.account(for: account.id, in: self.persistentContainer.backgroundContext) {
accountMO.updateFrom(apiAccount: account, container: self.persistentContainer) accountMO.updateFrom(apiAccount: account, container: self.persistentContainer)
@ -118,13 +120,12 @@ class MastodonController {
let request = Client.getInstance() let request = Client.getInstance()
run(request) { (response) in run(request) { (response) in
guard case let .success(instance, _) = response else { fatalError() } guard case let .success(instance, _) = response else { fatalError() }
self.instance = instance DispatchQueue.main.async {
completion?(instance) self.instance = instance
completion?(instance)
}
} }
} }
} }
} }
// ObservableObject so that SwiftUI views can receive it through @EnvironmentObject
extension MastodonController: ObservableObject {}

View File

@ -158,15 +158,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
mastodonController.getOwnInstance() mastodonController.getOwnInstance()
let rootController: UIViewController let rootController: UIViewController
#if SDK_IOS_14
if #available(iOS 14.0, *) { if #available(iOS 14.0, *) {
rootController = MainSplitViewController(mastodonController: mastodonController) rootController = MainSplitViewController(mastodonController: mastodonController)
} else { } else {
rootController = MainTabBarViewController(mastodonController: mastodonController) rootController = MainTabBarViewController(mastodonController: mastodonController)
} }
#else
rootController = MainTabBarViewController(mastodonController: mastodonController)
#endif
window!.rootViewController = rootController window!.rootViewController = rootController
} }

View File

@ -91,7 +91,9 @@ struct ComposeAttachmentsList: View {
} }
private var canAddAttachment: Bool { private var canAddAttachment: Bool {
switch mastodonController.instance.instanceType { switch mastodonController.instance?.instanceType {
case nil:
return false
case .pleroma: case .pleroma:
return true return true
case .mastodon: case .mastodon:

View File

@ -9,7 +9,7 @@
import SwiftUI import SwiftUI
struct ComposeAvatarImageView: View { struct ComposeAvatarImageView: View {
let url: URL let url: URL?
@State var request: ImageCache.Request? = nil @State var request: ImageCache.Request? = nil
@State var avatarImage: UIImage? = nil @State var avatarImage: UIImage? = nil
@ObservedObject var preferences = Preferences.shared @ObservedObject var preferences = Preferences.shared
@ -19,7 +19,9 @@ struct ComposeAvatarImageView: View {
.resizable() .resizable()
.frame(width: 50, height: 50) .frame(width: 50, height: 50)
.cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50) .cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50)
.onAppear(perform: self.loadImage) .conditionally(url != nil) {
$0.onAppear(perform: self.loadImage)
}
.onDisappear(perform: self.cancelRequest) .onDisappear(perform: self.cancelRequest)
} }
@ -27,24 +29,33 @@ struct ComposeAvatarImageView: View {
if let avatarImage = avatarImage { if let avatarImage = avatarImage {
return Image(uiImage: avatarImage) return Image(uiImage: avatarImage)
} else { } else {
let imageName: String return placeholderImage
switch preferences.avatarStyle {
case .circle:
imageName = "person.crop.circle"
case .roundRect:
imageName = "person.crop.square"
}
return Image(systemName: imageName)
} }
} }
private var placeholderImage: Image {
let imageName: String
switch preferences.avatarStyle {
case .circle:
imageName = "person.crop.circle"
case .roundRect:
imageName = "person.crop.square"
}
return Image(systemName: imageName)
}
private func loadImage() { private func loadImage() {
guard let url = url else { return }
request = ImageCache.avatars.get(url) { (data) in request = ImageCache.avatars.get(url) { (data) in
DispatchQueue.main.async { if let data = data, let image = UIImage(data: data) {
self.request = nil DispatchQueue.main.async {
if let data = data, let image = UIImage(data: data) { self.request = nil
self.avatarImage = image self.avatarImage = image
} }
} else {
DispatchQueue.main.async {
self.request = nil
}
} }
} }
} }

View File

@ -12,24 +12,29 @@ import Pachyderm
struct ComposeCurrentAccount: View { struct ComposeCurrentAccount: View {
@EnvironmentObject var mastodonController: MastodonController @EnvironmentObject var mastodonController: MastodonController
var account: Account { var account: Account? {
mastodonController.account! mastodonController.account
} }
var body: some View { var body: some View {
HStack(alignment: .top) { HStack(alignment: .top) {
ComposeAvatarImageView(url: account.avatar) ComposeAvatarImageView(url: account?.avatar)
.accessibility(label: Text("\(account.displayName) avatar")) .accessibility(label: Text(account != nil ? "\(account!.displayName) avatar" : "Avatar"))
VStack(alignment: .leading) { if let id = account?.id,
AccountDisplayNameLabel(account: mastodonController.persistentContainer.account(for: account.id)!, fontSize: 20) let account = mastodonController.persistentContainer.account(for: id) {
.lineLimit(1) VStack(alignment: .leading) {
AccountDisplayNameLabel(account: account, fontSize: 20)
.lineLimit(1)
Text(verbatim: "@\(account.acct)") Text(verbatim: "@\(account.acct)")
.font(.system(size: 17, weight: .light)) .font(.system(size: 17, weight: .light))
.foregroundColor(.secondary) .foregroundColor(.secondary)
.lineLimit(1) .lineLimit(1)
}
} }
Spacer()
} }
} }
} }

View File

@ -58,15 +58,11 @@ class ComposeDrawingViewController: UIViewController {
canvasView.drawing = initialDrawing canvasView.drawing = initialDrawing
} }
canvasView.delegate = self canvasView.delegate = self
#if SDK_IOS_14
if #available(iOS 14.0, *) { if #available(iOS 14.0, *) {
canvasView.drawingPolicy = .anyInput canvasView.drawingPolicy = .anyInput
} else { } else {
canvasView.allowsFingerDrawing = true canvasView.allowsFingerDrawing = true
} }
#else
canvasView.allowsFingerDrawing = true
#endif
canvasView.minimumZoomScale = 0.5 canvasView.minimumZoomScale = 0.5
canvasView.maximumZoomScale = 2 canvasView.maximumZoomScale = 2
canvasView.backgroundColor = .systemBackground canvasView.backgroundColor = .systemBackground

View File

@ -27,13 +27,7 @@ struct ComposeTextView: View {
var body: some View { var body: some View {
ZStack(alignment: .topLeading) { ZStack(alignment: .topLeading) {
WrappedTextView( Color(backgroundColor)
text: $text,
textDidChange: self.textDidChange,
backgroundColor: backgroundColor,
font: .systemFont(ofSize: fontSize)
)
.frame(height: height ?? minHeight)
if text.isEmpty, let placeholder = placeholder { if text.isEmpty, let placeholder = placeholder {
placeholder placeholder
@ -41,6 +35,13 @@ struct ComposeTextView: View {
.foregroundColor(.secondary) .foregroundColor(.secondary)
.offset(x: 4, y: 8) .offset(x: 4, y: 8)
} }
WrappedTextView(
text: $text,
textDidChange: self.textDidChange,
font: .systemFont(ofSize: fontSize)
)
.frame(height: height ?? minHeight)
} }
} }
@ -73,14 +74,13 @@ struct WrappedTextView: UIViewRepresentable {
@Binding var text: String @Binding var text: String
var textDidChange: ((UITextView) -> Void)? var textDidChange: ((UITextView) -> Void)?
var backgroundColor = UIColor.secondarySystemBackground
var font = UIFont.systemFont(ofSize: 20) var font = UIFont.systemFont(ofSize: 20)
func makeUIView(context: Context) -> UITextView { func makeUIView(context: Context) -> UITextView {
let textView = UITextView() let textView = UITextView()
textView.delegate = context.coordinator textView.delegate = context.coordinator
textView.isEditable = true textView.isEditable = true
textView.backgroundColor = backgroundColor textView.backgroundColor = .clear
textView.font = font textView.font = font
textView.textContainer.lineBreakMode = .byWordWrapping textView.textContainer.lineBreakMode = .byWordWrapping
return textView return textView

View File

@ -28,7 +28,7 @@ struct ComposeView: View {
} }
var charactersRemaining: Int { var charactersRemaining: Int {
let limit = mastodonController.instance.maxStatusCharacters ?? 500 let limit = mastodonController.instance?.maxStatusCharacters ?? 500
let cwCount = draft.contentWarningEnabled ? draft.contentWarning.count : 0 let cwCount = draft.contentWarningEnabled ? draft.contentWarning.count : 0
return limit - (cwCount + CharacterCounter.count(text: draft.text)) return limit - (cwCount + CharacterCounter.count(text: draft.text))
} }

View File

@ -20,6 +20,15 @@ struct MainComposeTextView: View {
var body: some View { var body: some View {
ZStack(alignment: .topLeading) { ZStack(alignment: .topLeading) {
Color(UIColor.secondarySystemBackground)
if draft.text.isEmpty {
placeholder
.font(.system(size: 20))
.foregroundColor(.secondary)
.offset(x: 4, y: 8)
}
MainComposeWrappedTextView( MainComposeWrappedTextView(
text: $draft.text, text: $draft.text,
visibility: draft.visibility, visibility: draft.visibility,
@ -28,13 +37,6 @@ struct MainComposeTextView: View {
self.height = max(textView.contentSize.height, minHeight) self.height = max(textView.contentSize.height, minHeight)
} }
.frame(height: height ?? minHeight) .frame(height: height ?? minHeight)
if draft.text.isEmpty {
placeholder
.font(.system(size: 20))
.foregroundColor(.secondary)
.offset(x: 4, y: 8)
}
}.onAppear { }.onAppear {
if !hasFirstAppeared { if !hasFirstAppeared {
hasFirstAppeared = true hasFirstAppeared = true
@ -59,7 +61,7 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
let textView = UITextView() let textView = UITextView()
textView.delegate = context.coordinator textView.delegate = context.coordinator
textView.isEditable = true textView.isEditable = true
textView.backgroundColor = .secondarySystemBackground textView.backgroundColor = .clear
textView.font = .systemFont(ofSize: 20) textView.font = .systemFont(ofSize: 20)
textView.textContainer.lineBreakMode = .byWordWrapping textView.textContainer.lineBreakMode = .byWordWrapping
context.coordinator.textView = textView context.coordinator.textView = textView

View File

@ -52,6 +52,8 @@ class ConversationTableViewController: EnhancedTableViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
title = NSLocalizedString("Conversation", comment: "conversation screen title")
tableView.delegate = self tableView.delegate = self
tableView.dataSource = self tableView.dataSource = self

View File

@ -20,9 +20,7 @@ class LargeImageInteractionController: UIPercentDrivenInteractiveTransition {
super.init() super.init()
self.viewController = viewController self.viewController = viewController
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:))) let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:)))
if #available(iOS 13.4, *) { panRecognizer.allowedScrollTypesMask = .all
panRecognizer.allowedScrollTypesMask = .all
}
viewController.view.addGestureRecognizer(panRecognizer) viewController.view.addGestureRecognizer(panRecognizer)
} }

View File

@ -9,7 +9,6 @@
import UIKit import UIKit
import Pachyderm import Pachyderm
#if SDK_IOS_14
@available(iOS 14.0, *) @available(iOS 14.0, *)
protocol MainSidebarViewControllerDelegate: class { protocol MainSidebarViewControllerDelegate: class {
func sidebarRequestPresentCompose(_ sidebarViewController: MainSidebarViewController) func sidebarRequestPresentCompose(_ sidebarViewController: MainSidebarViewController)
@ -380,4 +379,3 @@ extension MainSidebarViewController: InstanceTimelineViewControllerDelegate {
dismiss(animated: true) dismiss(animated: true)
} }
} }
#endif

View File

@ -8,7 +8,6 @@
import UIKit import UIKit
#if SDK_IOS_14
@available(iOS 14.0, *) @available(iOS 14.0, *)
class MainSplitViewController: UISplitViewController { class MainSplitViewController: UISplitViewController {
@ -316,11 +315,15 @@ extension MainSplitViewController: TuskerRootViewController {
} }
func select(tab: MainTabBarViewController.Tab) { func select(tab: MainTabBarViewController.Tab) {
if tab == .compose { if traitCollection.horizontalSizeClass == .compact {
presentCompose() tabBarViewController?.select(tab: tab)
} else { } else {
select(item: .tab(tab)) if tab == .compose {
presentCompose()
} else {
select(item: .tab(tab))
sidebar.select(item: .tab(tab), animated: false)
}
} }
} }
} }
#endif

View File

@ -36,11 +36,9 @@ class InteractivePushTransition: UIPercentDrivenInteractiveTransition {
interactivePushGestureRecognizer.require(toFail: navigationController.interactivePopGestureRecognizer!) interactivePushGestureRecognizer.require(toFail: navigationController.interactivePopGestureRecognizer!)
navigationController.view.addGestureRecognizer(interactivePushGestureRecognizer) navigationController.view.addGestureRecognizer(interactivePushGestureRecognizer)
if #available(iOS 13.4, *) { let trackpadGestureRecognizer = TrackpadScrollGestureRecognizer(target: self, action: #selector(handleSwipeForward(_:)))
let trackpadGestureRecognizer = TrackpadScrollGestureRecognizer(target: self, action: #selector(handleSwipeForward(_:))) trackpadGestureRecognizer.require(toFail: navigationController.interactivePopGestureRecognizer!)
trackpadGestureRecognizer.require(toFail: navigationController.interactivePopGestureRecognizer!) navigationController.view.addGestureRecognizer(trackpadGestureRecognizer)
navigationController.view.addGestureRecognizer(trackpadGestureRecognizer)
}
} }
@objc func handleSwipeForward(_ recognizer: UIPanGestureRecognizer) { @objc func handleSwipeForward(_ recognizer: UIPanGestureRecognizer) {

View File

@ -54,8 +54,6 @@ extension MenuPreviewProvider {
}), }),
] ]
// todo: handle pre-iOS 14
#if SDK_IOS_14
if accountID != mastodonController.account.id, if accountID != mastodonController.account.id,
#available(iOS 14.0, *) { #available(iOS 14.0, *) {
actionsSection.append(UIDeferredMenuElement({ (elementHandler) in actionsSection.append(UIDeferredMenuElement({ (elementHandler) in
@ -71,7 +69,7 @@ extension MenuPreviewProvider {
let following = relationship.following let following = relationship.following
DispatchQueue.main.async { DispatchQueue.main.async {
elementHandler([ elementHandler([
self.createAction(identifier: "follow", title: following ? "Unfollow" : "Follow", systemImageName: following ? "person.badge.minus" : "person.badge.minus", handler: { (_) in self.createAction(identifier: "follow", title: following ? "Unfollow" : "Follow", systemImageName: following ? "person.badge.minus" : "person.badge.plus", handler: { (_) in
let request = (following ? Account.unfollow : Account.follow)(accountID) let request = (following ? Account.unfollow : Account.follow)(accountID)
mastodonController.run(request) { (_) in mastodonController.run(request) { (_) in
} }
@ -82,7 +80,6 @@ extension MenuPreviewProvider {
} }
})) }))
} }
#endif
let shareSection = [ let shareSection = [
openInSafariAction(url: account.url), openInSafariAction(url: account.url),

View File

@ -47,8 +47,9 @@ enum AppShortcutItem: String, CaseIterable {
} }
let scene = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first! let scene = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first!
let window = scene.windows.first { $0.isKeyWindow }! let window = scene.windows.first { $0.isKeyWindow }!
let controller = window.rootViewController as! MainTabBarViewController if let controller = window.rootViewController as? TuskerRootViewController {
controller.select(tab: tab) controller.select(tab: tab)
}
} }
} }

View File

@ -58,11 +58,11 @@ class AttachmentsContainerView: UIView {
attachmentViews.removeAllObjects() attachmentViews.removeAllObjects()
moreView?.removeFromSuperview() moreView?.removeFromSuperview()
var accessibilityElements = [Any]()
if attachments.count > 0 { if attachments.count > 0 {
self.isHidden = false self.isHidden = false
var accessibilityElements = [Any]()
switch attachments.count { switch attachments.count {
case 1: case 1:
let attachmentView = createAttachmentView(index: 0, hSize: .full, vSize: .full) let attachmentView = createAttachmentView(index: 0, hSize: .full, vSize: .full)
@ -215,13 +215,16 @@ class AttachmentsContainerView: UIView {
accessibilityElements.append(topRight) accessibilityElements.append(topRight)
accessibilityElements.append(bottomLeft) accessibilityElements.append(bottomLeft)
accessibilityElements.append(moreView) accessibilityElements.append(moreView)
}
self.accessibilityElements = accessibilityElements }
} else { } else {
self.isHidden = true self.isHidden = true
} }
// Make sure accessibilityElements is set every time the UI is updated, otherwise it holds
// on to strong references to the old set of attachment views
self.accessibilityElements = accessibilityElements
contentHidden = Preferences.shared.blurAllMedia || status.sensitive contentHidden = Preferences.shared.blurAllMedia || status.sensitive
} }

View File

@ -1,56 +0,0 @@
//
// ComposeStatusReplyView.swift
// Tusker
//
// Created by Shadowfacts on 1/6/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
class ComposeStatusReplyView: UIView {
weak var mastodonController: MastodonController?
@IBOutlet weak var avatarImageView: UIImageView!
@IBOutlet weak var displayNameLabel: EmojiLabel!
@IBOutlet weak var usernameLabel: UILabel!
@IBOutlet weak var statusContentTextView: StatusContentTextView!
var avatarRequest: ImageCache.Request?
static func create() -> ComposeStatusReplyView {
return UINib(nibName: "ComposeStatusReplyView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! ComposeStatusReplyView
}
deinit {
avatarRequest?.cancel()
}
override func awakeFromNib() {
super.awakeFromNib()
updateUIForPreferences()
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
}
@objc func updateUIForPreferences() {
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
}
func updateUI(for status: StatusMO) {
displayNameLabel.updateForAccountDisplayName(account: status.account)
usernameLabel.text = "@\(status.account.acct)"
statusContentTextView.overrideMastodonController = mastodonController
statusContentTextView.setTextFrom(status: status)
avatarRequest = ImageCache.avatars.get(status.account.avatar) { [weak self] (data) in
guard let self = self, let data = data else { return }
DispatchQueue.main.async {
self.avatarImageView.image = UIImage(data: data)
}
}
}
}

View File

@ -1,83 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16092.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16082.1"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="ComposeStatusReplyView" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Ypn-Ed-MTq">
<rect key="frame" x="8" y="8" width="50" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="8qi-gl-5ci"/>
<constraint firstAttribute="width" constant="50" id="Dy2-jh-AJj"/>
</constraints>
</imageView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2cE-sS-Uut">
<rect key="frame" x="66" y="8" width="301" height="651"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Sdv-dB-Plm" customClass="EmojiLabel" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="107" height="20.5"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="749" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0yZ-71-eTj">
<rect key="frame" x="115" y="0.0" width="178" height="21"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" staticText="YES" notEnabled="YES"/>
<bool key="isElement" value="NO"/>
</accessibility>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" scrollEnabled="NO" delaysContentTouches="NO" editable="NO" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="atN-ay-ceL" customClass="StatusContentTextView" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="0.0" y="25" width="301" height="626"/>
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="atN-ay-ceL" secondAttribute="bottom" id="3ub-qq-laF"/>
<constraint firstItem="Sdv-dB-Plm" firstAttribute="leading" secondItem="2cE-sS-Uut" secondAttribute="leading" id="6v5-7p-9gm"/>
<constraint firstItem="Sdv-dB-Plm" firstAttribute="top" secondItem="2cE-sS-Uut" secondAttribute="top" id="YmP-yU-sfe"/>
<constraint firstItem="0yZ-71-eTj" firstAttribute="top" secondItem="2cE-sS-Uut" secondAttribute="top" id="bdX-ge-bMT"/>
<constraint firstAttribute="trailing" secondItem="0yZ-71-eTj" secondAttribute="trailing" constant="8" id="hU7-aZ-ibI"/>
<constraint firstItem="atN-ay-ceL" firstAttribute="leading" secondItem="2cE-sS-Uut" secondAttribute="leading" id="k5c-jg-Dy8"/>
<constraint firstItem="0yZ-71-eTj" firstAttribute="leading" secondItem="Sdv-dB-Plm" secondAttribute="trailing" constant="8" id="m0X-YU-m3V"/>
<constraint firstItem="atN-ay-ceL" firstAttribute="top" secondItem="0yZ-71-eTj" secondAttribute="bottom" constant="4" id="pXc-4g-PAe"/>
<constraint firstAttribute="trailing" secondItem="atN-ay-ceL" secondAttribute="trailing" id="qcg-bA-8ba"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="2cE-sS-Uut" firstAttribute="height" relation="greaterThanOrEqual" secondItem="Ypn-Ed-MTq" secondAttribute="height" id="Fn3-o4-RGx"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="2cE-sS-Uut" secondAttribute="bottom" constant="8" id="G2d-Kz-c4e"/>
<constraint firstItem="Ypn-Ed-MTq" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="8" id="MbW-9d-3gC"/>
<constraint firstItem="2cE-sS-Uut" firstAttribute="leading" secondItem="Ypn-Ed-MTq" secondAttribute="trailing" constant="8" id="TS2-Sr-PB3"/>
<constraint firstItem="2cE-sS-Uut" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="cat-Cr-PSV"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="2cE-sS-Uut" secondAttribute="trailing" constant="8" id="eH4-lG-5UR"/>
<constraint firstItem="Ypn-Ed-MTq" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" placeholder="YES" id="xCn-8G-jUZ"/>
</constraints>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<connections>
<outlet property="avatarImageView" destination="Ypn-Ed-MTq" id="eea-bc-klc"/>
<outlet property="displayNameLabel" destination="Sdv-dB-Plm" id="RxW-Ra-Ups"/>
<outlet property="statusContentTextView" destination="atN-ay-ceL" id="i6A-Rd-rJp"/>
<outlet property="usernameLabel" destination="0yZ-71-eTj" id="VQm-Dq-3zP"/>
</connections>
<point key="canvasLocation" x="138.40000000000001" y="-72.863568215892059"/>
</view>
</objects>
</document>

View File

@ -67,15 +67,11 @@ class ProfileHeaderView: UIView {
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
if #available(iOS 13.4, *) { moreButton.addInteraction(UIPointerInteraction(delegate: self))
moreButton.addInteraction(UIPointerInteraction(delegate: self))
}
#if SDK_IOS_14
if #available(iOS 14.0, *) { if #available(iOS 14.0, *) {
moreButton.showsMenuAsPrimaryAction = true moreButton.showsMenuAsPrimaryAction = true
moreButton.isContextMenuInteractionEnabled = true moreButton.isContextMenuInteractionEnabled = true
} }
#endif
} }
func updateUI(for accountID: String) { func updateUI(for accountID: String) {

View File

@ -87,11 +87,9 @@ class BaseStatusTableViewCell: UITableViewCell {
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!] accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!]
attachmentsView.isAccessibilityElement = true attachmentsView.isAccessibilityElement = true
#if SDK_IOS_14
if #available(iOS 14.0, *) { if #available(iOS 14.0, *) {
moreButton.showsMenuAsPrimaryAction = true moreButton.showsMenuAsPrimaryAction = true
} }
#endif
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
} }
@ -122,19 +120,25 @@ class BaseStatusTableViewCell: UITableViewCell {
} }
} }
func updateUI(statusID: String, state: StatusState) { final func updateUI(statusID: String, state: StatusState) {
createObserversIfNecessary() createObserversIfNecessary()
guard let status = mastodonController.persistentContainer.status(for: statusID) else { guard let status = mastodonController.persistentContainer.status(for: statusID) else {
fatalError("Missing cached status") fatalError("Missing cached status")
} }
self.statusID = statusID self.statusID = statusID
doUpdateUI(status: status, state: state)
}
func doUpdateUI(status: StatusMO, state: StatusState) {
self.statusState = state self.statusState = state
let account = status.account let account = status.account
self.accountID = account.id self.accountID = account.id
updateUI(account: account) updateUI(account: account)
updateUIForPreferences(account: account) updateUIForPreferences(account: account, status: status)
attachmentsView.updateUI(status: status) attachmentsView.updateUI(status: status)
attachmentsView.isAccessibilityElement = status.attachments.count > 0 attachmentsView.isAccessibilityElement = status.attachments.count > 0
@ -194,12 +198,10 @@ class BaseStatusTableViewCell: UITableViewCell {
reblogButton.accessibilityLabel = NSLocalizedString("Reblog", comment: "reblog button accessibility label") reblogButton.accessibilityLabel = NSLocalizedString("Reblog", comment: "reblog button accessibility label")
} }
#if SDK_IOS_14
if #available(iOS 14.0, *) { if #available(iOS 14.0, *) {
// keep menu in sync with changed states e.g. bookmarked, muted // keep menu in sync with changed states e.g. bookmarked, muted
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: actionsForStatus(statusID: statusID, sourceView: moreButton)) moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: actionsForStatus(statusID: statusID, sourceView: moreButton))
} }
#endif
} }
func updateUI(account: AccountMO) { func updateUI(account: AccountMO) {
@ -213,18 +215,19 @@ class BaseStatusTableViewCell: UITableViewCell {
} }
} }
@objc func preferencesChanged() { @objc private func preferencesChanged() {
guard let mastodonController = mastodonController, guard let mastodonController = mastodonController,
let account = mastodonController.persistentContainer.account(for: accountID), let account = mastodonController.persistentContainer.account(for: accountID),
let status = mastodonController.persistentContainer.status(for: statusID) else { return } let status = mastodonController.persistentContainer.status(for: statusID) else { return }
updateUIForPreferences(account: account) updateUIForPreferences(account: account, status: status)
updateStatusIconsForPreferences(status)
} }
func updateUIForPreferences(account: AccountMO) { func updateUIForPreferences(account: AccountMO, status: StatusMO) {
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView) avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
displayNameLabel.updateForAccountDisplayName(account: account) displayNameLabel.updateForAccountDisplayName(account: account)
attachmentsView.contentHidden = Preferences.shared.blurAllMedia || (mastodonController.persistentContainer.status(for: statusID)?.sensitive ?? false) attachmentsView.contentHidden = Preferences.shared.blurAllMedia || (mastodonController.persistentContainer.status(for: statusID)?.sensitive ?? false)
updateStatusIconsForPreferences(status)
} }
func updateStatusIconsForPreferences(_ status: StatusMO) { func updateStatusIconsForPreferences(_ status: StatusMO) {

View File

@ -38,9 +38,8 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
contentTextView.defaultFont = .systemFont(ofSize: 18) contentTextView.defaultFont = .systemFont(ofSize: 18)
} }
override func updateUI(statusID: String, state: StatusState) { override func doUpdateUI(status: StatusMO, state: StatusState) {
super.updateUI(statusID: statusID, state: state) super.doUpdateUI(status: status, state: state)
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError() }
var timestampAndClientText = ConversationMainStatusTableViewCell.dateFormatter.string(from: status.createdAt) var timestampAndClientText = ConversationMainStatusTableViewCell.dateFormatter.string(from: status.createdAt)
if let application = status.applicationName { if let application = status.applicationName {
@ -63,8 +62,8 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
profileAccessibilityElement.accessibilityLabel = account.displayNameWithoutCustomEmoji profileAccessibilityElement.accessibilityLabel = account.displayNameWithoutCustomEmoji
} }
override func updateUIForPreferences(account: AccountMO) { override func updateUIForPreferences(account: AccountMO, status: StatusMO) {
super.updateUIForPreferences(account: account) super.updateUIForPreferences(account: account, status: status)
favoriteAndReblogCountStackView.isHidden = !Preferences.shared.showFavoriteAndReblogCounts favoriteAndReblogCountStackView.isHidden = !Preferences.shared.showFavoriteAndReblogCounts
} }

View File

@ -68,10 +68,9 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
} }
} }
override func updateUI(statusID: String, state: StatusState) { override func doUpdateUI(status: StatusMO, state: StatusState) {
guard var status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID)") } var status = status
let realStatusID: String
if let rebloggedStatus = status.reblog { if let rebloggedStatus = status.reblog {
reblogStatusID = statusID reblogStatusID = statusID
rebloggerID = status.account.id rebloggerID = status.account.id
@ -79,25 +78,24 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
updateRebloggerLabel(reblogger: status.account) updateRebloggerLabel(reblogger: status.account)
status = rebloggedStatus status = rebloggedStatus
realStatusID = rebloggedStatus.id statusID = rebloggedStatus.id
} else { } else {
reblogStatusID = nil reblogStatusID = nil
rebloggerID = nil rebloggerID = nil
reblogLabel.isHidden = true reblogLabel.isHidden = true
realStatusID = statusID
} }
super.updateUI(statusID: realStatusID, state: state) super.doUpdateUI(status: status, state: state)
updateTimestamp() doUpdateTimestamp(status: status)
let pinned = showPinned && (status.pinned ?? false) let pinned = showPinned && (status.pinned ?? false)
timestampLabel.isHidden = pinned timestampLabel.isHidden = pinned
pinImageView.isHidden = !pinned pinImageView.isHidden = !pinned
} }
@objc override func preferencesChanged() { override func updateUIForPreferences(account: AccountMO, status: StatusMO) {
super.preferencesChanged() super.updateUIForPreferences(account: account, status: status)
if let rebloggerID = rebloggerID, if let rebloggerID = rebloggerID,
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) { let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
@ -121,12 +119,16 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
replyImageView.isHidden = !Preferences.shared.showIsStatusReplyIcon || !showReplyIndicator || status.inReplyToID == nil replyImageView.isHidden = !Preferences.shared.showIsStatusReplyIcon || !showReplyIndicator || status.inReplyToID == nil
} }
func updateTimestamp() { private func updateTimestamp() {
// if the mastodonController is nil (i.e. the delegate is nil), then the screen this cell was a part of has been deallocated // if the mastodonController is nil (i.e. the delegate is nil), then the screen this cell was a part of has been deallocated
// so we bail out immediately, since there's nothing to update // so we bail out immediately, since there's nothing to update
guard let mastodonController = mastodonController else { return } guard let mastodonController = mastodonController else { return }
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") } guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
doUpdateTimestamp(status: status)
}
private func doUpdateTimestamp(status: StatusMO) {
timestampLabel.text = status.createdAt.timeAgoString() timestampLabel.text = status.createdAt.timeAgoString()
timestampLabel.accessibilityLabel = TimelineStatusTableViewCell.relativeDateFormatter.localizedString(for: status.createdAt, relativeTo: Date()) timestampLabel.accessibilityLabel = TimelineStatusTableViewCell.relativeDateFormatter.localizedString(for: status.createdAt, relativeTo: Date())

View File

@ -52,9 +52,7 @@ class VisualEffectImageButton: UIControl {
imageView.bottomAnchor.constraint(equalTo: vibrancyView.bottomAnchor, constant: -2), imageView.bottomAnchor.constraint(equalTo: vibrancyView.bottomAnchor, constant: -2),
]) ])
#if SDK_IOS_14
addInteraction(UIContextMenuInteraction(delegate: self)) addInteraction(UIContextMenuInteraction(delegate: self))
#endif
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap))) addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
} }
@ -63,12 +61,10 @@ class VisualEffectImageButton: UIControl {
sendActions(for: .touchUpInside) sendActions(for: .touchUpInside)
} }
#if SDK_IOS_14
override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
guard let menu = menu else { return nil } guard let menu = menu else { return nil }
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { (_) -> UIMenu? in return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { (_) -> UIMenu? in
return menu return menu
} }
} }
#endif
} }

View File

@ -16,12 +16,16 @@ fileprivate class WeakWrapper<T: AnyObject> {
} }
} }
struct WeakArray<Element: AnyObject>: Collection { struct WeakArray<Element: AnyObject>: MutableCollection, RangeReplaceableCollection {
private var array: [WeakWrapper<Element>] private var array: [WeakWrapper<Element>]
var startIndex: Int { array.startIndex } var startIndex: Int { array.startIndex }
var endIndex: Int { array.endIndex } var endIndex: Int { array.endIndex }
init() {
array = []
}
init(_ elements: [Element]) { init(_ elements: [Element]) {
array = elements.map { WeakWrapper($0) } array = elements.map { WeakWrapper($0) }
} }
@ -30,11 +34,20 @@ struct WeakArray<Element: AnyObject>: Collection {
array = elements.map { WeakWrapper($0) } array = elements.map { WeakWrapper($0) }
} }
subscript(_ index: Int) -> Element? { subscript(position: Int) -> Element? {
return array[index].value get {
array[position].value
}
set(newValue) {
array[position] = WeakWrapper(newValue)
}
} }
func index(after i: Int) -> Int { func index(after i: Int) -> Int {
return array.index(after: i) return array.index(after: i)
} }
mutating func replaceSubrange<C>(_ subrange: Range<Int>, with newElements: C) where C : Collection, Self.Element == C.Element {
array.replaceSubrange(subrange, with: newElements.map { WeakWrapper($0) })
}
} }