Compare commits
13 Commits
ee90b20f7f
...
576e4aa90d
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 576e4aa90d | |
Shadowfacts | ea3de4cdda | |
Shadowfacts | 83c7609df5 | |
Shadowfacts | 809584cc54 | |
Shadowfacts | 9b85090884 | |
Shadowfacts | 6965a4c374 | |
Shadowfacts | b6c0c02028 | |
Shadowfacts | 42f9d19ee9 | |
Shadowfacts | b80a61cc95 | |
Shadowfacts | 0d972d987c | |
Shadowfacts | 3e33c8e6f9 | |
Shadowfacts | 3822d536c8 | |
Shadowfacts | 5906c374ba |
|
@ -173,8 +173,6 @@
|
|||
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67B506C250B291200FAECFB /* BlurHashDecode.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 */; };
|
||||
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 */; };
|
||||
D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68015412401A74600D6103B /* MediaPrefsView.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -1145,15 +1141,6 @@
|
|||
path = "Account Detail";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D67C57B021E28F9400C3118B /* Compose Status Reply */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */,
|
||||
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */,
|
||||
);
|
||||
path = "Compose Status Reply";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D6A3BC7223218C6E00FD64D5 /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1273,7 +1260,6 @@
|
|||
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
|
||||
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */,
|
||||
D67C57A721E2649B00C3118B /* Account Detail */,
|
||||
D67C57B021E28F9400C3118B /* Compose Status Reply */,
|
||||
D626494023C122C800612E6E /* Asset Picker */,
|
||||
D61959D0241E842400A37B8E /* Draft Cell */,
|
||||
D641C78A213DD926004B4513 /* Status */,
|
||||
|
@ -1645,7 +1631,6 @@
|
|||
D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */,
|
||||
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */,
|
||||
D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */,
|
||||
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */,
|
||||
D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */,
|
||||
D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */,
|
||||
D6F2E966249E8BFD005846BB /* CrashReporterViewController.xib in Resources */,
|
||||
|
@ -1818,7 +1803,6 @@
|
|||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
||||
D681E4D5246E2BC30053414F /* UnmuteConversationActivity.swift in Sources */,
|
||||
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameLabel.swift in Sources */,
|
||||
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */,
|
||||
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */,
|
||||
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */,
|
||||
D62275A024F1677200B82A16 /* ComposeHostingController.swift in Sources */,
|
||||
|
|
|
@ -89,6 +89,13 @@
|
|||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "DISABLE_IMAGE_CACHE"
|
||||
value = "1"
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
|
|
@ -15,6 +15,7 @@ enum Cache<T> {
|
|||
case disk(DiskStorage<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 {
|
||||
switch self {
|
||||
case let .memory(memory):
|
||||
|
|
|
@ -16,6 +16,12 @@ class ImageCache {
|
|||
static let attachments = ImageCache(name: "Attachments", memoryExpiry: .seconds(60 * 2))
|
||||
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 var groups = [URL: RequestGroup]()
|
||||
|
@ -38,7 +44,7 @@ class ImageCache {
|
|||
|
||||
func get(_ url: URL, completion: ((Data?) -> Void)?) -> Request? {
|
||||
let key = url.absoluteString
|
||||
if (try? cache.existsObject(forKey: key)) ?? false,
|
||||
if !ImageCache.disableCaching,
|
||||
let data = try? cache.object(forKey: key) {
|
||||
completion?(data)
|
||||
return nil
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import Foundation
|
||||
import Pachyderm
|
||||
|
||||
class MastodonController {
|
||||
class MastodonController: ObservableObject {
|
||||
|
||||
static private(set) var all = [LocalData.UserAccountInfo: MastodonController]()
|
||||
|
||||
|
@ -42,8 +42,8 @@ class MastodonController {
|
|||
|
||||
let client: Client!
|
||||
|
||||
var account: Account!
|
||||
var instance: Instance!
|
||||
@Published private(set) var account: Account!
|
||||
@Published private(set) var instance: Instance!
|
||||
|
||||
var loggedIn: Bool {
|
||||
accountInfo != nil
|
||||
|
@ -95,7 +95,9 @@ class MastodonController {
|
|||
completion?(.failure(error))
|
||||
|
||||
case let .success(account, _):
|
||||
DispatchQueue.main.async {
|
||||
self.account = account
|
||||
}
|
||||
self.persistentContainer.backgroundContext.perform {
|
||||
if let accountMO = self.persistentContainer.account(for: account.id, in: self.persistentContainer.backgroundContext) {
|
||||
accountMO.updateFrom(apiAccount: account, container: self.persistentContainer)
|
||||
|
@ -118,13 +120,12 @@ class MastodonController {
|
|||
let request = Client.getInstance()
|
||||
run(request) { (response) in
|
||||
guard case let .success(instance, _) = response else { fatalError() }
|
||||
DispatchQueue.main.async {
|
||||
self.instance = instance
|
||||
completion?(instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ObservableObject so that SwiftUI views can receive it through @EnvironmentObject
|
||||
extension MastodonController: ObservableObject {}
|
||||
|
|
|
@ -158,15 +158,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
mastodonController.getOwnInstance()
|
||||
|
||||
let rootController: UIViewController
|
||||
#if SDK_IOS_14
|
||||
if #available(iOS 14.0, *) {
|
||||
rootController = MainSplitViewController(mastodonController: mastodonController)
|
||||
} else {
|
||||
rootController = MainTabBarViewController(mastodonController: mastodonController)
|
||||
}
|
||||
#else
|
||||
rootController = MainTabBarViewController(mastodonController: mastodonController)
|
||||
#endif
|
||||
window!.rootViewController = rootController
|
||||
}
|
||||
|
||||
|
|
|
@ -91,7 +91,9 @@ struct ComposeAttachmentsList: View {
|
|||
}
|
||||
|
||||
private var canAddAttachment: Bool {
|
||||
switch mastodonController.instance.instanceType {
|
||||
switch mastodonController.instance?.instanceType {
|
||||
case nil:
|
||||
return false
|
||||
case .pleroma:
|
||||
return true
|
||||
case .mastodon:
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import SwiftUI
|
||||
|
||||
struct ComposeAvatarImageView: View {
|
||||
let url: URL
|
||||
let url: URL?
|
||||
@State var request: ImageCache.Request? = nil
|
||||
@State var avatarImage: UIImage? = nil
|
||||
@ObservedObject var preferences = Preferences.shared
|
||||
|
@ -19,7 +19,9 @@ struct ComposeAvatarImageView: View {
|
|||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50)
|
||||
.onAppear(perform: self.loadImage)
|
||||
.conditionally(url != nil) {
|
||||
$0.onAppear(perform: self.loadImage)
|
||||
}
|
||||
.onDisappear(perform: self.cancelRequest)
|
||||
}
|
||||
|
||||
|
@ -27,6 +29,11 @@ struct ComposeAvatarImageView: View {
|
|||
if let avatarImage = avatarImage {
|
||||
return Image(uiImage: avatarImage)
|
||||
} else {
|
||||
return placeholderImage
|
||||
}
|
||||
}
|
||||
|
||||
private var placeholderImage: Image {
|
||||
let imageName: String
|
||||
switch preferences.avatarStyle {
|
||||
case .circle:
|
||||
|
@ -36,15 +43,19 @@ struct ComposeAvatarImageView: View {
|
|||
}
|
||||
return Image(systemName: imageName)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadImage() {
|
||||
guard let url = url else { return }
|
||||
request = ImageCache.avatars.get(url) { (data) in
|
||||
if let data = data, let image = UIImage(data: data) {
|
||||
DispatchQueue.main.async {
|
||||
self.request = nil
|
||||
if let data = data, let image = UIImage(data: data) {
|
||||
self.avatarImage = image
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.request = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,17 +12,19 @@ import Pachyderm
|
|||
struct ComposeCurrentAccount: View {
|
||||
@EnvironmentObject var mastodonController: MastodonController
|
||||
|
||||
var account: Account {
|
||||
mastodonController.account!
|
||||
var account: Account? {
|
||||
mastodonController.account
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top) {
|
||||
ComposeAvatarImageView(url: account.avatar)
|
||||
.accessibility(label: Text("\(account.displayName) avatar"))
|
||||
ComposeAvatarImageView(url: account?.avatar)
|
||||
.accessibility(label: Text(account != nil ? "\(account!.displayName) avatar" : "Avatar"))
|
||||
|
||||
if let id = account?.id,
|
||||
let account = mastodonController.persistentContainer.account(for: id) {
|
||||
VStack(alignment: .leading) {
|
||||
AccountDisplayNameLabel(account: mastodonController.persistentContainer.account(for: account.id)!, fontSize: 20)
|
||||
AccountDisplayNameLabel(account: account, fontSize: 20)
|
||||
.lineLimit(1)
|
||||
|
||||
Text(verbatim: "@\(account.acct)")
|
||||
|
@ -31,6 +33,9 @@ struct ComposeCurrentAccount: View {
|
|||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,15 +58,11 @@ class ComposeDrawingViewController: UIViewController {
|
|||
canvasView.drawing = initialDrawing
|
||||
}
|
||||
canvasView.delegate = self
|
||||
#if SDK_IOS_14
|
||||
if #available(iOS 14.0, *) {
|
||||
canvasView.drawingPolicy = .anyInput
|
||||
} else {
|
||||
canvasView.allowsFingerDrawing = true
|
||||
}
|
||||
#else
|
||||
canvasView.allowsFingerDrawing = true
|
||||
#endif
|
||||
canvasView.minimumZoomScale = 0.5
|
||||
canvasView.maximumZoomScale = 2
|
||||
canvasView.backgroundColor = .systemBackground
|
||||
|
|
|
@ -27,13 +27,7 @@ struct ComposeTextView: View {
|
|||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
WrappedTextView(
|
||||
text: $text,
|
||||
textDidChange: self.textDidChange,
|
||||
backgroundColor: backgroundColor,
|
||||
font: .systemFont(ofSize: fontSize)
|
||||
)
|
||||
.frame(height: height ?? minHeight)
|
||||
Color(backgroundColor)
|
||||
|
||||
if text.isEmpty, let placeholder = placeholder {
|
||||
placeholder
|
||||
|
@ -41,6 +35,13 @@ struct ComposeTextView: View {
|
|||
.foregroundColor(.secondary)
|
||||
.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
|
||||
var textDidChange: ((UITextView) -> Void)?
|
||||
var backgroundColor = UIColor.secondarySystemBackground
|
||||
var font = UIFont.systemFont(ofSize: 20)
|
||||
|
||||
func makeUIView(context: Context) -> UITextView {
|
||||
let textView = UITextView()
|
||||
textView.delegate = context.coordinator
|
||||
textView.isEditable = true
|
||||
textView.backgroundColor = backgroundColor
|
||||
textView.backgroundColor = .clear
|
||||
textView.font = font
|
||||
textView.textContainer.lineBreakMode = .byWordWrapping
|
||||
return textView
|
||||
|
|
|
@ -28,7 +28,7 @@ struct ComposeView: View {
|
|||
}
|
||||
|
||||
var charactersRemaining: Int {
|
||||
let limit = mastodonController.instance.maxStatusCharacters ?? 500
|
||||
let limit = mastodonController.instance?.maxStatusCharacters ?? 500
|
||||
let cwCount = draft.contentWarningEnabled ? draft.contentWarning.count : 0
|
||||
return limit - (cwCount + CharacterCounter.count(text: draft.text))
|
||||
}
|
||||
|
|
|
@ -20,6 +20,15 @@ struct MainComposeTextView: View {
|
|||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
Color(UIColor.secondarySystemBackground)
|
||||
|
||||
if draft.text.isEmpty {
|
||||
placeholder
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(.secondary)
|
||||
.offset(x: 4, y: 8)
|
||||
}
|
||||
|
||||
MainComposeWrappedTextView(
|
||||
text: $draft.text,
|
||||
visibility: draft.visibility,
|
||||
|
@ -28,13 +37,6 @@ struct MainComposeTextView: View {
|
|||
self.height = max(textView.contentSize.height, minHeight)
|
||||
}
|
||||
.frame(height: height ?? minHeight)
|
||||
|
||||
if draft.text.isEmpty {
|
||||
placeholder
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(.secondary)
|
||||
.offset(x: 4, y: 8)
|
||||
}
|
||||
}.onAppear {
|
||||
if !hasFirstAppeared {
|
||||
hasFirstAppeared = true
|
||||
|
@ -59,7 +61,7 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
|
|||
let textView = UITextView()
|
||||
textView.delegate = context.coordinator
|
||||
textView.isEditable = true
|
||||
textView.backgroundColor = .secondarySystemBackground
|
||||
textView.backgroundColor = .clear
|
||||
textView.font = .systemFont(ofSize: 20)
|
||||
textView.textContainer.lineBreakMode = .byWordWrapping
|
||||
context.coordinator.textView = textView
|
||||
|
|
|
@ -52,6 +52,8 @@ class ConversationTableViewController: EnhancedTableViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
title = NSLocalizedString("Conversation", comment: "conversation screen title")
|
||||
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
|
||||
|
|
|
@ -20,9 +20,7 @@ class LargeImageInteractionController: UIPercentDrivenInteractiveTransition {
|
|||
super.init()
|
||||
self.viewController = viewController
|
||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:)))
|
||||
if #available(iOS 13.4, *) {
|
||||
panRecognizer.allowedScrollTypesMask = .all
|
||||
}
|
||||
viewController.view.addGestureRecognizer(panRecognizer)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
#if SDK_IOS_14
|
||||
@available(iOS 14.0, *)
|
||||
protocol MainSidebarViewControllerDelegate: class {
|
||||
func sidebarRequestPresentCompose(_ sidebarViewController: MainSidebarViewController)
|
||||
|
@ -380,4 +379,3 @@ extension MainSidebarViewController: InstanceTimelineViewControllerDelegate {
|
|||
dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
#if SDK_IOS_14
|
||||
@available(iOS 14.0, *)
|
||||
class MainSplitViewController: UISplitViewController {
|
||||
|
||||
|
@ -316,11 +315,15 @@ extension MainSplitViewController: TuskerRootViewController {
|
|||
}
|
||||
|
||||
func select(tab: MainTabBarViewController.Tab) {
|
||||
if traitCollection.horizontalSizeClass == .compact {
|
||||
tabBarViewController?.select(tab: tab)
|
||||
} else {
|
||||
if tab == .compose {
|
||||
presentCompose()
|
||||
} else {
|
||||
select(item: .tab(tab))
|
||||
sidebar.select(item: .tab(tab), animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -36,12 +36,10 @@ class InteractivePushTransition: UIPercentDrivenInteractiveTransition {
|
|||
interactivePushGestureRecognizer.require(toFail: navigationController.interactivePopGestureRecognizer!)
|
||||
navigationController.view.addGestureRecognizer(interactivePushGestureRecognizer)
|
||||
|
||||
if #available(iOS 13.4, *) {
|
||||
let trackpadGestureRecognizer = TrackpadScrollGestureRecognizer(target: self, action: #selector(handleSwipeForward(_:)))
|
||||
trackpadGestureRecognizer.require(toFail: navigationController.interactivePopGestureRecognizer!)
|
||||
navigationController.view.addGestureRecognizer(trackpadGestureRecognizer)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func handleSwipeForward(_ recognizer: UIPanGestureRecognizer) {
|
||||
interactive = true
|
||||
|
|
|
@ -54,8 +54,6 @@ extension MenuPreviewProvider {
|
|||
}),
|
||||
]
|
||||
|
||||
// todo: handle pre-iOS 14
|
||||
#if SDK_IOS_14
|
||||
if accountID != mastodonController.account.id,
|
||||
#available(iOS 14.0, *) {
|
||||
actionsSection.append(UIDeferredMenuElement({ (elementHandler) in
|
||||
|
@ -71,7 +69,7 @@ extension MenuPreviewProvider {
|
|||
let following = relationship.following
|
||||
DispatchQueue.main.async {
|
||||
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)
|
||||
mastodonController.run(request) { (_) in
|
||||
}
|
||||
|
@ -82,7 +80,6 @@ extension MenuPreviewProvider {
|
|||
}
|
||||
}))
|
||||
}
|
||||
#endif
|
||||
|
||||
let shareSection = [
|
||||
openInSafariAction(url: account.url),
|
||||
|
|
|
@ -47,9 +47,10 @@ enum AppShortcutItem: String, CaseIterable {
|
|||
}
|
||||
let scene = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first!
|
||||
let window = scene.windows.first { $0.isKeyWindow }!
|
||||
let controller = window.rootViewController as! MainTabBarViewController
|
||||
if let controller = window.rootViewController as? TuskerRootViewController {
|
||||
controller.select(tab: tab)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AppShortcutItem {
|
||||
|
|
|
@ -58,11 +58,11 @@ class AttachmentsContainerView: UIView {
|
|||
attachmentViews.removeAllObjects()
|
||||
moreView?.removeFromSuperview()
|
||||
|
||||
var accessibilityElements = [Any]()
|
||||
|
||||
if attachments.count > 0 {
|
||||
self.isHidden = false
|
||||
|
||||
var accessibilityElements = [Any]()
|
||||
|
||||
switch attachments.count {
|
||||
case 1:
|
||||
let attachmentView = createAttachmentView(index: 0, hSize: .full, vSize: .full)
|
||||
|
@ -215,13 +215,16 @@ class AttachmentsContainerView: UIView {
|
|||
accessibilityElements.append(topRight)
|
||||
accessibilityElements.append(bottomLeft)
|
||||
accessibilityElements.append(moreView)
|
||||
}
|
||||
|
||||
self.accessibilityElements = accessibilityElements
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -67,15 +67,11 @@ class ProfileHeaderView: UIView {
|
|||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
|
||||
if #available(iOS 13.4, *) {
|
||||
moreButton.addInteraction(UIPointerInteraction(delegate: self))
|
||||
}
|
||||
#if SDK_IOS_14
|
||||
if #available(iOS 14.0, *) {
|
||||
moreButton.showsMenuAsPrimaryAction = true
|
||||
moreButton.isContextMenuInteractionEnabled = true
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func updateUI(for accountID: String) {
|
||||
|
|
|
@ -87,11 +87,9 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!]
|
||||
attachmentsView.isAccessibilityElement = true
|
||||
|
||||
#if SDK_IOS_14
|
||||
if #available(iOS 14.0, *) {
|
||||
moreButton.showsMenuAsPrimaryAction = true
|
||||
}
|
||||
#endif
|
||||
|
||||
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()
|
||||
|
||||
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
|
||||
fatalError("Missing cached status")
|
||||
}
|
||||
|
||||
self.statusID = statusID
|
||||
|
||||
doUpdateUI(status: status, state: state)
|
||||
}
|
||||
|
||||
func doUpdateUI(status: StatusMO, state: StatusState) {
|
||||
self.statusState = state
|
||||
|
||||
let account = status.account
|
||||
self.accountID = account.id
|
||||
updateUI(account: account)
|
||||
updateUIForPreferences(account: account)
|
||||
updateUIForPreferences(account: account, status: status)
|
||||
|
||||
attachmentsView.updateUI(status: status)
|
||||
attachmentsView.isAccessibilityElement = status.attachments.count > 0
|
||||
|
@ -194,12 +198,10 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
reblogButton.accessibilityLabel = NSLocalizedString("Reblog", comment: "reblog button accessibility label")
|
||||
}
|
||||
|
||||
#if SDK_IOS_14
|
||||
if #available(iOS 14.0, *) {
|
||||
// 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))
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func updateUI(account: AccountMO) {
|
||||
|
@ -213,18 +215,19 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
}
|
||||
}
|
||||
|
||||
@objc func preferencesChanged() {
|
||||
@objc private func preferencesChanged() {
|
||||
guard let mastodonController = mastodonController,
|
||||
let account = mastodonController.persistentContainer.account(for: accountID),
|
||||
let status = mastodonController.persistentContainer.status(for: statusID) else { return }
|
||||
updateUIForPreferences(account: account)
|
||||
updateStatusIconsForPreferences(status)
|
||||
updateUIForPreferences(account: account, status: status)
|
||||
}
|
||||
|
||||
func updateUIForPreferences(account: AccountMO) {
|
||||
func updateUIForPreferences(account: AccountMO, status: StatusMO) {
|
||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||
displayNameLabel.updateForAccountDisplayName(account: account)
|
||||
attachmentsView.contentHidden = Preferences.shared.blurAllMedia || (mastodonController.persistentContainer.status(for: statusID)?.sensitive ?? false)
|
||||
|
||||
updateStatusIconsForPreferences(status)
|
||||
}
|
||||
|
||||
func updateStatusIconsForPreferences(_ status: StatusMO) {
|
||||
|
|
|
@ -38,9 +38,8 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
|||
contentTextView.defaultFont = .systemFont(ofSize: 18)
|
||||
}
|
||||
|
||||
override func updateUI(statusID: String, state: StatusState) {
|
||||
super.updateUI(statusID: statusID, state: state)
|
||||
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError() }
|
||||
override func doUpdateUI(status: StatusMO, state: StatusState) {
|
||||
super.doUpdateUI(status: status, state: state)
|
||||
|
||||
var timestampAndClientText = ConversationMainStatusTableViewCell.dateFormatter.string(from: status.createdAt)
|
||||
if let application = status.applicationName {
|
||||
|
@ -63,8 +62,8 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
|||
profileAccessibilityElement.accessibilityLabel = account.displayNameWithoutCustomEmoji
|
||||
}
|
||||
|
||||
override func updateUIForPreferences(account: AccountMO) {
|
||||
super.updateUIForPreferences(account: account)
|
||||
override func updateUIForPreferences(account: AccountMO, status: StatusMO) {
|
||||
super.updateUIForPreferences(account: account, status: status)
|
||||
|
||||
favoriteAndReblogCountStackView.isHidden = !Preferences.shared.showFavoriteAndReblogCounts
|
||||
}
|
||||
|
|
|
@ -68,10 +68,9 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
}
|
||||
}
|
||||
|
||||
override func updateUI(statusID: String, state: StatusState) {
|
||||
guard var status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
||||
override func doUpdateUI(status: StatusMO, state: StatusState) {
|
||||
var status = status
|
||||
|
||||
let realStatusID: String
|
||||
if let rebloggedStatus = status.reblog {
|
||||
reblogStatusID = statusID
|
||||
rebloggerID = status.account.id
|
||||
|
@ -79,25 +78,24 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
updateRebloggerLabel(reblogger: status.account)
|
||||
|
||||
status = rebloggedStatus
|
||||
realStatusID = rebloggedStatus.id
|
||||
statusID = rebloggedStatus.id
|
||||
} else {
|
||||
reblogStatusID = nil
|
||||
rebloggerID = nil
|
||||
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)
|
||||
timestampLabel.isHidden = pinned
|
||||
pinImageView.isHidden = !pinned
|
||||
}
|
||||
|
||||
@objc override func preferencesChanged() {
|
||||
super.preferencesChanged()
|
||||
override func updateUIForPreferences(account: AccountMO, status: StatusMO) {
|
||||
super.updateUIForPreferences(account: account, status: status)
|
||||
|
||||
if let rebloggerID = rebloggerID,
|
||||
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
|
||||
|
@ -121,12 +119,16 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
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
|
||||
// so we bail out immediately, since there's nothing to update
|
||||
guard let mastodonController = mastodonController else { return }
|
||||
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.accessibilityLabel = TimelineStatusTableViewCell.relativeDateFormatter.localizedString(for: status.createdAt, relativeTo: Date())
|
||||
|
||||
|
|
|
@ -52,9 +52,7 @@ class VisualEffectImageButton: UIControl {
|
|||
imageView.bottomAnchor.constraint(equalTo: vibrancyView.bottomAnchor, constant: -2),
|
||||
])
|
||||
|
||||
#if SDK_IOS_14
|
||||
addInteraction(UIContextMenuInteraction(delegate: self))
|
||||
#endif
|
||||
|
||||
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
|
||||
}
|
||||
|
@ -63,12 +61,10 @@ class VisualEffectImageButton: UIControl {
|
|||
sendActions(for: .touchUpInside)
|
||||
}
|
||||
|
||||
#if SDK_IOS_14
|
||||
override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
||||
guard let menu = menu else { return nil }
|
||||
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { (_) -> UIMenu? in
|
||||
return menu
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -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>]
|
||||
|
||||
var startIndex: Int { array.startIndex }
|
||||
var endIndex: Int { array.endIndex }
|
||||
|
||||
init() {
|
||||
array = []
|
||||
}
|
||||
|
||||
init(_ elements: [Element]) {
|
||||
array = elements.map { WeakWrapper($0) }
|
||||
}
|
||||
|
@ -30,11 +34,20 @@ struct WeakArray<Element: AnyObject>: Collection {
|
|||
array = elements.map { WeakWrapper($0) }
|
||||
}
|
||||
|
||||
subscript(_ index: Int) -> Element? {
|
||||
return array[index].value
|
||||
subscript(position: Int) -> Element? {
|
||||
get {
|
||||
array[position].value
|
||||
}
|
||||
set(newValue) {
|
||||
array[position] = WeakWrapper(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
func index(after i: Int) -> Int {
|
||||
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) })
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue