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 */; };
|
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 */,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -15,6 +15,12 @@ class ImageCache {
|
||||||
static let headers = ImageCache(name: "Headers", memoryExpiry: .seconds(60 * 5), diskExpiry: .seconds(60 * 60))
|
static let headers = ImageCache(name: "Headers", memoryExpiry: .seconds(60 * 5), diskExpiry: .seconds(60 * 60))
|
||||||
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>
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
|
|
@ -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 {}
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
Text(verbatim: "@\(account.acct)")
|
.lineLimit(1)
|
||||||
.font(.system(size: 17, weight: .light))
|
|
||||||
.foregroundColor(.secondary)
|
Text(verbatim: "@\(account.acct)")
|
||||||
.lineLimit(1)
|
.font(.system(size: 17, weight: .light))
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -51,6 +51,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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,12 +57,12 @@ class AttachmentsContainerView: UIView {
|
||||||
attachmentViews.allObjects.forEach { $0.removeFromSuperview() }
|
attachmentViews.allObjects.forEach { $0.removeFromSuperview() }
|
||||||
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,12 +215,15 @@ 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -38,10 +38,9 @@ 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 {
|
||||||
timestampAndClientText += " • \(application)"
|
timestampAndClientText += " • \(application)"
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue