forked from shadowfacts/Tusker
parent
bcc70e9f8c
commit
b2977540e0
|
@ -223,6 +223,7 @@
|
||||||
D691771129A2B76A0054D7EF /* MainActor+Unsafe.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771029A2B76A0054D7EF /* MainActor+Unsafe.swift */; };
|
D691771129A2B76A0054D7EF /* MainActor+Unsafe.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771029A2B76A0054D7EF /* MainActor+Unsafe.swift */; };
|
||||||
D691771529A6FCAB0054D7EF /* StateRestorableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771429A6FCAB0054D7EF /* StateRestorableViewController.swift */; };
|
D691771529A6FCAB0054D7EF /* StateRestorableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771429A6FCAB0054D7EF /* StateRestorableViewController.swift */; };
|
||||||
D691771729A710520054D7EF /* ProfileNoContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771629A710520054D7EF /* ProfileNoContentCollectionViewCell.swift */; };
|
D691771729A710520054D7EF /* ProfileNoContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771629A710520054D7EF /* ProfileNoContentCollectionViewCell.swift */; };
|
||||||
|
D691771929A7B8820054D7EF /* ProfileHeaderMovedOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771829A7B8820054D7EF /* ProfileHeaderMovedOverlayView.swift */; };
|
||||||
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */; };
|
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */; };
|
||||||
D693A72A25CF8C1E003A14E2 /* ProfileDirectoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */; };
|
D693A72A25CF8C1E003A14E2 /* ProfileDirectoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */; };
|
||||||
D693A72F25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */; };
|
D693A72F25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */; };
|
||||||
|
@ -641,6 +642,7 @@
|
||||||
D691771029A2B76A0054D7EF /* MainActor+Unsafe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainActor+Unsafe.swift"; sourceTree = "<group>"; };
|
D691771029A2B76A0054D7EF /* MainActor+Unsafe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainActor+Unsafe.swift"; sourceTree = "<group>"; };
|
||||||
D691771429A6FCAB0054D7EF /* StateRestorableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRestorableViewController.swift; sourceTree = "<group>"; };
|
D691771429A6FCAB0054D7EF /* StateRestorableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRestorableViewController.swift; sourceTree = "<group>"; };
|
||||||
D691771629A710520054D7EF /* ProfileNoContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNoContentCollectionViewCell.swift; sourceTree = "<group>"; };
|
D691771629A710520054D7EF /* ProfileNoContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNoContentCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
D691771829A7B8820054D7EF /* ProfileHeaderMovedOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderMovedOverlayView.swift; sourceTree = "<group>"; };
|
||||||
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagsViewController.swift; sourceTree = "<group>"; };
|
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagsViewController.swift; sourceTree = "<group>"; };
|
||||||
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDirectoryViewController.swift; sourceTree = "<group>"; };
|
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDirectoryViewController.swift; sourceTree = "<group>"; };
|
||||||
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedProfileCollectionViewCell.swift; sourceTree = "<group>"; };
|
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedProfileCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1208,6 +1210,7 @@
|
||||||
D6412B0C24B0D4CF00F5412E /* ProfileHeaderView.swift */,
|
D6412B0C24B0D4CF00F5412E /* ProfileHeaderView.swift */,
|
||||||
D651C5B32915B00400236EF6 /* ProfileFieldsView.swift */,
|
D651C5B32915B00400236EF6 /* ProfileFieldsView.swift */,
|
||||||
D6A3A37F295515550036B6EF /* ProfileHeaderButton.swift */,
|
D6A3A37F295515550036B6EF /* ProfileHeaderButton.swift */,
|
||||||
|
D691771829A7B8820054D7EF /* ProfileHeaderMovedOverlayView.swift */,
|
||||||
);
|
);
|
||||||
path = "Profile Header";
|
path = "Profile Header";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2229,6 +2232,7 @@
|
||||||
D621733328F1D5ED004C7DB1 /* ReblogService.swift in Sources */,
|
D621733328F1D5ED004C7DB1 /* ReblogService.swift in Sources */,
|
||||||
D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */,
|
D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */,
|
||||||
D61F75AF293AF50C00C0B37F /* EditedFilter.swift in Sources */,
|
D61F75AF293AF50C00C0B37F /* EditedFilter.swift in Sources */,
|
||||||
|
D691771929A7B8820054D7EF /* ProfileHeaderMovedOverlayView.swift in Sources */,
|
||||||
D61F75B9293C15A000C0B37F /* ZeroHeightCollectionViewCell.swift in Sources */,
|
D61F75B9293C15A000C0B37F /* ZeroHeightCollectionViewCell.swift in Sources */,
|
||||||
D677284824ECBCB100C732D3 /* ComposeView.swift in Sources */,
|
D677284824ECBCB100C732D3 /* ComposeView.swift in Sources */,
|
||||||
D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */,
|
D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
//
|
||||||
|
// ProfileHeaderMovedOverlayView.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 2/23/23.
|
||||||
|
// Copyright © 2023 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class ProfileHeaderMovedOverlayView: UIView {
|
||||||
|
|
||||||
|
private var movedToID: String!
|
||||||
|
weak var delegate: TuskerNavigationDelegate?
|
||||||
|
|
||||||
|
var collapse: (() -> Void)?
|
||||||
|
|
||||||
|
private var avatarImageView: CachedImageView!
|
||||||
|
private var displayNameLabel: EmojiLabel!
|
||||||
|
private var usernameLabel: UILabel!
|
||||||
|
private(set) var collapseButton: UIButton!
|
||||||
|
|
||||||
|
init() {
|
||||||
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
let blur = UIBlurEffect(style: .systemUltraThinMaterial)
|
||||||
|
let blurView = UIVisualEffectView(effect: blur)
|
||||||
|
blurView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(blurView)
|
||||||
|
|
||||||
|
let vibrancy = UIVibrancyEffect(blurEffect: blur, style: .label)
|
||||||
|
let vibrancyView = UIVisualEffectView(effect: vibrancy)
|
||||||
|
vibrancyView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
blurView.contentView.addSubview(vibrancyView)
|
||||||
|
|
||||||
|
let label = UILabel()
|
||||||
|
label.text = "This account has moved to"
|
||||||
|
label.font = .preferredFont(forTextStyle: .title3).withTraits(.traitBold)
|
||||||
|
label.adjustsFontForContentSizeCategory = true
|
||||||
|
label.textColor = .label
|
||||||
|
|
||||||
|
avatarImageView = CachedImageView(cache: .avatars)
|
||||||
|
avatarImageView.layer.masksToBounds = true
|
||||||
|
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 50
|
||||||
|
avatarImageView.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
|
avatarImageView.isUserInteractionEnabled = true
|
||||||
|
|
||||||
|
displayNameLabel = EmojiLabel()
|
||||||
|
displayNameLabel.adjustsFontForContentSizeCategory = true
|
||||||
|
displayNameLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .body).addingAttributes([
|
||||||
|
.traits: [
|
||||||
|
UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold.rawValue,
|
||||||
|
]
|
||||||
|
]), size: 0)
|
||||||
|
|
||||||
|
usernameLabel = UILabel()
|
||||||
|
usernameLabel.adjustsFontForContentSizeCategory = true
|
||||||
|
usernameLabel.font = .preferredFont(forTextStyle: .body)
|
||||||
|
usernameLabel.textColor = .secondaryLabel
|
||||||
|
|
||||||
|
let nameVStack = UIStackView(arrangedSubviews: [
|
||||||
|
displayNameLabel,
|
||||||
|
usernameLabel,
|
||||||
|
])
|
||||||
|
nameVStack.axis = .vertical
|
||||||
|
nameVStack.alignment = .leading
|
||||||
|
nameVStack.spacing = 4
|
||||||
|
|
||||||
|
let accountHStack = UIStackView(arrangedSubviews: [
|
||||||
|
avatarImageView,
|
||||||
|
nameVStack,
|
||||||
|
])
|
||||||
|
accountHStack.axis = .horizontal
|
||||||
|
accountHStack.alignment = .top
|
||||||
|
accountHStack.spacing = 4
|
||||||
|
accountHStack.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountTapped)))
|
||||||
|
|
||||||
|
let stack = UIStackView(arrangedSubviews: [
|
||||||
|
label,
|
||||||
|
accountHStack,
|
||||||
|
])
|
||||||
|
stack.axis = .vertical
|
||||||
|
stack.alignment = .center
|
||||||
|
stack.spacing = 8
|
||||||
|
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
vibrancyView.contentView.addSubview(stack)
|
||||||
|
|
||||||
|
var config = UIButton.Configuration.plain()
|
||||||
|
config.image = UIImage(systemName: "chevron.up")
|
||||||
|
collapseButton = UIButton(configuration: config, primaryAction: UIAction(handler: { [unowned self] _ in
|
||||||
|
self.collapse?()
|
||||||
|
}))
|
||||||
|
collapseButton.accessibilityLabel = "Shrink banner"
|
||||||
|
collapseButton.isPointerInteractionEnabled = true
|
||||||
|
collapseButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(collapseButton)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
blurView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
blurView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
blurView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
blurView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
|
||||||
|
vibrancyView.leadingAnchor.constraint(equalTo: blurView.contentView.leadingAnchor),
|
||||||
|
vibrancyView.trailingAnchor.constraint(equalTo: blurView.contentView.trailingAnchor),
|
||||||
|
vibrancyView.topAnchor.constraint(equalTo: blurView.contentView.topAnchor),
|
||||||
|
vibrancyView.bottomAnchor.constraint(equalTo: blurView.contentView.bottomAnchor),
|
||||||
|
|
||||||
|
stack.centerXAnchor.constraint(equalTo: vibrancyView.contentView.readableContentGuide.centerXAnchor),
|
||||||
|
stack.centerYAnchor.constraint(equalTo: vibrancyView.contentView.centerYAnchor),
|
||||||
|
stack.leadingAnchor.constraint(greaterThanOrEqualTo: vibrancyView.contentView.readableContentGuide.leadingAnchor),
|
||||||
|
stack.trailingAnchor.constraint(lessThanOrEqualTo: vibrancyView.contentView.readableContentGuide.trailingAnchor),
|
||||||
|
stack.topAnchor.constraint(greaterThanOrEqualTo: vibrancyView.contentView.topAnchor),
|
||||||
|
stack.bottomAnchor.constraint(lessThanOrEqualTo: vibrancyView.contentView.bottomAnchor),
|
||||||
|
|
||||||
|
avatarImageView.widthAnchor.constraint(equalToConstant: 50),
|
||||||
|
avatarImageView.heightAnchor.constraint(equalToConstant: 50),
|
||||||
|
|
||||||
|
bottomAnchor.constraint(equalToSystemSpacingBelow: collapseButton.bottomAnchor, multiplier: 1),
|
||||||
|
collapseButton.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func preferencesChanged() {
|
||||||
|
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 50
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUI(movedTo: AccountMO) {
|
||||||
|
movedToID = movedTo.id
|
||||||
|
|
||||||
|
avatarImageView.update(for: movedTo.avatar)
|
||||||
|
displayNameLabel.text = movedTo.displayOrUserName
|
||||||
|
displayNameLabel.setEmojis(movedTo.emojis, identifier: movedTo.id)
|
||||||
|
usernameLabel.text = "@\(movedTo.acct)"
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func accountTapped() {
|
||||||
|
delegate?.selected(account: movedToID)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ProfileHeaderMovedOverlayView: UIPointerInteractionDelegate {
|
||||||
|
func pointerInteraction(_ interaction: UIPointerInteraction, regionFor request: UIPointerRegionRequest, defaultRegion: UIPointerRegion) -> UIPointerRegion? {
|
||||||
|
return defaultRegion
|
||||||
|
}
|
||||||
|
|
||||||
|
func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? {
|
||||||
|
let preview = UITargetedPreview(view: interaction.view!)
|
||||||
|
return UIPointerStyle(effect: .lift(preview))
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ class ProfileHeaderView: UIView {
|
||||||
@IBOutlet weak var fieldsView: ProfileFieldsView!
|
@IBOutlet weak var fieldsView: ProfileFieldsView!
|
||||||
@IBOutlet weak var followCountButton: UIButton!
|
@IBOutlet weak var followCountButton: UIButton!
|
||||||
private(set) var pagesSegmentedControl: ScrollingSegmentedControl<ProfileViewController.Page>!
|
private(set) var pagesSegmentedControl: ScrollingSegmentedControl<ProfileViewController.Page>!
|
||||||
|
private var movedOverlayView: ProfileHeaderMovedOverlayView?
|
||||||
|
|
||||||
var accountID: String!
|
var accountID: String!
|
||||||
|
|
||||||
|
@ -170,15 +171,64 @@ class ProfileHeaderView: UIView {
|
||||||
followCountButton.setAttributedTitle(followCountTitle, for: .normal)
|
followCountButton.setAttributedTitle(followCountTitle, for: .normal)
|
||||||
followCountButton.accessibilityLabel = "\(followingSpelledOut) following, \(followersSpelledOut) followers"
|
followCountButton.accessibilityLabel = "\(followingSpelledOut) following, \(followersSpelledOut) followers"
|
||||||
|
|
||||||
accessibilityElements = [
|
if let movedTo = account.movedTo {
|
||||||
displayNameLabel!,
|
if let movedOverlayView {
|
||||||
usernameLabel!,
|
movedOverlayView.updateUI(movedTo: movedTo)
|
||||||
relationshipLabel!,
|
} else {
|
||||||
noteTextView!,
|
let overlay = createMovedOverlayView(movedTo: movedTo)
|
||||||
fieldsView!,
|
movedOverlayView = overlay
|
||||||
moreButton!,
|
|
||||||
pagesSegmentedControl!,
|
accessibilityElements = [
|
||||||
]
|
overlay,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
movedOverlayView?.removeFromSuperview()
|
||||||
|
movedOverlayView = nil
|
||||||
|
|
||||||
|
accessibilityElements = [
|
||||||
|
displayNameLabel!,
|
||||||
|
usernameLabel!,
|
||||||
|
relationshipLabel!,
|
||||||
|
noteTextView!,
|
||||||
|
fieldsView!,
|
||||||
|
moreButton!,
|
||||||
|
pagesSegmentedControl!,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createMovedOverlayView(movedTo: AccountMO) -> ProfileHeaderMovedOverlayView {
|
||||||
|
let overlay = ProfileHeaderMovedOverlayView()
|
||||||
|
overlay.delegate = delegate
|
||||||
|
overlay.updateUI(movedTo: movedTo)
|
||||||
|
overlay.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(overlay)
|
||||||
|
let bottomConstraint = overlay.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
overlay.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
overlay.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
overlay.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
bottomConstraint,
|
||||||
|
])
|
||||||
|
|
||||||
|
overlay.collapse = { [weak self, weak overlay] in
|
||||||
|
guard let self, let overlay else { return }
|
||||||
|
bottomConstraint.isActive = false
|
||||||
|
overlay.bottomAnchor.constraint(equalTo: self.avatarContainerView.topAnchor, constant: -2).isActive = true
|
||||||
|
let animator = UIViewPropertyAnimator(duration: 0.35, dampingRatio: 0.8)
|
||||||
|
animator.addAnimations {
|
||||||
|
self.layoutIfNeeded()
|
||||||
|
overlay.collapseButton.layer.opacity = 0
|
||||||
|
}
|
||||||
|
animator.addCompletion { _ in
|
||||||
|
overlay.collapseButton.layer.opacity = 1
|
||||||
|
overlay.collapseButton?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
animator.startAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
return overlay
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateRelationship() {
|
private func updateRelationship() {
|
||||||
|
|
Loading…
Reference in New Issue