Compare commits

..

No commits in common. "cca2a03b2fe1d84456e2e078f2c4d1be57dc97a3" and "c03fc863004535f7da1d9e0bc99f54605b21e7f3" have entirely different histories.

10 changed files with 41 additions and 125 deletions

View File

@ -99,9 +99,9 @@ private func createFavoriteAction(status: StatusMO, container: StatusSwipeAction
} }
let title = status.favourited ? "Unfavorite" : "Favorite" let title = status.favourited ? "Unfavorite" : "Favorite"
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
completion(true)
Task { @MainActor in Task { @MainActor in
await FavoriteService(status: status, mastodonController: container.mastodonController, presenter: container.navigationDelegate).toggleFavorite() await FavoriteService(status: status, mastodonController: container.mastodonController, presenter: container.navigationDelegate).toggleFavorite()
completion(true)
} }
} }
action.image = UIImage(systemName: "star.fill") action.image = UIImage(systemName: "star.fill")
@ -116,9 +116,9 @@ private func createReblogAction(status: StatusMO, container: StatusSwipeActionCo
} }
let title = status.reblogged ? "Unreblog" : "Reblog" let title = status.reblogged ? "Unreblog" : "Reblog"
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
completion(true)
Task { @MainActor in Task { @MainActor in
await ReblogService(status: status, mastodonController: container.mastodonController, presenter: container.navigationDelegate).toggleReblog() await ReblogService(status: status, mastodonController: container.mastodonController, presenter: container.navigationDelegate).toggleReblog()
completion(true)
} }
} }
action.image = UIImage(systemName: "repeat") action.image = UIImage(systemName: "repeat")
@ -145,7 +145,6 @@ private func createBookmarkAction(status: StatusMO, container: StatusSwipeAction
let bookmarked = status.bookmarked ?? false let bookmarked = status.bookmarked ?? false
let title = bookmarked ? "Unbookmark" : "Bookmark" let title = bookmarked ? "Unbookmark" : "Bookmark"
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
completion(true)
Task { @MainActor in Task { @MainActor in
let request = (bookmarked ? Status.unbookmark : Status.bookmark)(status.id) let request = (bookmarked ? Status.unbookmark : Status.bookmark)(status.id)
do { do {
@ -157,6 +156,7 @@ private func createBookmarkAction(status: StatusMO, container: StatusSwipeAction
toastable.showToast(configuration: config, animated: true) toastable.showToast(configuration: config, animated: true)
} }
} }
completion(true)
} }
} }
action.image = UIImage(systemName: "bookmark.fill") action.image = UIImage(systemName: "bookmark.fill")

View File

@ -89,8 +89,6 @@ class MainSidebarViewController: UIViewController {
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self
collectionView.isSpringLoaded = true collectionView.isSpringLoaded = true
// TODO: allow focusing sidebar once there's a workaround for keyboard shortcuts from main split content not being accessible when not in the responder chain
collectionView.allowsFocus = false
view.addSubview(collectionView) view.addSubview(collectionView)
dataSource = createDataSource() dataSource = createDataSource()

View File

@ -432,9 +432,8 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
} }
// update the timeline position in case some statuses couldn't be loaded // update the timeline position in case some statuses couldn't be loaded
if let center = position.centerStatusID, if let center = position.centerStatusID {
let centerIndex = position.statusIDs.firstIndex(of: center) { let nearestLoadedStatusToCenter = position.statusIDs[position.statusIDs.firstIndex(of: center)!...].first(where: { id in
let nearestLoadedStatusToCenter = position.statusIDs[centerIndex...].first(where: { id in
// was already loaded or was just now loaded // was already loaded or was just now loaded
!unloaded.contains(id) || statuses.contains(where: { $0.id == id }) !unloaded.contains(id) || statuses.contains(where: { $0.id == id })
}) })

View File

@ -198,9 +198,3 @@ extension SegmentedPageViewController: StatusBarTappableViewController {
return .continue return .continue
} }
} }
extension SegmentedPageViewController: NestedResponderProvider {
var innerResponder: UIResponder? {
currentViewController
}
}

View File

@ -283,11 +283,7 @@ private class SplitSecondaryNavigationController: EnhancedNavigationViewControll
// ordinarily, the next responder in the chain would be the SplitNavigationController's view // ordinarily, the next responder in the chain would be the SplitNavigationController's view
// but that would bypass the VC in the root nav, so we reroute the repsonder chain to include it // but that would bypass the VC in the root nav, so we reroute the repsonder chain to include it
// first seems to be nil when using the view debugger for some reason, so in that case, defer to super // first seems to be nil when using the view debugger for some reason, so in that case, defer to super
if let root = owner.viewControllers.first { owner.viewControllers.first?.view ?? super.next
return root.innermostResponder() ?? super.next
} else {
return super.next
}
} }
private func configureSecondarySplitCloseButton(for viewController: UIViewController) { private func configureSecondarySplitCloseButton(for viewController: UIViewController) {
@ -304,17 +300,3 @@ private class SplitSecondaryNavigationController: EnhancedNavigationViewControll
} }
} }
protocol NestedResponderProvider {
var innerResponder: UIResponder? { get }
}
extension UIResponder {
func innermostResponder() -> UIResponder? {
if let nestedProvider = self as? NestedResponderProvider {
return nestedProvider.innerResponder?.innermostResponder() ?? self
} else {
return self
}
}
}

View File

@ -361,31 +361,23 @@ class BaseStatusTableViewCell: UITableViewCell {
let buttonImage = UIImage(systemName: collapsed ? "chevron.down" : "chevron.up")! let buttonImage = UIImage(systemName: collapsed ? "chevron.down" : "chevron.up")!
if let buttonImageView = collapseButton.imageView { if animated, let buttonImageView = collapseButton.imageView {
collapseButton.setImage(buttonImage, for: .normal) // we need to use a keyframe animation for this, because we want to control the direction the chevron rotates
// when rotating ±π, UIKit will always rotate in the same direction
if animated { // using a keyframe to set an intermediate point in the animation allows us to force a specific direction
buttonImageView.layer.opacity = 0 UIView.animateKeyframes(withDuration: 0.2, delay: 0, options: .calculationModeLinear, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
// this whole hack is necessary because when just rotating buttonImageView, it moves to the left of the button and then animates back to the center buttonImageView.transform = CGAffineTransform(rotationAngle: collapsed ? .pi / 2 : -.pi / 2)
let imageView = UIImageView(image: buttonImageView.image)
imageView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(imageView)
NSLayoutConstraint.activate([
imageView.widthAnchor.constraint(equalTo: buttonImageView.widthAnchor),
imageView.heightAnchor.constraint(equalTo: buttonImageView.heightAnchor),
imageView.centerXAnchor.constraint(equalTo: collapseButton.centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: collapseButton.centerYAnchor),
])
imageView.tintColor = .white
UIView.animate(withDuration: 0.3, delay: 0) {
imageView.transform = CGAffineTransform(rotationAngle: .pi)
} completion: { _ in
imageView.removeFromSuperview()
buttonImageView.layer.opacity = 1
} }
} UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
buttonImageView.transform = CGAffineTransform(rotationAngle: .pi)
}
}, completion: { (finished) in
buttonImageView.transform = .identity
self.collapseButton.setImage(buttonImage, for: .normal)
})
} else {
collapseButton.setImage(buttonImage, for: .normal)
} }
if collapsed { if collapsed {

View File

@ -81,14 +81,10 @@
<constraints> <constraints>
<constraint firstAttribute="height" constant="30" id="icD-3q-uJ6"/> <constraint firstAttribute="height" constant="30" id="icD-3q-uJ6"/>
</constraints> </constraints>
<color key="tintColor" systemColor="tintColor"/> <color key="tintColor" systemColor="systemBackgroundColor"/>
<state key="normal" image="chevron.down" catalog="system"> <state key="normal" image="chevron.down" catalog="system">
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/> <preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
</state> </state>
<buttonConfiguration key="configuration" style="filled" image="chevron.down" catalog="system">
<preferredSymbolConfiguration key="preferredSymbolConfigurationForImage" scale="large"/>
<color key="baseForegroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</buttonConfiguration>
<connections> <connections>
<action selector="collapseButtonPressed" destination="IDI-ur-8pa" eventType="touchUpInside" id="00b-nM-U5g"/> <action selector="collapseButtonPressed" destination="IDI-ur-8pa" eventType="touchUpInside" id="00b-nM-U5g"/>
</connections> </connections>
@ -102,9 +98,9 @@
</textView> </textView>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QqC-GR-TLC" customClass="StatusCardView" customModule="Tusker" customModuleProvider="target"> <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QqC-GR-TLC" customClass="StatusCardView" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="0.0" y="131" width="361" height="0.0"/> <rect key="frame" x="0.0" y="131" width="361" height="0.0"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints> <constraints>
<constraint firstAttribute="height" priority="999" constant="90" id="Tdo-Hv-ITE"/> <constraint firstAttribute="height" priority="999" constant="65" id="Tdo-Hv-ITE"/>
</constraints> </constraints>
</view> </view>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IF9-9U-Gk0" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target"> <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IF9-9U-Gk0" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">

View File

@ -25,10 +25,8 @@ class StatusCardView: UIView {
private var imageRequest: ImageCache.Request? private var imageRequest: ImageCache.Request?
private var isGrayscale = false private var isGrayscale = false
private var hStack: UIStackView!
private var titleLabel: UILabel! private var titleLabel: UILabel!
private var descriptionLabel: UILabel! private var descriptionLabel: UILabel!
private var domainLabel: UILabel!
private var imageView: UIImageView! private var imageView: UIImageView!
private var placeholderImageView: UIImageView! private var placeholderImageView: UIImageView!
@ -43,13 +41,11 @@ class StatusCardView: UIView {
} }
private func commonInit() { private func commonInit() {
// self.clipsToBounds = true self.clipsToBounds = true
// self.layer.borderWidth = 0.5 self.layer.cornerRadius = 6.5
// self.layer.borderColor = UIColor.lightGray.cgColor self.layer.borderWidth = 1
self.layer.shadowColor = UIColor.black.cgColor self.layer.borderColor = UIColor.lightGray.cgColor
self.layer.shadowRadius = 5 self.backgroundColor = inactiveBackgroundColor
self.layer.shadowOpacity = 0.2
self.layer.shadowOffset = .zero
self.addInteraction(UIContextMenuInteraction(delegate: self)) self.addInteraction(UIContextMenuInteraction(delegate: self))
@ -64,16 +60,9 @@ class StatusCardView: UIView {
descriptionLabel.numberOfLines = 2 descriptionLabel.numberOfLines = 2
descriptionLabel.setContentCompressionResistancePriority(.defaultLow, for: .vertical) descriptionLabel.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
domainLabel = UILabel()
domainLabel.font = .preferredFont(forTextStyle: .caption2)
domainLabel.adjustsFontForContentSizeCategory = true
domainLabel.numberOfLines = 1
domainLabel.textColor = .tintColor
let vStack = UIStackView(arrangedSubviews: [ let vStack = UIStackView(arrangedSubviews: [
titleLabel, titleLabel,
descriptionLabel, descriptionLabel
domainLabel,
]) ])
vStack.axis = .vertical vStack.axis = .vertical
vStack.alignment = .leading vStack.alignment = .leading
@ -84,23 +73,15 @@ class StatusCardView: UIView {
imageView.contentMode = .scaleAspectFill imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true imageView.clipsToBounds = true
let spacer = UIView() let hStack = UIStackView(arrangedSubviews: [
spacer.backgroundColor = .clear
hStack = UIStackView(arrangedSubviews: [
imageView, imageView,
vStack, vStack
spacer,
]) ])
hStack.translatesAutoresizingMaskIntoConstraints = false hStack.translatesAutoresizingMaskIntoConstraints = false
hStack.axis = .horizontal hStack.axis = .horizontal
hStack.alignment = .center hStack.alignment = .center
hStack.distribution = .fill hStack.distribution = .fill
hStack.spacing = 4 hStack.spacing = 4
hStack.clipsToBounds = true
hStack.layer.borderWidth = 0.5
hStack.layer.borderColor = UIColor.lightGray.cgColor
hStack.backgroundColor = inactiveBackgroundColor
addSubview(hStack) addSubview(hStack)
@ -117,10 +98,8 @@ class StatusCardView: UIView {
vStack.heightAnchor.constraint(equalTo: heightAnchor, constant: -8), vStack.heightAnchor.constraint(equalTo: heightAnchor, constant: -8),
spacer.widthAnchor.constraint(equalToConstant: 4),
hStack.leadingAnchor.constraint(equalTo: leadingAnchor), hStack.leadingAnchor.constraint(equalTo: leadingAnchor),
hStack.trailingAnchor.constraint(equalTo: trailingAnchor), hStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -4),
hStack.topAnchor.constraint(equalTo: topAnchor), hStack.topAnchor.constraint(equalTo: topAnchor),
hStack.bottomAnchor.constraint(equalTo: bottomAnchor), hStack.bottomAnchor.constraint(equalTo: bottomAnchor),
@ -133,11 +112,6 @@ class StatusCardView: UIView {
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
} }
override func layoutSubviews() {
super.layoutSubviews()
hStack.layer.cornerRadius = 0.1 * bounds.height
}
func updateUI(status: StatusMO) { func updateUI(status: StatusMO) {
guard status.id != statusID else { guard status.id != statusID else {
return return
@ -161,13 +135,6 @@ class StatusCardView: UIView {
let description = card.description.trimmingCharacters(in: .whitespacesAndNewlines) let description = card.description.trimmingCharacters(in: .whitespacesAndNewlines)
descriptionLabel.text = description descriptionLabel.text = description
descriptionLabel.isHidden = description.isEmpty descriptionLabel.isHidden = description.isEmpty
if let host = card.url.host {
domainLabel.text = host.serialized
domainLabel.isHidden = false
} else {
domainLabel.isHidden = true
}
} }
@objc private func updateUIForPreferences() { @objc private func updateUIForPreferences() {
@ -234,7 +201,7 @@ class StatusCardView: UIView {
} }
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
hStack.backgroundColor = activeBackgroundColor backgroundColor = activeBackgroundColor
setNeedsDisplay() setNeedsDisplay()
} }
@ -242,7 +209,7 @@ class StatusCardView: UIView {
} }
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
hStack.backgroundColor = inactiveBackgroundColor backgroundColor = inactiveBackgroundColor
setNeedsDisplay() setNeedsDisplay()
if let card = card, let delegate = navigationDelegate { if let card = card, let delegate = navigationDelegate {
@ -251,7 +218,7 @@ class StatusCardView: UIView {
} }
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
hStack.backgroundColor = inactiveBackgroundColor backgroundColor = inactiveBackgroundColor
setNeedsDisplay() setNeedsDisplay()
} }
@ -271,12 +238,6 @@ extension StatusCardView: UIContextMenuInteractionDelegate {
} }
} }
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
let params = UIPreviewParameters()
params.visiblePath = UIBezierPath(roundedRect: hStack.bounds, cornerRadius: hStack.layer.cornerRadius)
return UITargetedPreview(view: hStack, parameters: params)
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
if let viewController = animator.previewViewController, if let viewController = animator.previewViewController,
let delegate = navigationDelegate { let delegate = navigationDelegate {

View File

@ -20,7 +20,7 @@ class StatusContentContainer: UIView {
let cardView = StatusCardView().configure { let cardView = StatusCardView().configure {
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
$0.heightAnchor.constraint(equalToConstant: 90), $0.heightAnchor.constraint(equalToConstant: 65),
]) ])
} }

View File

@ -109,16 +109,10 @@
<constraints> <constraints>
<constraint firstAttribute="height" constant="30" id="z84-XW-gP3"/> <constraint firstAttribute="height" constant="30" id="z84-XW-gP3"/>
</constraints> </constraints>
<color key="tintColor" systemColor="tintColor"/> <color key="tintColor" systemColor="systemBackgroundColor"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="chevron.down" catalog="system"> <state key="normal" image="chevron.down" catalog="system">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/> <preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
</state> </state>
<buttonConfiguration key="configuration" style="filled" image="chevron.down" catalog="system">
<preferredSymbolConfiguration key="preferredSymbolConfigurationForImage" scale="large"/>
<color key="baseForegroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</buttonConfiguration>
<connections> <connections>
<action selector="collapseButtonPressed" destination="BR5-ZS-LIo" eventType="touchUpInside" id="twO-rE-1pQ"/> <action selector="collapseButtonPressed" destination="BR5-ZS-LIo" eventType="touchUpInside" id="twO-rE-1pQ"/>
</connections> </connections>
@ -132,9 +126,9 @@
</textView> </textView>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LKo-VB-XWl" customClass="StatusCardView" customModule="Tusker" customModuleProvider="target"> <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LKo-VB-XWl" customClass="StatusCardView" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="0.0" y="200.5" width="295" height="0.0"/> <rect key="frame" x="0.0" y="200.5" width="295" height="0.0"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints> <constraints>
<constraint firstAttribute="height" priority="999" constant="90" id="khY-jm-CPn"/> <constraint firstAttribute="height" priority="999" constant="65" id="khY-jm-CPn"/>
</constraints> </constraints>
</view> </view>
<view hidden="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="nbq-yr-2mA" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target"> <view hidden="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="nbq-yr-2mA" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">