Compare commits
4 Commits
a901af6be9
...
1f40cc9928
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 1f40cc9928 | |
Shadowfacts | 66020b7847 | |
Shadowfacts | 00bf99334f | |
Shadowfacts | 3aef7d4d93 |
|
@ -168,6 +168,7 @@
|
||||||
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */; };
|
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 */; };
|
||||||
D681E4D3246E2AFF0053414F /* MuteConversationActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681E4D2246E2AFF0053414F /* MuteConversationActivity.swift */; };
|
D681E4D3246E2AFF0053414F /* MuteConversationActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681E4D2246E2AFF0053414F /* MuteConversationActivity.swift */; };
|
||||||
D681E4D5246E2BC30053414F /* UnmuteConversationActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681E4D4246E2BC30053414F /* UnmuteConversationActivity.swift */; };
|
D681E4D5246E2BC30053414F /* UnmuteConversationActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681E4D4246E2BC30053414F /* UnmuteConversationActivity.swift */; };
|
||||||
D681E4D7246E32290053414F /* StatusActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681E4D6246E32290053414F /* StatusActivityItemSource.swift */; };
|
D681E4D7246E32290053414F /* StatusActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681E4D6246E32290053414F /* StatusActivityItemSource.swift */; };
|
||||||
|
@ -471,6 +472,7 @@
|
||||||
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; 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>"; };
|
||||||
D681E4D2246E2AFF0053414F /* MuteConversationActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuteConversationActivity.swift; sourceTree = "<group>"; };
|
D681E4D2246E2AFF0053414F /* MuteConversationActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuteConversationActivity.swift; sourceTree = "<group>"; };
|
||||||
D681E4D4246E2BC30053414F /* UnmuteConversationActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnmuteConversationActivity.swift; sourceTree = "<group>"; };
|
D681E4D4246E2BC30053414F /* UnmuteConversationActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnmuteConversationActivity.swift; sourceTree = "<group>"; };
|
||||||
D681E4D6246E32290053414F /* StatusActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActivityItemSource.swift; sourceTree = "<group>"; };
|
D681E4D6246E32290053414F /* StatusActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActivityItemSource.swift; sourceTree = "<group>"; };
|
||||||
|
@ -942,6 +944,7 @@
|
||||||
D646C954213B364600269FB5 /* Transitions */,
|
D646C954213B364600269FB5 /* Transitions */,
|
||||||
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */,
|
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */,
|
||||||
D6C94D862139E62700CB5196 /* LargeImageViewController.swift */,
|
D6C94D862139E62700CB5196 /* LargeImageViewController.swift */,
|
||||||
|
D681A299249AD62D0085E54E /* LargeImageContentView.swift */,
|
||||||
041160FE22B442870030A9B7 /* LoadingLargeImageViewController.swift */,
|
041160FE22B442870030A9B7 /* LoadingLargeImageViewController.swift */,
|
||||||
);
|
);
|
||||||
path = "Large Image";
|
path = "Large Image";
|
||||||
|
@ -1770,6 +1773,7 @@
|
||||||
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
|
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
|
||||||
D67895C0246870DE00D4CD9E /* LocalAccountAvatarView.swift in Sources */,
|
D67895C0246870DE00D4CD9E /* LocalAccountAvatarView.swift in Sources */,
|
||||||
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,
|
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,
|
||||||
|
D681A29A249AD62D0085E54E /* LargeImageContentView.swift in Sources */,
|
||||||
D6DFC69E242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift in Sources */,
|
D6DFC69E242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift in Sources */,
|
||||||
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */,
|
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */,
|
||||||
D63F9C68241C4F79004C03CF /* AddAttachmentTableViewCell.swift in Sources */,
|
D63F9C68241C4F79004C03CF /* AddAttachmentTableViewCell.swift in Sources */,
|
||||||
|
|
|
@ -41,14 +41,18 @@ class Preferences: Codable, ObservableObject {
|
||||||
self.showRepliesInProfiles = try container.decode(Bool.self, forKey: .showRepliesInProfiles)
|
self.showRepliesInProfiles = try container.decode(Bool.self, forKey: .showRepliesInProfiles)
|
||||||
self.avatarStyle = try container.decode(AvatarStyle.self, forKey: .avatarStyle)
|
self.avatarStyle = try container.decode(AvatarStyle.self, forKey: .avatarStyle)
|
||||||
self.hideCustomEmojiInUsernames = try container.decode(Bool.self, forKey: .hideCustomEmojiInUsernames)
|
self.hideCustomEmojiInUsernames = try container.decode(Bool.self, forKey: .hideCustomEmojiInUsernames)
|
||||||
|
self.showIsStatusReplyIcon = try container.decode(Bool.self, forKey: .showIsStatusReplyIcon)
|
||||||
|
self.alwaysShowStatusVisibilityIcon = try container.decode(Bool.self, forKey: .alwaysShowStatusVisibilityIcon)
|
||||||
|
|
||||||
self.defaultPostVisibility = try container.decode(Status.Visibility.self, forKey: .defaultPostVisibility)
|
self.defaultPostVisibility = try container.decode(Status.Visibility.self, forKey: .defaultPostVisibility)
|
||||||
self.automaticallySaveDrafts = try container.decode(Bool.self, forKey: .automaticallySaveDrafts)
|
self.automaticallySaveDrafts = try container.decode(Bool.self, forKey: .automaticallySaveDrafts)
|
||||||
self.requireAttachmentDescriptions = try container.decode(Bool.self, forKey: .requireAttachmentDescriptions)
|
self.requireAttachmentDescriptions = try container.decode(Bool.self, forKey: .requireAttachmentDescriptions)
|
||||||
self.contentWarningCopyMode = try container.decode(ContentWarningCopyMode.self, forKey: .contentWarningCopyMode)
|
self.contentWarningCopyMode = try container.decode(ContentWarningCopyMode.self, forKey: .contentWarningCopyMode)
|
||||||
self.mentionReblogger = try container.decode(Bool.self, forKey: .mentionReblogger)
|
self.mentionReblogger = try container.decode(Bool.self, forKey: .mentionReblogger)
|
||||||
|
|
||||||
self.blurAllMedia = try container.decode(Bool.self, forKey: .blurAllMedia)
|
self.blurAllMedia = try container.decode(Bool.self, forKey: .blurAllMedia)
|
||||||
self.automaticallyPlayGifs = try container.decode(Bool.self, forKey: .automaticallyPlayGifs)
|
self.automaticallyPlayGifs = try container.decode(Bool.self, forKey: .automaticallyPlayGifs)
|
||||||
|
|
||||||
self.openLinksInApps = try container.decode(Bool.self, forKey: .openLinksInApps)
|
self.openLinksInApps = try container.decode(Bool.self, forKey: .openLinksInApps)
|
||||||
self.useInAppSafari = try container.decode(Bool.self, forKey: .useInAppSafari)
|
self.useInAppSafari = try container.decode(Bool.self, forKey: .useInAppSafari)
|
||||||
self.inAppSafariAutomaticReaderMode = try container.decode(Bool.self, forKey: .inAppSafariAutomaticReaderMode)
|
self.inAppSafariAutomaticReaderMode = try container.decode(Bool.self, forKey: .inAppSafariAutomaticReaderMode)
|
||||||
|
@ -67,14 +71,18 @@ class Preferences: Codable, ObservableObject {
|
||||||
try container.encode(showRepliesInProfiles, forKey: .showRepliesInProfiles)
|
try container.encode(showRepliesInProfiles, forKey: .showRepliesInProfiles)
|
||||||
try container.encode(avatarStyle, forKey: .avatarStyle)
|
try container.encode(avatarStyle, forKey: .avatarStyle)
|
||||||
try container.encode(hideCustomEmojiInUsernames, forKey: .hideCustomEmojiInUsernames)
|
try container.encode(hideCustomEmojiInUsernames, forKey: .hideCustomEmojiInUsernames)
|
||||||
|
try container.encode(showIsStatusReplyIcon, forKey: .showIsStatusReplyIcon)
|
||||||
|
try container.encode(alwaysShowStatusVisibilityIcon, forKey: .alwaysShowStatusVisibilityIcon)
|
||||||
|
|
||||||
try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility)
|
try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility)
|
||||||
try container.encode(automaticallySaveDrafts, forKey: .automaticallySaveDrafts)
|
try container.encode(automaticallySaveDrafts, forKey: .automaticallySaveDrafts)
|
||||||
try container.encode(requireAttachmentDescriptions, forKey: .requireAttachmentDescriptions)
|
try container.encode(requireAttachmentDescriptions, forKey: .requireAttachmentDescriptions)
|
||||||
try container.encode(contentWarningCopyMode, forKey: .contentWarningCopyMode)
|
try container.encode(contentWarningCopyMode, forKey: .contentWarningCopyMode)
|
||||||
try container.encode(mentionReblogger, forKey: .mentionReblogger)
|
try container.encode(mentionReblogger, forKey: .mentionReblogger)
|
||||||
|
|
||||||
try container.encode(blurAllMedia, forKey: .blurAllMedia)
|
try container.encode(blurAllMedia, forKey: .blurAllMedia)
|
||||||
try container.encode(automaticallyPlayGifs, forKey: .automaticallyPlayGifs)
|
try container.encode(automaticallyPlayGifs, forKey: .automaticallyPlayGifs)
|
||||||
|
|
||||||
try container.encode(openLinksInApps, forKey: .openLinksInApps)
|
try container.encode(openLinksInApps, forKey: .openLinksInApps)
|
||||||
try container.encode(useInAppSafari, forKey: .useInAppSafari)
|
try container.encode(useInAppSafari, forKey: .useInAppSafari)
|
||||||
try container.encode(inAppSafariAutomaticReaderMode, forKey: .inAppSafariAutomaticReaderMode)
|
try container.encode(inAppSafariAutomaticReaderMode, forKey: .inAppSafariAutomaticReaderMode)
|
||||||
|
@ -86,29 +94,35 @@ class Preferences: Codable, ObservableObject {
|
||||||
try container.encode(statusContentType, forKey: .statusContentType)
|
try container.encode(statusContentType, forKey: .statusContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Appearance
|
// MARK: Appearance
|
||||||
@Published var theme = UIUserInterfaceStyle.unspecified
|
@Published var theme = UIUserInterfaceStyle.unspecified
|
||||||
@Published var showRepliesInProfiles = false
|
@Published var showRepliesInProfiles = false
|
||||||
@Published var avatarStyle = AvatarStyle.roundRect
|
@Published var avatarStyle = AvatarStyle.roundRect
|
||||||
@Published var hideCustomEmojiInUsernames = false
|
@Published var hideCustomEmojiInUsernames = false
|
||||||
|
@Published var showIsStatusReplyIcon = false
|
||||||
|
@Published var alwaysShowStatusVisibilityIcon = false
|
||||||
|
|
||||||
// MARK: - Behavior
|
// MARK: Composing
|
||||||
@Published var defaultPostVisibility = Status.Visibility.public
|
@Published var defaultPostVisibility = Status.Visibility.public
|
||||||
@Published var automaticallySaveDrafts = true
|
@Published var automaticallySaveDrafts = true
|
||||||
@Published var requireAttachmentDescriptions = false
|
@Published var requireAttachmentDescriptions = false
|
||||||
@Published var contentWarningCopyMode = ContentWarningCopyMode.asIs
|
@Published var contentWarningCopyMode = ContentWarningCopyMode.asIs
|
||||||
@Published var mentionReblogger = false
|
@Published var mentionReblogger = false
|
||||||
|
|
||||||
|
// MARK: Media
|
||||||
@Published var blurAllMedia = false
|
@Published var blurAllMedia = false
|
||||||
@Published var automaticallyPlayGifs = true
|
@Published var automaticallyPlayGifs = true
|
||||||
|
|
||||||
|
// MARK: Behavior
|
||||||
@Published var openLinksInApps = true
|
@Published var openLinksInApps = true
|
||||||
@Published var useInAppSafari = true
|
@Published var useInAppSafari = true
|
||||||
@Published var inAppSafariAutomaticReaderMode = false
|
@Published var inAppSafariAutomaticReaderMode = false
|
||||||
|
|
||||||
// MARK: - Digital Wellness
|
// MARK: Digital Wellness
|
||||||
@Published var showFavoriteAndReblogCounts = true
|
@Published var showFavoriteAndReblogCounts = true
|
||||||
@Published var defaultNotificationsMode = NotificationsMode.allNotifications
|
@Published var defaultNotificationsMode = NotificationsMode.allNotifications
|
||||||
|
|
||||||
// MARK: - Advanced
|
// MARK: Advanced
|
||||||
@Published var silentActions: [String: Permission] = [:]
|
@Published var silentActions: [String: Permission] = [:]
|
||||||
@Published var statusContentType: StatusContentType = .plain
|
@Published var statusContentType: StatusContentType = .plain
|
||||||
|
|
||||||
|
@ -117,14 +131,18 @@ class Preferences: Codable, ObservableObject {
|
||||||
case showRepliesInProfiles
|
case showRepliesInProfiles
|
||||||
case avatarStyle
|
case avatarStyle
|
||||||
case hideCustomEmojiInUsernames
|
case hideCustomEmojiInUsernames
|
||||||
|
case showIsStatusReplyIcon
|
||||||
|
case alwaysShowStatusVisibilityIcon
|
||||||
|
|
||||||
case defaultPostVisibility
|
case defaultPostVisibility
|
||||||
case automaticallySaveDrafts
|
case automaticallySaveDrafts
|
||||||
case requireAttachmentDescriptions
|
case requireAttachmentDescriptions
|
||||||
case contentWarningCopyMode
|
case contentWarningCopyMode
|
||||||
case mentionReblogger
|
case mentionReblogger
|
||||||
|
|
||||||
case blurAllMedia
|
case blurAllMedia
|
||||||
case automaticallyPlayGifs
|
case automaticallyPlayGifs
|
||||||
|
|
||||||
case openLinksInApps
|
case openLinksInApps
|
||||||
case useInAppSafari
|
case useInAppSafari
|
||||||
case inAppSafariAutomaticReaderMode
|
case inAppSafariAutomaticReaderMode
|
||||||
|
|
|
@ -28,8 +28,8 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
||||||
|
|
||||||
var animationSourceView: UIImageView? { sourceViews[currentIndex] }
|
var animationSourceView: UIImageView? { sourceViews[currentIndex] }
|
||||||
var animationImage: UIImage? {
|
var animationImage: UIImage? {
|
||||||
if let page = pages[currentIndex] as? LoadingLargeImageViewController,
|
if let page = pages[currentIndex] as? LargeImageAnimatableViewController,
|
||||||
let image = page.largeImageVC?.image {
|
let image = page.animationImage {
|
||||||
return image
|
return image
|
||||||
} else {
|
} else {
|
||||||
return animationSourceView?.image
|
return animationSourceView?.image
|
||||||
|
@ -65,18 +65,29 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
||||||
self.sourceViews = WeakArray(sourceViews)
|
self.sourceViews = WeakArray(sourceViews)
|
||||||
self.startIndex = startIndex
|
self.startIndex = startIndex
|
||||||
|
|
||||||
self.pages = attachments.map {
|
self.pages = attachments.enumerated().map { (index, attachment) in
|
||||||
switch $0.kind {
|
switch attachment.kind {
|
||||||
case .image:
|
case .image:
|
||||||
let vc = LoadingLargeImageViewController(attachment: $0)
|
let vc = LoadingLargeImageViewController(attachment: attachment)
|
||||||
vc.shrinkGestureEnabled = false
|
vc.shrinkGestureEnabled = false
|
||||||
return vc
|
return vc
|
||||||
case .video, .audio:
|
case .video, .audio:
|
||||||
let vc = AVPlayerViewController()
|
let vc = AVPlayerViewController()
|
||||||
vc.player = AVPlayer(url: $0.url)
|
vc.player = AVPlayer(url: attachment.url)
|
||||||
return vc
|
return vc
|
||||||
case .gifv:
|
case .gifv:
|
||||||
return GifvAttachmentViewController(attachment: $0)
|
// Passing the source view to the LargeImageGifvContentView is a crappy workaround for not
|
||||||
|
// having the video size directly inside the content view. This will break when there
|
||||||
|
// are more than 4 attachments and there is a gifv at index >= 3 (the More... button will show
|
||||||
|
// in place of the fourth attachment, so there aren't source views for the attachments at index >= 3).
|
||||||
|
// Really, what should happen is the LargeImageGifvContentView should get the size of the video from
|
||||||
|
// the AVFoundation instead of the source view.
|
||||||
|
// This isn't a priority as only Mastodon converts gifs to gifvs, and Mastodon (in its default configuration,
|
||||||
|
// I don't know about forks) doesn't allow more than four attachments, meaning there will always be a source view.
|
||||||
|
let gifvContentView = LargeImageGifvContentView(attachment: attachment, source: sourceViews[index]!)
|
||||||
|
let vc = LargeImageViewController(contentView: gifvContentView, description: attachment.description, sourceView: nil)
|
||||||
|
vc.shrinkGestureEnabled = false
|
||||||
|
return vc
|
||||||
default:
|
default:
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
//
|
||||||
|
// LargeImageContentView.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/17/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Gifu
|
||||||
|
import Pachyderm
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
protocol LargeImageContentView {
|
||||||
|
var animationImage: UIImage? { get }
|
||||||
|
var animationGifData: Data? { get }
|
||||||
|
var activityItemsForSharing: [Any] { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
class LargeImageImageContentView: UIImageView, GIFAnimatable, LargeImageContentView {
|
||||||
|
lazy var animator: Animator? = {
|
||||||
|
return Animator(withDelegate: self)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var animationImage: UIImage? { image! }
|
||||||
|
let animationGifData: Data?
|
||||||
|
|
||||||
|
var activityItemsForSharing: [Any] {
|
||||||
|
[image!]
|
||||||
|
}
|
||||||
|
|
||||||
|
init(image: UIImage, gifData: Data?) {
|
||||||
|
self.animationGifData = gifData
|
||||||
|
|
||||||
|
super.init(image: image)
|
||||||
|
|
||||||
|
contentMode = .scaleAspectFit
|
||||||
|
|
||||||
|
if let data = gifData {
|
||||||
|
self.animate(withGIFData: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func display(_ layer: CALayer) {
|
||||||
|
updateImageIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LargeImageGifvContentView: GifvAttachmentView, LargeImageContentView {
|
||||||
|
private(set) var animationImage: UIImage?
|
||||||
|
var animationGifData: Data? { nil }
|
||||||
|
var activityItemsForSharing: [Any] {
|
||||||
|
// todo: what should we share for gifvs?
|
||||||
|
// some SO posts indicate that just sharing a URL to the video should work, but that may need to be a local URL?
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
|
||||||
|
private let asset: AVURLAsset
|
||||||
|
|
||||||
|
// The content view needs to supply an intrinsicContentSize for the LargeImageViewController to handle layout/scrolling/zooming correctly
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
// This is a really sucky workaround for the fact that in the content view, we don't have access to the size of the underlying video.
|
||||||
|
// There's probably some way of getting this from the AVPlayer/AVAsset directly
|
||||||
|
animationImage?.size ?? CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(attachment: Attachment, source: UIImageView) {
|
||||||
|
precondition(attachment.kind == .gifv)
|
||||||
|
|
||||||
|
self.asset = AVURLAsset(url: attachment.url)
|
||||||
|
|
||||||
|
super.init(asset: asset, gravity: .resizeAspect)
|
||||||
|
|
||||||
|
self.animationImage = source.image
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,22 +7,17 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Gifu
|
|
||||||
|
|
||||||
class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeImageAnimatableViewController {
|
class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeImageAnimatableViewController {
|
||||||
|
|
||||||
|
typealias ContentView = UIView & LargeImageContentView
|
||||||
|
|
||||||
weak var animationSourceView: UIImageView?
|
weak var animationSourceView: UIImageView?
|
||||||
var animationImage: UIImage? { image ?? animationSourceView?.image }
|
var animationImage: UIImage? { contentView.animationImage }
|
||||||
var animationGifData: Data? { gifData }
|
var animationGifData: Data? { contentView.animationGifData }
|
||||||
var dismissInteractionController: LargeImageInteractionController?
|
var dismissInteractionController: LargeImageInteractionController?
|
||||||
|
|
||||||
@IBOutlet weak var scrollView: UIScrollView!
|
@IBOutlet weak var scrollView: UIScrollView!
|
||||||
@IBOutlet weak var imageView: GIFImageView!
|
|
||||||
@IBOutlet weak var imageViewLeadingConstraint: NSLayoutConstraint!
|
|
||||||
@IBOutlet weak var imageViewTrailingConstraint: NSLayoutConstraint!
|
|
||||||
@IBOutlet weak var imageViewTopConstraint: NSLayoutConstraint!
|
|
||||||
@IBOutlet weak var imageViewBottomConstraint: NSLayoutConstraint!
|
|
||||||
|
|
||||||
@IBOutlet weak var topControlsView: UIView!
|
@IBOutlet weak var topControlsView: UIView!
|
||||||
@IBOutlet weak var topControlsHeightConstraint: NSLayoutConstraint!
|
@IBOutlet weak var topControlsHeightConstraint: NSLayoutConstraint!
|
||||||
@IBOutlet weak var shareButton: UIButton!
|
@IBOutlet weak var shareButton: UIButton!
|
||||||
|
@ -35,8 +30,10 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
@IBOutlet weak var bottomControlsView: UIView!
|
@IBOutlet weak var bottomControlsView: UIView!
|
||||||
@IBOutlet weak var descriptionLabel: UILabel!
|
@IBOutlet weak var descriptionLabel: UILabel!
|
||||||
|
|
||||||
var image: UIImage?
|
var contentView: ContentView
|
||||||
var gifData: Data?
|
var contentViewLeadingConstraint: NSLayoutConstraint!
|
||||||
|
var contentViewTopConstraint: NSLayoutConstraint!
|
||||||
|
|
||||||
var imageDescription: String?
|
var imageDescription: String?
|
||||||
|
|
||||||
var initialControlsVisible = true
|
var initialControlsVisible = true
|
||||||
|
@ -57,10 +54,11 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
return !controlsVisible
|
return !controlsVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
init(image: UIImage, description: String?, sourceView: UIImageView?) {
|
init(contentView: ContentView, description: String?, sourceView: UIImageView?) {
|
||||||
self.image = image
|
|
||||||
self.imageDescription = description
|
self.imageDescription = description
|
||||||
self.animationSourceView = sourceView
|
self.animationSourceView = sourceView
|
||||||
|
|
||||||
|
self.contentView = contentView
|
||||||
|
|
||||||
super.init(nibName: "LargeImageViewController", bundle: nil)
|
super.init(nibName: "LargeImageViewController", bundle: nil)
|
||||||
|
|
||||||
|
@ -74,16 +72,20 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
scrollView.addSubview(contentView)
|
||||||
|
contentViewLeadingConstraint = contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor)
|
||||||
|
contentViewTopConstraint = contentView.topAnchor.constraint(equalTo: scrollView.topAnchor)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
contentViewLeadingConstraint,
|
||||||
|
contentViewTopConstraint,
|
||||||
|
])
|
||||||
|
|
||||||
setControlsVisible(initialControlsVisible, animated: false)
|
setControlsVisible(initialControlsVisible, animated: false)
|
||||||
|
shareButton.isEnabled = !contentView.activityItemsForSharing.isEmpty
|
||||||
imageView.image = image
|
|
||||||
if let gifData = gifData {
|
|
||||||
imageView.animate(withGIFData: gifData)
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollView.delegate = self
|
scrollView.delegate = self
|
||||||
imageView.bounds = CGRect(origin: .zero, size: imageView.image!.size)
|
|
||||||
|
|
||||||
if let imageDescription = imageDescription {
|
if let imageDescription = imageDescription {
|
||||||
descriptionLabel.text = imageDescription
|
descriptionLabel.text = imageDescription
|
||||||
} else {
|
} else {
|
||||||
|
@ -99,23 +101,24 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
doubleTap.numberOfTapsRequired = 2
|
doubleTap.numberOfTapsRequired = 2
|
||||||
view.addGestureRecognizer(doubleTap)
|
view.addGestureRecognizer(doubleTap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
super.viewDidLayoutSubviews()
|
super.viewDidLayoutSubviews()
|
||||||
|
|
||||||
|
// todo: does this need to be in viewDidLayoutSubviews?
|
||||||
// limit the image height to the safe area height, so the image doesn't overlap the top controls
|
// limit the image height to the safe area height, so the image doesn't overlap the top controls
|
||||||
// while zoomed all the way out
|
// while zoomed all the way out
|
||||||
let maxHeight = view.bounds.height - view.safeAreaInsets.top - view.safeAreaInsets.bottom
|
let maxHeight = view.bounds.height - view.safeAreaInsets.top - view.safeAreaInsets.bottom
|
||||||
let heightScale = maxHeight / imageView.bounds.height
|
let heightScale = maxHeight / contentView.intrinsicContentSize.height
|
||||||
let widthScale = view.bounds.width / imageView.bounds.width
|
let widthScale = view.bounds.width / contentView.intrinsicContentSize.width
|
||||||
let minScale = min(widthScale, heightScale)
|
let minScale = min(widthScale, heightScale)
|
||||||
scrollView.minimumZoomScale = minScale
|
scrollView.minimumZoomScale = minScale
|
||||||
scrollView.zoomScale = minScale
|
scrollView.zoomScale = minScale
|
||||||
scrollView.maximumZoomScale = minScale >= 1 ? minScale + 2 : 2
|
scrollView.maximumZoomScale = minScale >= 1 ? minScale + 2 : 2
|
||||||
|
|
||||||
centerImage()
|
centerImage()
|
||||||
|
|
||||||
|
// todo: does this need to be in viewDidLayoutSubviews?
|
||||||
if view.safeAreaInsets.top == 44 {
|
if view.safeAreaInsets.top == 44 {
|
||||||
// running on iPhone X style notched device
|
// running on iPhone X style notched device
|
||||||
let notchWidth: CGFloat = 209
|
let notchWidth: CGFloat = 209
|
||||||
|
@ -147,7 +150,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||||
return imageView
|
return contentView
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
||||||
|
@ -163,18 +166,18 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
}
|
}
|
||||||
|
|
||||||
func centerImage() {
|
func centerImage() {
|
||||||
let yOffset = max(0, (view.bounds.size.height - imageView.frame.height) / 2)
|
let yOffset = max(0, (view.bounds.size.height - contentView.frame.height) / 2)
|
||||||
imageViewTopConstraint.constant = yOffset
|
contentViewTopConstraint.constant = yOffset
|
||||||
|
|
||||||
let xOffset = max(0, (view.bounds.size.width - imageView.frame.width) / 2)
|
let xOffset = max(0, (view.bounds.size.width - contentView.frame.width) / 2)
|
||||||
imageViewLeadingConstraint.constant = xOffset
|
contentViewLeadingConstraint.constant = xOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
func zoomRectFor(scale: CGFloat, center: CGPoint) -> CGRect {
|
func zoomRectFor(scale: CGFloat, center: CGPoint) -> CGRect {
|
||||||
var zoomRect = CGRect.zero
|
var zoomRect = CGRect.zero
|
||||||
zoomRect.size.width = imageView.frame.width / scale
|
zoomRect.size.width = contentView.frame.width / scale
|
||||||
zoomRect.size.height = imageView.frame.height / scale
|
zoomRect.size.height = contentView.frame.height / scale
|
||||||
let newCenter = scrollView.convert(center, to: imageView)
|
let newCenter = scrollView.convert(center, to: contentView)
|
||||||
zoomRect.origin.x = newCenter.x - (zoomRect.width / 2)
|
zoomRect.origin.x = newCenter.x - (zoomRect.width / 2)
|
||||||
zoomRect.origin.y = newCenter.y - (zoomRect.height / 2)
|
zoomRect.origin.y = newCenter.y - (zoomRect.height / 2)
|
||||||
return zoomRect
|
return zoomRect
|
||||||
|
@ -225,11 +228,8 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func sharePressed(_ sender: Any) {
|
@IBAction func sharePressed(_ sender: Any) {
|
||||||
guard let image = image else { return }
|
let activityVC = UIActivityViewController(activityItems: contentView.activityItemsForSharing, applicationActivities: nil)
|
||||||
let activityVC = UIActivityViewController(activityItems: [image], applicationActivities: nil)
|
activityVC.popoverPresentationController?.sourceView = shareButton
|
||||||
if let presentationController = activityVC.presentationController as? UIPopoverPresentationController {
|
|
||||||
presentationController.sourceView = shareButton
|
|
||||||
}
|
|
||||||
present(activityVC, animated: true)
|
present(activityVC, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,6 @@
|
||||||
<outlet property="closeButtonTopConstraint" destination="ImD-2H-0XK" id="DUe-b1-a2N"/>
|
<outlet property="closeButtonTopConstraint" destination="ImD-2H-0XK" id="DUe-b1-a2N"/>
|
||||||
<outlet property="closeButtonTrailingConstraint" destination="JFe-ig-3Ic" id="cWO-Rr-y3F"/>
|
<outlet property="closeButtonTrailingConstraint" destination="JFe-ig-3Ic" id="cWO-Rr-y3F"/>
|
||||||
<outlet property="descriptionLabel" destination="eo5-fc-RV8" id="vrW-RJ-y5k"/>
|
<outlet property="descriptionLabel" destination="eo5-fc-RV8" id="vrW-RJ-y5k"/>
|
||||||
<outlet property="imageView" destination="qcn-1t-3sS" id="Q01-G2-y1c"/>
|
|
||||||
<outlet property="imageViewLeadingConstraint" destination="bI3-V8-M70" id="nIe-xI-E9u"/>
|
|
||||||
<outlet property="imageViewTopConstraint" destination="tfL-hp-2I2" id="EDV-RO-pTe"/>
|
|
||||||
<outlet property="scrollView" destination="Skj-xq-AgQ" id="TFb-zF-m1b"/>
|
<outlet property="scrollView" destination="Skj-xq-AgQ" id="TFb-zF-m1b"/>
|
||||||
<outlet property="shareButton" destination="vhp-0u-Q0S" id="JZS-K9-4w9"/>
|
<outlet property="shareButton" destination="vhp-0u-Q0S" id="JZS-K9-4w9"/>
|
||||||
<outlet property="shareButtonLeadingConstraint" destination="MJx-2r-p0k" id="Dn5-Eg-Pid"/>
|
<outlet property="shareButtonLeadingConstraint" destination="MJx-2r-p0k" id="Dn5-Eg-Pid"/>
|
||||||
|
@ -31,19 +28,9 @@
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" minimumZoomScale="0.25" maximumZoomScale="2" translatesAutoresizingMaskIntoConstraints="NO" id="Skj-xq-AgQ">
|
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" minimumZoomScale="0.25" maximumZoomScale="2" translatesAutoresizingMaskIntoConstraints="NO" id="Skj-xq-AgQ">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<subviews>
|
|
||||||
<imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qcn-1t-3sS" customClass="GIFImageView" customModule="Gifu">
|
|
||||||
<rect key="frame" x="0.0" y="-10" width="375" height="647"/>
|
|
||||||
<gestureRecognizers/>
|
|
||||||
</imageView>
|
|
||||||
</subviews>
|
|
||||||
<gestureRecognizers/>
|
<gestureRecognizers/>
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="qcn-1t-3sS" firstAttribute="leading" secondItem="Skj-xq-AgQ" secondAttribute="leading" id="bI3-V8-M70"/>
|
|
||||||
<constraint firstItem="qcn-1t-3sS" firstAttribute="top" secondItem="Skj-xq-AgQ" secondAttribute="top" id="tfL-hp-2I2"/>
|
|
||||||
</constraints>
|
|
||||||
</scrollView>
|
</scrollView>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kHo-B9-R7a">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kHo-B9-R7a">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="36"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="36"/>
|
||||||
|
|
|
@ -36,8 +36,8 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
|
||||||
var shrinkGestureEnabled = true
|
var shrinkGestureEnabled = true
|
||||||
|
|
||||||
weak var animationSourceView: UIImageView?
|
weak var animationSourceView: UIImageView?
|
||||||
var animationImage: UIImage? { largeImageVC?.image ?? animationSourceView?.image }
|
var animationImage: UIImage? { largeImageVC?.animationImage ?? animationSourceView?.image }
|
||||||
var animationGifData: Data? { largeImageVC?.gifData }
|
var animationGifData: Data? { largeImageVC?.animationGifData }
|
||||||
var dismissInteractionController: LargeImageInteractionController?
|
var dismissInteractionController: LargeImageInteractionController?
|
||||||
|
|
||||||
override var prefersStatusBarHidden: Bool {
|
override var prefersStatusBarHidden: Bool {
|
||||||
|
@ -108,12 +108,12 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
|
||||||
|
|
||||||
func createLargeImage(data: Data) {
|
func createLargeImage(data: Data) {
|
||||||
guard let image = UIImage(data: data) else { return }
|
guard let image = UIImage(data: data) else { return }
|
||||||
largeImageVC = LargeImageViewController(image: image, description: imageDescription, sourceView: animationSourceView)
|
let gifData = url.pathExtension == "gif" ? data : nil
|
||||||
|
let imageView = LargeImageImageContentView(image: image, gifData: gifData)
|
||||||
|
|
||||||
|
largeImageVC = LargeImageViewController(contentView: imageView, description: imageDescription, sourceView: animationSourceView)
|
||||||
largeImageVC!.initialControlsVisible = initialControlsVisible
|
largeImageVC!.initialControlsVisible = initialControlsVisible
|
||||||
largeImageVC!.shrinkGestureEnabled = false
|
largeImageVC!.shrinkGestureEnabled = false
|
||||||
if url.pathExtension == "gif" {
|
|
||||||
largeImageVC!.gifData = data
|
|
||||||
}
|
|
||||||
embedChild(largeImageVC!)
|
embedChild(largeImageVC!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,12 @@ struct AppearancePrefsView : View {
|
||||||
Toggle(isOn: $preferences.hideCustomEmojiInUsernames) {
|
Toggle(isOn: $preferences.hideCustomEmojiInUsernames) {
|
||||||
Text("Hide Custom Emoji in Usernames")
|
Text("Hide Custom Emoji in Usernames")
|
||||||
}
|
}
|
||||||
|
Toggle(isOn: $preferences.showIsStatusReplyIcon) {
|
||||||
|
Text("Show Status Reply Icons")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $preferences.alwaysShowStatusVisibilityIcon) {
|
||||||
|
Text("Always Show Status Visibility Icons")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.listStyle(GroupedListStyle())
|
.listStyle(GroupedListStyle())
|
||||||
.navigationBarTitle(Text("Appearance"))
|
.navigationBarTitle(Text("Appearance"))
|
||||||
|
|
|
@ -36,14 +36,6 @@ protocol TuskerNavigationDelegate: class {
|
||||||
|
|
||||||
func reply(to statusID: String, mentioningAcct: String?)
|
func reply(to statusID: String, mentioningAcct: String?)
|
||||||
|
|
||||||
func largeImage(_ image: UIImage, description: String?, sourceView: UIImageView) -> LargeImageViewController
|
|
||||||
|
|
||||||
func largeImage(gifData: Data, description: String?, sourceView: UIImageView) -> LargeImageViewController
|
|
||||||
|
|
||||||
func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIImageView)
|
|
||||||
|
|
||||||
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIImageView)
|
|
||||||
|
|
||||||
func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController
|
func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController
|
||||||
|
|
||||||
func showLoadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView)
|
func showLoadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView)
|
||||||
|
@ -150,27 +142,6 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
||||||
vc.presentationController?.delegate = compose
|
vc.presentationController?.delegate = compose
|
||||||
present(vc, animated: true)
|
present(vc, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func largeImage(_ image: UIImage, description: String?, sourceView: UIImageView) -> LargeImageViewController {
|
|
||||||
let vc = LargeImageViewController(image: image, description: description, sourceView: sourceView)
|
|
||||||
vc.transitioningDelegate = self
|
|
||||||
return vc
|
|
||||||
}
|
|
||||||
|
|
||||||
func largeImage(gifData: Data, description: String?, sourceView: UIImageView) -> LargeImageViewController {
|
|
||||||
let vc = LargeImageViewController(image: UIImage(data: gifData)!, description: description, sourceView: sourceView)
|
|
||||||
vc.transitioningDelegate = self
|
|
||||||
vc.gifData = gifData
|
|
||||||
return vc
|
|
||||||
}
|
|
||||||
|
|
||||||
func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIImageView) {
|
|
||||||
present(largeImage(image, description: description, sourceView: sourceView), animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIImageView) {
|
|
||||||
present(largeImage(gifData: gifData, description: description, sourceView: sourceView), animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController {
|
func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController {
|
||||||
let vc = LoadingLargeImageViewController(url: url, cache: cache, imageDescription: description)
|
let vc = LoadingLargeImageViewController(url: url, cache: cache, imageDescription: description)
|
||||||
|
|
|
@ -19,8 +19,8 @@ class GifvAttachmentView: UIView {
|
||||||
layer as! AVPlayerLayer
|
layer as! AVPlayerLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
private let item: AVPlayerItem
|
let item: AVPlayerItem
|
||||||
private let player: AVPlayer
|
let player: AVPlayer
|
||||||
|
|
||||||
init(asset: AVAsset, gravity: AVLayerVideoGravity) {
|
init(asset: AVAsset, gravity: AVLayerVideoGravity) {
|
||||||
item = AVPlayerItem(asset: asset)
|
item = AVPlayerItem(asset: asset)
|
||||||
|
|
|
@ -128,9 +128,6 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
updateUI(account: account)
|
updateUI(account: account)
|
||||||
updateUIForPreferences(account: account)
|
updateUIForPreferences(account: account)
|
||||||
|
|
||||||
visibilityImageView.image = UIImage(systemName: status.visibility.unfilledImageName)
|
|
||||||
visibilityImageView.accessibilityLabel = String(format: NSLocalizedString("Visibility: %@", comment: "status visibility indicator accessibility label"), status.visibility.displayName)
|
|
||||||
|
|
||||||
attachmentsView.updateUI(status: status)
|
attachmentsView.updateUI(status: status)
|
||||||
attachmentsView.isAccessibilityElement = status.attachments.count > 0
|
attachmentsView.isAccessibilityElement = status.attachments.count > 0
|
||||||
attachmentsView.accessibilityLabel = String(format: NSLocalizedString("%d attachments", comment: "status attachments count accessibility label"), status.attachments.count)
|
attachmentsView.accessibilityLabel = String(format: NSLocalizedString("%d attachments", comment: "status attachments count accessibility label"), status.attachments.count)
|
||||||
|
@ -154,8 +151,8 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
reblogDisabled = status.visibility == .direct || (status.visibility == .private && status.account.id != mastodonController.account.id)
|
reblogDisabled = status.visibility == .direct || (status.visibility == .private && status.account.id != mastodonController.account.id)
|
||||||
}
|
}
|
||||||
reblogButton.isEnabled = !reblogDisabled
|
reblogButton.isEnabled = !reblogDisabled
|
||||||
let reblogButtonImage = reblogDisabled ? UIImage(systemName: status.visibility.imageName) : UIImage(systemName: "repeat")
|
|
||||||
reblogButton.setImage(reblogButtonImage, for: .normal)
|
updateStatusIconsForPreferences(status)
|
||||||
|
|
||||||
if state.unknown {
|
if state.unknown {
|
||||||
collapsible = !status.spoilerText.isEmpty
|
collapsible = !status.spoilerText.isEmpty
|
||||||
|
@ -207,8 +204,11 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func preferencesChanged() {
|
@objc func preferencesChanged() {
|
||||||
guard let mastodonController = mastodonController, let account = mastodonController.persistentContainer.account(for: accountID) else { return }
|
guard let mastodonController = mastodonController,
|
||||||
|
let account = mastodonController.persistentContainer.account(for: accountID),
|
||||||
|
let status = mastodonController.persistentContainer.status(for: statusID) else { return }
|
||||||
updateUIForPreferences(account: account)
|
updateUIForPreferences(account: account)
|
||||||
|
updateStatusIconsForPreferences(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIForPreferences(account: AccountMO) {
|
func updateUIForPreferences(account: AccountMO) {
|
||||||
|
@ -217,6 +217,21 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
attachmentsView.contentHidden = Preferences.shared.blurAllMedia || (mastodonController.persistentContainer.status(for: statusID)?.sensitive ?? false)
|
attachmentsView.contentHidden = Preferences.shared.blurAllMedia || (mastodonController.persistentContainer.status(for: statusID)?.sensitive ?? false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateStatusIconsForPreferences(_ status: StatusMO) {
|
||||||
|
visibilityImageView.isHidden = !Preferences.shared.alwaysShowStatusVisibilityIcon
|
||||||
|
if Preferences.shared.alwaysShowStatusVisibilityIcon {
|
||||||
|
visibilityImageView.image = UIImage(systemName: status.visibility.unfilledImageName)
|
||||||
|
visibilityImageView.accessibilityLabel = String(format: NSLocalizedString("Visibility: %@", comment: "status visibility indicator accessibility label"), status.visibility.displayName)
|
||||||
|
}
|
||||||
|
let reblogButtonImage: UIImage
|
||||||
|
if Preferences.shared.alwaysShowStatusVisibilityIcon || reblogButton.isEnabled {
|
||||||
|
reblogButtonImage = UIImage(systemName: "repeat")!
|
||||||
|
} else {
|
||||||
|
reblogButtonImage = UIImage(systemName: status.visibility.imageName)!
|
||||||
|
}
|
||||||
|
reblogButton.setImage(reblogButtonImage, for: .normal)
|
||||||
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
|
|
||||||
|
|
|
@ -89,12 +89,11 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
timestampLabel.isHidden = pinned
|
timestampLabel.isHidden = pinned
|
||||||
pinImageView.isHidden = !pinned
|
pinImageView.isHidden = !pinned
|
||||||
}
|
}
|
||||||
|
|
||||||
replyImageView.isHidden = !showReplyIndicator || status.inReplyToID == nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc override func preferencesChanged() {
|
@objc override func preferencesChanged() {
|
||||||
super.preferencesChanged()
|
super.preferencesChanged()
|
||||||
|
|
||||||
if let rebloggerID = rebloggerID,
|
if let rebloggerID = rebloggerID,
|
||||||
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
|
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
|
||||||
updateRebloggerLabel(reblogger: reblogger)
|
updateRebloggerLabel(reblogger: reblogger)
|
||||||
|
@ -111,6 +110,12 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func updateStatusIconsForPreferences(_ status: StatusMO) {
|
||||||
|
super.updateStatusIconsForPreferences(status)
|
||||||
|
|
||||||
|
replyImageView.isHidden = !Preferences.shared.showIsStatusReplyIcon || !showReplyIndicator || status.inReplyToID == nil
|
||||||
|
}
|
||||||
|
|
||||||
func updateTimestamp() {
|
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
|
||||||
|
|
|
@ -151,36 +151,39 @@
|
||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="globe" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="LRh-Cc-1br">
|
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="oie-wK-IpU">
|
||||||
<rect key="frame" x="31" y="55" width="19" height="20"/>
|
<rect key="frame" x="0.5" y="54" width="49.5" height="22"/>
|
||||||
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
<subviews>
|
||||||
<constraints>
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="bubble.left.and.bubble.right" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="KdQ-Zn-IhD">
|
||||||
<constraint firstAttribute="height" constant="22" id="3Mk-NN-6fY"/>
|
<rect key="frame" x="0.0" y="1" width="25.5" height="21.5"/>
|
||||||
</constraints>
|
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" weight="thin"/>
|
<accessibility key="accessibilityConfiguration" label="Is a reply"/>
|
||||||
</imageView>
|
<constraints>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="bubble.left.and.bubble.right" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="KdQ-Zn-IhD">
|
<constraint firstAttribute="height" constant="22" id="x0C-Qo-YVA"/>
|
||||||
<rect key="frame" x="0.0" y="55" width="25.5" height="21.5"/>
|
</constraints>
|
||||||
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
<preferredSymbolConfiguration key="preferredSymbolConfiguration" weight="thin"/>
|
||||||
<accessibility key="accessibilityConfiguration" label="Is a reply"/>
|
</imageView>
|
||||||
<constraints>
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="globe" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="LRh-Cc-1br">
|
||||||
<constraint firstAttribute="height" constant="22" id="x0C-Qo-YVA"/>
|
<rect key="frame" x="30.5" y="1" width="19" height="20"/>
|
||||||
</constraints>
|
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" weight="thin"/>
|
<constraints>
|
||||||
</imageView>
|
<constraint firstAttribute="height" constant="22" id="3Mk-NN-6fY"/>
|
||||||
|
</constraints>
|
||||||
|
<preferredSymbolConfiguration key="preferredSymbolConfiguration" weight="thin"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="gIY-Wp-RSk" firstAttribute="leading" secondItem="QMP-j2-HLn" secondAttribute="trailing" constant="8" id="0Tm-v7-Ts4"/>
|
<constraint firstItem="gIY-Wp-RSk" firstAttribute="leading" secondItem="QMP-j2-HLn" secondAttribute="trailing" constant="8" id="0Tm-v7-Ts4"/>
|
||||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="QMP-j2-HLn" secondAttribute="bottom" constant="8" id="2Ao-Gj-fY3"/>
|
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="QMP-j2-HLn" secondAttribute="bottom" constant="8" id="2Ao-Gj-fY3"/>
|
||||||
|
<constraint firstItem="oie-wK-IpU" firstAttribute="top" secondItem="QMP-j2-HLn" secondAttribute="bottom" constant="4" id="7Mp-WS-FhY"/>
|
||||||
<constraint firstItem="QMP-j2-HLn" firstAttribute="top" secondItem="ve3-Y1-NQH" secondAttribute="top" id="PC4-Bi-QXm"/>
|
<constraint firstItem="QMP-j2-HLn" firstAttribute="top" secondItem="ve3-Y1-NQH" secondAttribute="top" id="PC4-Bi-QXm"/>
|
||||||
<constraint firstItem="LRh-Cc-1br" firstAttribute="top" secondItem="QMP-j2-HLn" secondAttribute="bottom" constant="4" id="QE5-0f-q1a"/>
|
<constraint firstItem="oie-wK-IpU" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="ve3-Y1-NQH" secondAttribute="leading" id="QKi-ny-jOJ"/>
|
||||||
<constraint firstItem="KdQ-Zn-IhD" firstAttribute="top" secondItem="QMP-j2-HLn" secondAttribute="bottom" constant="4" id="R2V-fr-WCN"/>
|
|
||||||
<constraint firstItem="gIY-Wp-RSk" firstAttribute="top" secondItem="QMP-j2-HLn" secondAttribute="top" id="fEd-wN-kuQ"/>
|
<constraint firstItem="gIY-Wp-RSk" firstAttribute="top" secondItem="QMP-j2-HLn" secondAttribute="top" id="fEd-wN-kuQ"/>
|
||||||
|
<constraint firstItem="gIY-Wp-RSk" firstAttribute="leading" secondItem="oie-wK-IpU" secondAttribute="trailing" constant="8" id="fqd-p6-oGe"/>
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="gIY-Wp-RSk" secondAttribute="trailing" id="hKk-kO-wFT"/>
|
<constraint firstAttribute="trailingMargin" secondItem="gIY-Wp-RSk" secondAttribute="trailing" id="hKk-kO-wFT"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="gIY-Wp-RSk" secondAttribute="bottom" id="kRU-Ct-CIg"/>
|
<constraint firstAttribute="bottom" secondItem="gIY-Wp-RSk" secondAttribute="bottom" id="kRU-Ct-CIg"/>
|
||||||
<constraint firstItem="KdQ-Zn-IhD" firstAttribute="bottom" relation="lessThanOrEqual" secondItem="ve3-Y1-NQH" secondAttribute="bottom" id="rp8-N9-Iid"/>
|
|
||||||
<constraint firstItem="KdQ-Zn-IhD" firstAttribute="leading" secondItem="QMP-j2-HLn" secondAttribute="leading" id="uJd-Cz-AG3"/>
|
|
||||||
<constraint firstItem="gIY-Wp-RSk" firstAttribute="leading" secondItem="LRh-Cc-1br" secondAttribute="trailing" constant="8" id="zFc-5l-916"/>
|
|
||||||
<constraint firstItem="QMP-j2-HLn" firstAttribute="leading" secondItem="ve3-Y1-NQH" secondAttribute="leading" id="zeW-tQ-uJl"/>
|
<constraint firstItem="QMP-j2-HLn" firstAttribute="leading" secondItem="ve3-Y1-NQH" secondAttribute="leading" id="zeW-tQ-uJl"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
|
|
Loading…
Reference in New Issue