Compare commits
6 Commits
c3cf38b0c9
...
f825760fe9
Author | SHA1 | Date |
---|---|---|
Shadowfacts | f825760fe9 | |
Shadowfacts | a339884d1f | |
Shadowfacts | 1de586f907 | |
Shadowfacts | bd162afdcc | |
Shadowfacts | 956b817045 | |
Shadowfacts | 28ee0908d7 |
|
@ -17,7 +17,6 @@ class ReblogService {
|
||||||
private let status: StatusMO
|
private let status: StatusMO
|
||||||
|
|
||||||
var hapticFeedback = true
|
var hapticFeedback = true
|
||||||
var visibility: Visibility? = nil
|
|
||||||
var requireConfirmation = Preferences.shared.confirmBeforeReblog
|
var requireConfirmation = Preferences.shared.confirmBeforeReblog
|
||||||
|
|
||||||
init(status: StatusMO, mastodonController: MastodonController, presenter: any TuskerNavigationDelegate) {
|
init(status: StatusMO, mastodonController: MastodonController, presenter: any TuskerNavigationDelegate) {
|
||||||
|
@ -31,26 +30,30 @@ class ReblogService {
|
||||||
requireConfirmation {
|
requireConfirmation {
|
||||||
presentConfirmationAlert()
|
presentConfirmationAlert()
|
||||||
} else {
|
} else {
|
||||||
await doToggleReblog()
|
await doToggleReblog(visibility: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentConfirmationAlert() {
|
private func presentConfirmationAlert() {
|
||||||
let image: UIImage?
|
let image: UIImage?
|
||||||
let reblogVisibilityActions: [CustomAlertController.MenuAction]?
|
let reblogVisibilityActions: [CustomAlertController.MenuAction]
|
||||||
|
let maximumVisibility = status.visibility
|
||||||
if mastodonController.instanceFeatures.reblogVisibility {
|
if mastodonController.instanceFeatures.reblogVisibility {
|
||||||
image = UIImage(systemName: Visibility.public.unfilledImageName)
|
image = UIImage(systemName: maximumVisibility.unfilledImageName)
|
||||||
reblogVisibilityActions = [Visibility.unlisted, .private].map { visibility in
|
reblogVisibilityActions = [Visibility.unlisted, .private].compactMap { visibility in
|
||||||
CustomAlertController.MenuAction(title: "Reblog as \(visibility.displayName)", subtitle: visibility.subtitle, image: UIImage(systemName: visibility.unfilledImageName)) {
|
guard visibility < maximumVisibility else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return CustomAlertController.MenuAction(title: "Reblog as \(visibility.displayName)", subtitle: visibility.subtitle, image: UIImage(systemName: visibility.unfilledImageName)) {
|
||||||
// deliberately retain a strong reference to self
|
// deliberately retain a strong reference to self
|
||||||
Task {
|
Task {
|
||||||
await self.doToggleReblog()
|
await self.doToggleReblog(visibility: visibility)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
image = nil
|
image = nil
|
||||||
reblogVisibilityActions = nil
|
reblogVisibilityActions = []
|
||||||
}
|
}
|
||||||
|
|
||||||
let preview = ConfirmReblogStatusPreviewView(status: status)
|
let preview = ConfirmReblogStatusPreviewView(status: status)
|
||||||
|
@ -59,11 +62,11 @@ class ReblogService {
|
||||||
CustomAlertController.Action(title: "Reblog", image: image, style: .default, handler: {
|
CustomAlertController.Action(title: "Reblog", image: image, style: .default, handler: {
|
||||||
// deliberately retain a strong reference to self
|
// deliberately retain a strong reference to self
|
||||||
Task {
|
Task {
|
||||||
await self.doToggleReblog()
|
await self.doToggleReblog(visibility: nil)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
if let reblogVisibilityActions {
|
if !reblogVisibilityActions.isEmpty {
|
||||||
var menuAction = CustomAlertController.Action(title: nil, image: UIImage(systemName: "chevron.down"), style: .menu(reblogVisibilityActions), handler: nil)
|
var menuAction = CustomAlertController.Action(title: nil, image: UIImage(systemName: "chevron.down"), style: .menu(reblogVisibilityActions), handler: nil)
|
||||||
menuAction.isSecondaryMenu = true
|
menuAction.isSecondaryMenu = true
|
||||||
config.actions.append(menuAction)
|
config.actions.append(menuAction)
|
||||||
|
@ -72,7 +75,7 @@ class ReblogService {
|
||||||
presenter.present(alert, animated: true)
|
presenter.present(alert, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func doToggleReblog() async {
|
private func doToggleReblog(visibility: Visibility?) async {
|
||||||
let oldValue = status.reblogged
|
let oldValue = status.reblogged
|
||||||
status.reblogged.toggle()
|
status.reblogged.toggle()
|
||||||
mastodonController.persistentContainer.statusSubject.send(status.id)
|
mastodonController.persistentContainer.statusSubject.send(status.id)
|
||||||
|
|
|
@ -518,7 +518,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
if let centerStatusID,
|
if let centerStatusID,
|
||||||
let index = statusIDs.firstIndex(of: centerStatusID) {
|
let index = statusIDs.firstIndex(of: centerStatusID) {
|
||||||
self.scrollToItem(item: items[index])
|
self.scrollToItem(item: items[index])
|
||||||
stateRestorationLogger.fault("TimelineViewController: restored statuses with center ID \(centerStatusID)")
|
stateRestorationLogger.info("TimelineViewController: restored statuses with center ID \(centerStatusID)")
|
||||||
} else {
|
} else {
|
||||||
stateRestorationLogger.fault("TimelineViewController: restored statuses, but couldn't find center ID")
|
stateRestorationLogger.fault("TimelineViewController: restored statuses, but couldn't find center ID")
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,13 @@ class CachedImageView: UIImageView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showOnlyBlurHash(_ blurhash: String, for url: URL) {
|
||||||
|
if url != self.url {
|
||||||
|
self.url = url
|
||||||
|
updateBlurhash(blurhash, for: url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func preferencesChanged() {
|
@objc private func preferencesChanged() {
|
||||||
if isGrayscale != Preferences.shared.grayscaleImages {
|
if isGrayscale != Preferences.shared.grayscaleImages {
|
||||||
updateImage()
|
updateImage()
|
||||||
|
|
|
@ -19,6 +19,7 @@ class ProfileHeaderButton: UIButton {
|
||||||
var backgroundConfig = UIBackgroundConfiguration.clear()
|
var backgroundConfig = UIBackgroundConfiguration.clear()
|
||||||
backgroundConfig.visualEffect = UIBlurEffect(style: .systemThickMaterial)
|
backgroundConfig.visualEffect = UIBlurEffect(style: .systemThickMaterial)
|
||||||
config.background = backgroundConfig
|
config.background = backgroundConfig
|
||||||
|
config.imagePadding = 4
|
||||||
self.configuration = config
|
self.configuration = config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,13 @@ class ScrollingSegmentedControl<Value: Hashable>: UIScrollView, UIGestureRecogni
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
if traitCollection.preferredContentSizeCategory != previousTraitCollection?.preferredContentSizeCategory {
|
||||||
|
invalidateIntrinsicContentSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func createOptionViews() {
|
private func createOptionViews() {
|
||||||
optionsStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
optionsStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ class StatusCardView: UIView {
|
||||||
private var titleLabel: UILabel!
|
private var titleLabel: UILabel!
|
||||||
private var descriptionLabel: UILabel!
|
private var descriptionLabel: UILabel!
|
||||||
private var domainLabel: UILabel!
|
private var domainLabel: UILabel!
|
||||||
private var imageView: CachedImageView!
|
private var imageView: StatusCardImageView!
|
||||||
private var placeholderImageView: UIImageView!
|
private var placeholderImageView: UIImageView!
|
||||||
private var leadingSpacer: UIView!
|
private var leadingSpacer: UIView!
|
||||||
private var trailingSpacer: UIView!
|
private var trailingSpacer: UIView!
|
||||||
|
@ -80,7 +80,7 @@ class StatusCardView: UIView {
|
||||||
vStack.alignment = .leading
|
vStack.alignment = .leading
|
||||||
vStack.spacing = 0
|
vStack.spacing = 0
|
||||||
|
|
||||||
imageView = CachedImageView(cache: .attachments)
|
imageView = StatusCardImageView(cache: .attachments)
|
||||||
imageView.contentMode = .scaleAspectFill
|
imageView.contentMode = .scaleAspectFill
|
||||||
imageView.clipsToBounds = true
|
imageView.clipsToBounds = true
|
||||||
|
|
||||||
|
@ -167,7 +167,18 @@ class StatusCardView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let image = card.image {
|
if let image = card.image {
|
||||||
|
if status.sensitive {
|
||||||
|
if let blurhash = card.blurhash {
|
||||||
|
imageView.blurImage = false
|
||||||
|
imageView.showOnlyBlurHash(blurhash, for: URL(image)!)
|
||||||
|
} else {
|
||||||
|
// if we don't have a blurhash, load the image and show it behind a blur
|
||||||
|
imageView.blurImage = true
|
||||||
|
imageView.update(for: URL(image), blurhash: nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
imageView.update(for: URL(image), blurhash: card.blurhash)
|
imageView.update(for: URL(image), blurhash: card.blurhash)
|
||||||
|
}
|
||||||
imageView.isHidden = false
|
imageView.isHidden = false
|
||||||
leadingSpacer.isHidden = true
|
leadingSpacer.isHidden = true
|
||||||
} else {
|
} else {
|
||||||
|
@ -257,3 +268,25 @@ extension StatusCardView: UIContextMenuInteractionDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class StatusCardImageView: CachedImageView {
|
||||||
|
@Lazy private var blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||||
|
var blurImage = false {
|
||||||
|
didSet {
|
||||||
|
if blurImage {
|
||||||
|
if !_blurView.isInitialized {
|
||||||
|
blurView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(blurView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
blurView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
blurView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
blurView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
blurView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_blurView.valueIfInitialized?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue