Compare commits

..

No commits in common. "eb89aec00fbb4b979ed97a2cb36f2931d354d9d7" and "2e2279ba8c319ea8cf2d075a32d78dce3456061c" have entirely different histories.

10 changed files with 26 additions and 91 deletions

View File

@ -1,12 +1,5 @@
# Changelog # Changelog
## 2024.3 (130)
Bugfixes:
- Fix reply author avatar on Compose screen not being pinned to top when scrolling while typing
- Fix crash when dragging between buttons in reblog confirmation alert
- Fix potential crash when displaying search results
- Mac: Fix Post button not displaying on Compose screen
## 2024.3 (129) ## 2024.3 (129)
Bugfixes: Bugfixes:
- Fix excessive network traffic on profile pages - Fix excessive network traffic on profile pages

View File

@ -333,12 +333,7 @@ public final class ComposeController: ViewController {
} }
.toolbar { .toolbar {
ToolbarItem(placement: .cancellationAction) { cancelButton } ToolbarItem(placement: .cancellationAction) { cancelButton }
#if targetEnvironment(macCatalyst)
ToolbarItem(placement: .topBarTrailing) { draftsButton }
ToolbarItem(placement: .confirmationAction) { postButton } ToolbarItem(placement: .confirmationAction) { postButton }
#else
ToolbarItem(placement: .confirmationAction) { postOrDraftsButton }
#endif
#if os(visionOS) #if os(visionOS)
ToolbarItem(placement: .bottomOrnament) { ToolbarItem(placement: .bottomOrnament) {
ControllerView(controller: { controller.toolbarController }) ControllerView(controller: { controller.toolbarController })
@ -466,26 +461,18 @@ public final class ComposeController: ViewController {
} }
@ViewBuilder @ViewBuilder
private var postOrDraftsButton: some View {
if draft.hasContent || draft.editedStatusID != nil || !controller.config.allowSwitchingDrafts {
postButton
} else {
draftsButton
}
}
private var draftsButton: some View {
Button(action: controller.showDrafts) {
Text("Drafts")
}
}
private var postButton: some View { private var postButton: some View {
Button(action: controller.postStatus) { if draft.hasContent || draft.editedStatusID != nil || !controller.config.allowSwitchingDrafts {
Text(draft.editedStatusID == nil ? "Post" : "Edit") Button(action: controller.postStatus) {
Text(draft.editedStatusID == nil ? "Post" : "Edit")
}
.keyboardShortcut(.return, modifiers: .command)
.disabled(!controller.postButtonEnabled)
} else {
Button(action: controller.showDrafts) {
Text("Drafts")
}
} }
.keyboardShortcut(.return, modifiers: .command)
.disabled(!controller.postButtonEnabled)
} }
#if !os(visionOS) #if !os(visionOS)

View File

@ -16,8 +16,6 @@ public class DraftsPersistentContainer: NSPersistentContainer {
public static let shared = DraftsPersistentContainer() public static let shared = DraftsPersistentContainer()
public static var captureError: ((any Error) -> Void)?
private static let managedObjectModel: NSManagedObjectModel = { private static let managedObjectModel: NSManagedObjectModel = {
let url = Bundle.module.url(forResource: "Drafts", withExtension: "momd")! let url = Bundle.module.url(forResource: "Drafts", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: url)! return NSManagedObjectModel(contentsOf: url)!
@ -41,7 +39,6 @@ public class DraftsPersistentContainer: NSPersistentContainer {
loadPersistentStores { _, error in loadPersistentStores { _, error in
if let error { if let error {
DraftsPersistentContainer.captureError?(error)
fatalError("Loading persistent store: \(error)") fatalError("Loading persistent store: \(error)")
} }
} }

View File

@ -39,7 +39,6 @@ extension TextViewCaretScrolling {
let animator = UIViewPropertyAnimator(duration: 0.2, curve: .easeInOut) { let animator = UIViewPropertyAnimator(duration: 0.2, curve: .easeInOut) {
scrollView.scrollRectToVisible(rectToMakeVisible, animated: false) scrollView.scrollRectToVisible(rectToMakeVisible, animated: false)
scrollView.layoutIfNeeded()
} }
self.caretScrollPositionAnimator = animator self.caretScrollPositionAnimator = animator
animator.startAnimation() animator.startAnimation()

View File

@ -76,15 +76,13 @@ struct ReplyStatusView: View {
// once you scroll past the in-reply-to-content, the bottom of the avatar should be pinned to the bottom of the content // once you scroll past the in-reply-to-content, the bottom of the avatar should be pinned to the bottom of the content
offset = min(offset, maxOffset) offset = min(offset, maxOffset)
return AvatarContainerRepresentable(offset: offset) { return AvatarImageView(
AvatarImageView( url: status.account.avatar,
url: status.account.avatar, size: 50,
size: 50, style: controller.config.avatarStyle,
style: controller.config.avatarStyle, fetchAvatar: controller.fetchAvatar
fetchAvatar: controller.fetchAvatar )
) .offset(x: 0, y: offset)
}
.frame(width: 50, height: 50)
.accessibilityHidden(true) .accessibilityHidden(true)
} }
@ -96,39 +94,3 @@ private struct DisplayNameHeightPrefKey: PreferenceKey {
value = nextValue() value = nextValue()
} }
} }
// This whole dance is necessary so that the offset can be animatable from
// UIKit animations, like TextViewCaretScrolling.
private struct AvatarContainerRepresentable<Content: View>: UIViewControllerRepresentable {
let offset: CGFloat
@ViewBuilder let content: Content
func makeUIViewController(context: Context) -> Controller {
Controller(host: UIHostingController(rootView: content))
}
func updateUIViewController(_ uiViewController: Controller, context: Context) {
uiViewController.host.rootView = content
uiViewController.host.view.transform = CGAffineTransform(translationX: 0, y: offset)
}
// This extra layer is necessary because applying a transform to the
// representable's VC's view doesn't seem to have an effect.
class Controller: UIViewController {
let host: UIHostingController<Content>
init(host: UIHostingController<Content>) {
self.host = host
super.init(nibName: nil, bundle: nil)
addChild(host)
host.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(host.view)
host.view.frame = view.bounds
host.didMove(toParent: self)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}

View File

@ -56,9 +56,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// make sure the persistent container is initialized on the main thread // make sure the persistent container is initialized on the main thread
// otherwise initializing it on the background thread can deadlock with accessing it on the main thread elsewhere // otherwise initializing it on the background thread can deadlock with accessing it on the main thread elsewhere
#if canImport(Sentry)
DraftsPersistentContainer.captureError = { SentrySDK.capture(error: $0) }
#endif
_ = DraftsPersistentContainer.shared _ = DraftsPersistentContainer.shared
DispatchQueue.global(qos: .userInitiated).async { DispatchQueue.global(qos: .userInitiated).async {

View File

@ -95,7 +95,7 @@ class NotificationLoadingViewController: UIViewController {
return return
} }
guard let navigationController else { guard let navigationController else {
fatalError("Don't know how to show notification VC outside of navigation controller: parent is \(parent?.description ?? "<nil>")") fatalError("Don't know how to show notification VC outside of navigation controller")
} }
navigationController.viewControllers[navigationController.viewControllers.count - 1] = vc navigationController.viewControllers[navigationController.viewControllers.count - 1] = vc
} }

View File

@ -414,7 +414,7 @@ extension SearchResultsViewController {
hasher.combine(id) hasher.combine(id)
case let .hashtag(hashtag): case let .hashtag(hashtag):
hasher.combine("hashtag") hasher.combine("hashtag")
hasher.combine(hashtag.name) hasher.combine(hashtag.url)
case let .status(id, _): case let .status(id, _):
hasher.combine("status") hasher.combine("status")
hasher.combine(id) hasher.combine(id)

View File

@ -320,12 +320,12 @@ class CustomAlertActionsView: UIControl {
actionButtons[currentSelectedActionIndex].backgroundColor = nil actionButtons[currentSelectedActionIndex].backgroundColor = nil
} }
#if !os(visionOS) #if !os(visionOS)
if selectedButton != nil { if #available(iOS 17.5, *) {
if #available(iOS 17.5, *) { let view = selectedButton!.element
generator.selectionChanged(at: recognizer.location(in: generator.view)) let location = convert(CGPoint(x: view.bounds.midX, y: view.bounds.midY), from: view)
} else { generator.selectionChanged(at: location)
generator.selectionChanged() } else {
} generator.selectionChanged()
} }
#endif #endif

View File

@ -10,7 +10,7 @@
// https://help.apple.com/xcode/#/dev745c5c974 // https://help.apple.com/xcode/#/dev745c5c974
MARKETING_VERSION = 2024.3 MARKETING_VERSION = 2024.3
CURRENT_PROJECT_VERSION = 130 CURRENT_PROJECT_VERSION = 129
CURRENT_PROJECT_VERSION = $(inherited)$(CURRENT_PROJECT_VERSION_BUILD_SUFFIX_$(CONFIGURATION)) CURRENT_PROJECT_VERSION = $(inherited)$(CURRENT_PROJECT_VERSION_BUILD_SUFFIX_$(CONFIGURATION))
CURRENT_PROJECT_VERSION_BUILD_SUFFIX_Debug=-dev CURRENT_PROJECT_VERSION_BUILD_SUFFIX_Debug=-dev