Compare commits

..

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

28 changed files with 121 additions and 350 deletions

View File

@ -1,23 +1,5 @@
# Changelog # Changelog
## 2024.3 (129)
Bugfixes:
- Fix excessive network traffic on profile pages
- Fix attachment gallery controls visibility not being synced between pages
- Fix video attachments not restarting when play pressed while at ends
- Fix profile field text being misaligned
- Fix at sign in timeline statuses usernames sometimes clipping
- Fix add hashtag/instance to Pinned Timelines sheets dismissing immediately when opened
- Fix for display name being replaced with incorrect user in certain circumstances
- Fix profile moved overlay view appearing behind avatar/header
- Fix profile moved view accessibility with VoiceOver
- Fix mention/status push notifications not showing content warning
- Fix sensitive attachment thumbnails being shown in push notifications
- Fix Dynamic Type not applying to status content
- Fix expand all option in Conversation not transferring when opening ancestors
- Fix not being able to resolve remote Mastodon status links in Conversation screen
- Fix status indicator icons overlapping thread links when Dynamic Type is enabled
## 2024.3 (128) ## 2024.3 (128)
Bugfixes: Bugfixes:
- Fix selecting poll option playing too much haptic feedback - Fix selecting poll option playing too much haptic feedback

View File

@ -122,12 +122,7 @@ class NotificationService: UNNotificationServiceExtension {
let notificationContent: String? let notificationContent: String?
if let status = notification.status { if let status = notification.status {
if notification.kind == .mention || notification.kind == .status, notificationContent = NotificationService.textConverter.convert(html: status.content)
!status.spoilerText.isEmpty {
notificationContent = "⚠️ \(status.spoilerText)"
} else {
notificationContent = NotificationService.textConverter.convert(html: status.content)
}
} else if notification.kind == .follow || notification.kind == .followRequest { } else if notification.kind == .follow || notification.kind == .followRequest {
notificationContent = nil notificationContent = nil
} else { } else {
@ -140,9 +135,7 @@ class NotificationService: UNNotificationServiceExtension {
// We deliberately don't include attachments for other types of notifications that have statuses (favs, etc.) // We deliberately don't include attachments for other types of notifications that have statuses (favs, etc.)
// because we risk just fetching the same thing a bunch of times for many senders. // because we risk just fetching the same thing a bunch of times for many senders.
if notification.kind == .mention || notification.kind == .status || notification.kind == .update, if notification.kind == .mention || notification.kind == .status || notification.kind == .update,
let status = notification.status, let attachment = notification.status?.attachments.first {
!status.sensitive,
let attachment = status.attachments.first {
let url = attachment.previewURL ?? attachment.url let url = attachment.previewURL ?? attachment.url
attachmentDataTask = Task { attachmentDataTask = Task {
do { do {

View File

@ -44,7 +44,6 @@ class GalleryItemViewController: UIViewController {
private(set) var scrollAndZoomEnabled = true private(set) var scrollAndZoomEnabled = true
private var scrollViewSizeForLastZoomScaleUpdate: CGSize? private var scrollViewSizeForLastZoomScaleUpdate: CGSize?
override var prefersHomeIndicatorAutoHidden: Bool { override var prefersHomeIndicatorAutoHidden: Bool {
return !controlsVisible return !controlsVisible
} }
@ -228,8 +227,6 @@ class GalleryItemViewController: UIViewController {
updateZoomScale(resetZoom: true) updateZoomScale(resetZoom: true)
} }
centerContent() centerContent()
// Ensure the transform is correct if the controls are hidden and their size changed.
setControlsVisible(controlsVisible, animated: false)
} }
override func viewDidAppear(_ animated: Bool) { override func viewDidAppear(_ animated: Bool) {
@ -292,12 +289,10 @@ class GalleryItemViewController: UIViewController {
func setControlsVisible(_ visible: Bool, animated: Bool) { func setControlsVisible(_ visible: Bool, animated: Bool) {
controlsVisible = visible controlsVisible = visible
guard let topControlsView, guard let topControlsView,
let bottomControlsView else { let bottomControlsView else {
return return
} }
func updateControlsViews() { func updateControlsViews() {
topControlsView.transform = CGAffineTransform(translationX: 0, y: visible ? 0 : -topControlsView.bounds.height) topControlsView.transform = CGAffineTransform(translationX: 0, y: visible ? 0 : -topControlsView.bounds.height)
bottomControlsView.transform = CGAffineTransform(translationX: 0, y: visible ? 0 : bottomControlsView.bounds.height) bottomControlsView.transform = CGAffineTransform(translationX: 0, y: visible ? 0 : bottomControlsView.bounds.height)

View File

@ -125,8 +125,6 @@ extension GalleryViewController: UIPageViewControllerDataSource {
extension GalleryViewController: UIPageViewControllerDelegate { extension GalleryViewController: UIPageViewControllerDelegate {
public func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { public func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
currentItemViewController.content.galleryContentWillDisappear() currentItemViewController.content.galleryContentWillDisappear()
let new = pendingViewControllers[0] as! GalleryItemViewController
new.setControlsVisible(currentItemViewController.controlsVisible, animated: false)
} }
public func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { public func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {

View File

@ -235,7 +235,6 @@
D698F46D2BD0B8310054DB14 /* AnnouncementsCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D698F46C2BD0B8310054DB14 /* AnnouncementsCollection.swift */; }; D698F46D2BD0B8310054DB14 /* AnnouncementsCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D698F46C2BD0B8310054DB14 /* AnnouncementsCollection.swift */; };
D698F46F2BD0B8DF0054DB14 /* AddReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D698F46E2BD0B8DF0054DB14 /* AddReactionView.swift */; }; D698F46F2BD0B8DF0054DB14 /* AddReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D698F46E2BD0B8DF0054DB14 /* AddReactionView.swift */; };
D698F4712BD0CBAA0054DB14 /* AnnouncementContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D698F4702BD0CBAA0054DB14 /* AnnouncementContentTextView.swift */; }; D698F4712BD0CBAA0054DB14 /* AnnouncementContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D698F4702BD0CBAA0054DB14 /* AnnouncementContentTextView.swift */; };
D69F26342C4CDFD300FAF761 /* AccountDisplayAndUserNameLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F26332C4CDFD300FAF761 /* AccountDisplayAndUserNameLabel.swift */; };
D6A00B1D26379FC900316AD4 /* PollOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */; }; D6A00B1D26379FC900316AD4 /* PollOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */; };
D6A3A380295515550036B6EF /* ProfileHeaderButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3A37F295515550036B6EF /* ProfileHeaderButton.swift */; }; D6A3A380295515550036B6EF /* ProfileHeaderButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3A37F295515550036B6EF /* ProfileHeaderButton.swift */; };
D6A3A3822956123A0036B6EF /* TimelinePosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3A3812956123A0036B6EF /* TimelinePosition.swift */; }; D6A3A3822956123A0036B6EF /* TimelinePosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3A3812956123A0036B6EF /* TimelinePosition.swift */; };
@ -669,7 +668,6 @@
D698F46C2BD0B8310054DB14 /* AnnouncementsCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementsCollection.swift; sourceTree = "<group>"; }; D698F46C2BD0B8310054DB14 /* AnnouncementsCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementsCollection.swift; sourceTree = "<group>"; };
D698F46E2BD0B8DF0054DB14 /* AddReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddReactionView.swift; sourceTree = "<group>"; }; D698F46E2BD0B8DF0054DB14 /* AddReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddReactionView.swift; sourceTree = "<group>"; };
D698F4702BD0CBAA0054DB14 /* AnnouncementContentTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnnouncementContentTextView.swift; sourceTree = "<group>"; }; D698F4702BD0CBAA0054DB14 /* AnnouncementContentTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnnouncementContentTextView.swift; sourceTree = "<group>"; };
D69F26332C4CDFD300FAF761 /* AccountDisplayAndUserNameLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDisplayAndUserNameLabel.swift; sourceTree = "<group>"; };
D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionsView.swift; sourceTree = "<group>"; }; D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionsView.swift; sourceTree = "<group>"; };
D6A3A37F295515550036B6EF /* ProfileHeaderButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderButton.swift; sourceTree = "<group>"; }; D6A3A37F295515550036B6EF /* ProfileHeaderButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderButton.swift; sourceTree = "<group>"; };
D6A3A3812956123A0036B6EF /* TimelinePosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinePosition.swift; sourceTree = "<group>"; }; D6A3A3812956123A0036B6EF /* TimelinePosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinePosition.swift; sourceTree = "<group>"; };
@ -1482,9 +1480,7 @@
D6BED1722126661300F02DA0 /* Views */ = { D6BED1722126661300F02DA0 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */,
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameView.swift */, D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameView.swift */,
D69F26332C4CDFD300FAF761 /* AccountDisplayAndUserNameLabel.swift */,
D68E6F5E253C9B2D001A1B4C /* BaseEmojiLabel.swift */, D68E6F5E253C9B2D001A1B4C /* BaseEmojiLabel.swift */,
D6ADB6EF28ED1F25009924AB /* CachedImageView.swift */, D6ADB6EF28ED1F25009924AB /* CachedImageView.swift */,
D6895DC328D65342006341DA /* ConfirmReblogStatusPreviewView.swift */, D6895DC328D65342006341DA /* ConfirmReblogStatusPreviewView.swift */,
@ -1518,6 +1514,7 @@
D641C78B213DD92F004B4513 /* Profile Header */, D641C78B213DD92F004B4513 /* Profile Header */,
D641C78A213DD926004B4513 /* Status */, D641C78A213DD926004B4513 /* Status */,
D64AAE8F26C80DB600FC57FB /* Toast */, D64AAE8F26C80DB600FC57FB /* Toast */,
D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2360,7 +2357,6 @@
D65B4B542971F71D00DABDFB /* EditedReport.swift in Sources */, D65B4B542971F71D00DABDFB /* EditedReport.swift in Sources */,
D62D67C52A97D8CD00167EE2 /* MultiColumnNavigationController.swift in Sources */, D62D67C52A97D8CD00167EE2 /* MultiColumnNavigationController.swift in Sources */,
D6A6C11525B62E9700298D0F /* CacheExpiry.swift in Sources */, D6A6C11525B62E9700298D0F /* CacheExpiry.swift in Sources */,
D69F26342C4CDFD300FAF761 /* AccountDisplayAndUserNameLabel.swift in Sources */,
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */, D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */,
D68A76F129539116001DA1B3 /* FlipView.swift in Sources */, D68A76F129539116001DA1B3 /* FlipView.swift in Sources */,
D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */, D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */,
@ -3260,7 +3256,7 @@
repositoryURL = "https://git.shadowfacts.net/shadowfacts/HTMLStreamer.git"; repositoryURL = "https://git.shadowfacts.net/shadowfacts/HTMLStreamer.git";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = 0.3.0; version = 0.2.5;
}; };
}; };
D63CC700290EC0B8000E19DE /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = { D63CC700290EC0B8000E19DE /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = {

View File

@ -25,14 +25,8 @@ class HTMLConverter {
private let converter: AttributedStringConverter<Callbacks> private let converter: AttributedStringConverter<Callbacks>
init(font: UIFont, monospaceFont: UIFont, fontMetrics: UIFontMetrics, color: UIColor, paragraphStyle: NSParagraphStyle) { init(font: UIFont, monospaceFont: UIFont, color: UIColor, paragraphStyle: NSParagraphStyle) {
let config = AttributedStringConverterConfiguration( let config = AttributedStringConverterConfiguration(font: font, monospaceFont: monospaceFont, color: color, paragraphStyle: paragraphStyle)
font: font,
monospaceFont: monospaceFont,
fontMetrics: fontMetrics,
color: color,
paragraphStyle: paragraphStyle
)
self.converter = AttributedStringConverter(configuration: config) self.converter = AttributedStringConverter(configuration: config)
} }

View File

@ -364,9 +364,7 @@ extension ConversationCollectionViewController: UICollectionViewDelegate {
conv.showStatusesAutomatically = showStatusesAutomatically conv.showStatusesAutomatically = showStatusesAutomatically
show(conv) show(conv)
} else { } else {
let conv = ConversationViewController(for: id, state: state.copy(), mastodonController: mastodonController) selected(status: id, state: state.copy())
conv.showStatusesAutomatically = showStatusesAutomatically
show(conv)
} }
case .expandThread(childThreads: let childThreads, inline: _): case .expandThread(childThreads: let childThreads, inline: _):
let indexPathBeforeExpandThread = IndexPath(row: indexPath.row - 1, section: indexPath.section) let indexPathBeforeExpandThread = IndexPath(row: indexPath.row - 1, section: indexPath.section)

View File

@ -221,16 +221,10 @@ class ConversationViewController: UIViewController {
completionHandler(nil) completionHandler(nil)
} }
} }
if isLikelyMastodonRemoteStatus(url: url) { if isLikelyMastodonRemoteStatus(url: url),
var request = URLRequest(url: url) let (_, response) = try? await URLSession.appDefault.data(from: url, delegate: RedirectBlocker()),
// Mastodon uses an intermediate redirect page for browsers which requires user input that we don't want. let location = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "location") {
request.addValue("application/activity+json", forHTTPHeaderField: "accept") effectiveURL = location
if let (_, response) = try? await URLSession.appDefault.data(for: request, delegate: RedirectBlocker()),
let location = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "location") {
effectiveURL = location
} else {
effectiveURL = WebURL(url)!.serialized(excludingFragment: true)
}
} else { } else {
effectiveURL = WebURL(url)!.serialized(excludingFragment: true) effectiveURL = WebURL(url)!.serialized(excludingFragment: true)
} }
@ -238,14 +232,9 @@ class ConversationViewController: UIViewController {
let request = Client.search(query: effectiveURL, types: [.statuses], resolve: true) let request = Client.search(query: effectiveURL, types: [.statuses], resolve: true)
do { do {
let (results, _) = try await mastodonController.run(request) let (results, _) = try await mastodonController.run(request)
let statuses = results.statuses.compactMap(\.value) guard let status = results.statuses.compactMap(\.value).first(where: { $0.url?.serialized() == effectiveURL }) else {
// Don't try to exactly match effective URL because the URL form Mastodon
// uses for the ActivityPub redirect doesn't match what's returned by the API.
// Instead we just assume that, if only one status was returned, it worked.
guard statuses.count == 1 else {
throw UnableToResolveError() throw UnableToResolveError()
} }
let status = statuses[0]
_ = mastodonController.persistentContainer.addOrUpdateOnViewContext(status: status) _ = mastodonController.persistentContainer.addOrUpdateOnViewContext(status: status)
mode = .localID(status.id) mode = .localID(status.id)
return status.id return status.id

View File

@ -13,7 +13,7 @@ struct CustomizeTimelinesView: View {
let mastodonController: MastodonController let mastodonController: MastodonController
var body: some View { var body: some View {
CustomizeTimelinesList(pinnedTimelines: mastodonController.accountPreferences.pinnedTimelines) CustomizeTimelinesList()
.environmentObject(mastodonController) .environmentObject(mastodonController)
.environment(\.managedObjectContext, mastodonController.persistentContainer.viewContext) .environment(\.managedObjectContext, mastodonController.persistentContainer.viewContext)
} }
@ -26,15 +26,7 @@ struct CustomizeTimelinesList: View {
@FetchRequest(sortDescriptors: []) private var filters: FetchedResults<FilterMO> @FetchRequest(sortDescriptors: []) private var filters: FetchedResults<FilterMO>
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@State private var deletionError: (any Error)? @State private var deletionError: (any Error)?
// store this separately from AccountPreferences in the view, b/c the @LazilyDecoding wrapper breaks animations
@State private var pinnedTimelines: [PinnedTimeline]
@State private var isShowingAddHashtagSheet = false
@State private var isShowingAddInstanceSheet = false
init(pinnedTimelines: [PinnedTimeline]) {
self.pinnedTimelines = pinnedTimelines
}
var body: some View { var body: some View {
if #available(iOS 16.0, *) { if #available(iOS 16.0, *) {
NavigationStack { NavigationStack {
@ -58,12 +50,8 @@ struct CustomizeTimelinesList: View {
private var navigationBody: some View { private var navigationBody: some View {
List { List {
PinnedTimelinesView( PinnedTimelinesView(accountPreferences: mastodonController.accountPreferences)
pinnedTimelines: $pinnedTimelines, .appGroupedListRowBackground()
isShowingAddHashtagSheet: $isShowingAddHashtagSheet,
isShowingAddInstanceSheet: $isShowingAddInstanceSheet
)
.appGroupedListRowBackground()
Section { Section {
Toggle(isOn: $preferences.hideReblogsInTimelines) { Toggle(isOn: $preferences.hideReblogsInTimelines) {
@ -111,12 +99,6 @@ struct CustomizeTimelinesList: View {
} }
} }
} }
.modifier(PinnedTimelinesModifier(
accountPreferences: mastodonController.accountPreferences,
pinnedTimelines: $pinnedTimelines,
isShowingAddHashtagSheet: $isShowingAddHashtagSheet,
isShowingAddInstanceSheet: $isShowingAddInstanceSheet
))
.alertWithData("Error Deleting Filter", data: $deletionError, actions: { _ in .alertWithData("Error Deleting Filter", data: $deletionError, actions: { _ in
Button("OK") { Button("OK") {
self.deletionError = nil self.deletionError = nil

View File

@ -11,10 +11,17 @@ import Pachyderm
struct PinnedTimelinesView: View { struct PinnedTimelinesView: View {
@EnvironmentObject private var mastodonController: MastodonController @EnvironmentObject private var mastodonController: MastodonController
@ObservedObject private var accountPreferences: AccountPreferences
@Binding var pinnedTimelines: [PinnedTimeline] @State private var isShowingAddHashtagSheet = false
@Binding var isShowingAddHashtagSheet: Bool @State private var isShowingAddInstanceSheet = false
@Binding var isShowingAddInstanceSheet: Bool // store this separately from AccountPreferences in the view, b/c the @LazilyDecoding wrapper breaks animations
@State private var pinnedTimelines: [PinnedTimeline]
init(accountPreferences: AccountPreferences) {
self.accountPreferences = accountPreferences
self.pinnedTimelines = accountPreferences.pinnedTimelines
}
var body: some View { var body: some View {
Section { Section {
@ -103,53 +110,42 @@ struct PinnedTimelinesView: View {
} header: { } header: {
Text("Pinned Timelines") Text("Pinned Timelines")
} }
} .sheet(isPresented: $isShowingAddHashtagSheet, content: {
} #if os(visionOS)
AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines)
struct PinnedTimelinesModifier: ViewModifier { .edgesIgnoringSafeArea(.bottom)
let accountPreferences: AccountPreferences #else
@Binding var pinnedTimelines: [PinnedTimeline] if #available(iOS 16.0, *) {
@Binding var isShowingAddHashtagSheet: Bool
@Binding var isShowingAddInstanceSheet: Bool
func body(content: Content) -> some View {
content
.sheet(isPresented: $isShowingAddHashtagSheet, content: {
#if os(visionOS)
AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines) AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines)
.edgesIgnoringSafeArea(.bottom) .edgesIgnoringSafeArea(.bottom)
#else } else {
if #available(iOS 16.0, *) { AddHashtagPinnedTimelineRepresentable(pinnedTimelines: $pinnedTimelines)
AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines)
.edgesIgnoringSafeArea(.bottom)
} else {
AddHashtagPinnedTimelineRepresentable(pinnedTimelines: $pinnedTimelines)
.edgesIgnoringSafeArea(.bottom)
}
#endif
})
.sheet(isPresented: $isShowingAddInstanceSheet, content: {
AddInstancePinnedTimelineView(pinnedTimelines: $pinnedTimelines)
.edgesIgnoringSafeArea(.bottom) .edgesIgnoringSafeArea(.bottom)
})
.onReceive(accountPreferences.publisher(for: \.pinnedTimelinesData)) { _ in
if pinnedTimelines != accountPreferences.pinnedTimelines {
pinnedTimelines = accountPreferences.pinnedTimelines
}
}
#if os(visionOS)
.onChange(of: pinnedTimelines) {
if accountPreferences.pinnedTimelines != pinnedTimelines {
accountPreferences.pinnedTimelines = pinnedTimelines
}
}
#else
.onChange(of: pinnedTimelines) { newValue in
if accountPreferences.pinnedTimelines != newValue {
accountPreferences.pinnedTimelines = newValue
}
} }
#endif #endif
})
.sheet(isPresented: $isShowingAddInstanceSheet, content: {
AddInstancePinnedTimelineView(pinnedTimelines: $pinnedTimelines)
.edgesIgnoringSafeArea(.bottom)
})
.onReceive(accountPreferences.publisher(for: \.pinnedTimelinesData)) { _ in
if pinnedTimelines != accountPreferences.pinnedTimelines {
pinnedTimelines = accountPreferences.pinnedTimelines
}
}
#if os(visionOS)
.onChange(of: pinnedTimelines) {
if accountPreferences.pinnedTimelines != pinnedTimelines {
accountPreferences.pinnedTimelines = pinnedTimelines
}
}
#else
.onChange(of: pinnedTimelines) { newValue in
if accountPreferences.pinnedTimelines != newValue {
accountPreferences.pinnedTimelines = newValue
}
}
#endif
} }
} }

View File

@ -106,9 +106,6 @@ class VideoOverlayViewController: UIViewController {
if player.rate > 0 { if player.rate > 0 {
player.rate = 0 player.rate = 0
} else { } else {
if player.currentTime() >= player.currentItem!.duration {
player.seek(to: .zero)
}
#if os(visionOS) #if os(visionOS)
player.play() player.play()
#else #else

View File

@ -135,11 +135,12 @@ private struct MockStatusCardView: UIViewRepresentable {
func makeUIView(context: Context) -> StatusCardView { func makeUIView(context: Context) -> StatusCardView {
let view = StatusCardView() let view = StatusCardView()
view.isUserInteractionEnabled = false view.isUserInteractionEnabled = false
let card = StatusCardView.CardData( let card = Card(
url: WebURL("https://vaccor.space/tusker")!, url: WebURL("https://vaccor.space/tusker")!,
image: WebURL("https://vaccor.space/tusker/img/icon.png")!,
title: "Tusker", title: "Tusker",
description: "Tusker is an iOS app for Mastodon" description: "Tusker is an iOS app for Mastodon",
image: WebURL("https://vaccor.space/tusker/img/icon.png")!,
kind: .link
) )
view.updateUI(card: card, sensitive: false) view.updateUI(card: card, sensitive: false)
return view return view

View File

@ -9,6 +9,12 @@
import UIKit import UIKit
import Pachyderm import Pachyderm
import Combine import Combine
import OSLog
#if canImport(Sentry)
import Sentry
#endif
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ProfileStatusesViewController")
class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionViewController, CollectionViewController { class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionViewController, CollectionViewController {
@ -249,6 +255,20 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
state = .setupInitialSnapshot state = .setupInitialSnapshot
Task {
do {
let (all, _) = try await mastodonController.run(Client.getRelationships(accounts: [accountID]))
if let relationship = all.first {
self.mastodonController.persistentContainer.addOrUpdate(relationship: relationship)
}
} catch {
logger.error("Error fetching relationship: \(String(describing: error))")
#if canImport(Sentry)
SentrySDK.capture(error: error)
#endif
}
}
await controller.loadInitial() await controller.loadInitial()
await tryLoadPinned() await tryLoadPinned()

View File

@ -9,12 +9,6 @@
import UIKit import UIKit
import Pachyderm import Pachyderm
import Combine import Combine
import OSLog
#if canImport(Sentry)
import Sentry
#endif
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ProfileViewController")
class ProfileViewController: UIViewController, StateRestorableViewController { class ProfileViewController: UIViewController, StateRestorableViewController {
@ -126,19 +120,6 @@ class ProfileViewController: UIViewController, StateRestorableViewController {
guard let accountID else { guard let accountID else {
return return
} }
Task {
do {
let (all, _) = try await mastodonController.run(Client.getRelationships(accounts: [accountID]))
if let relationship = all.first {
self.mastodonController.persistentContainer.addOrUpdate(relationship: relationship)
}
} catch {
logger.error("Error fetching relationship: \(String(describing: error))")
#if canImport(Sentry)
SentrySDK.capture(error: error)
#endif
}
}
if let account = mastodonController.persistentContainer.account(for: accountID) { if let account = mastodonController.persistentContainer.account(for: accountID) {
updateAccountUI(account: account) updateAccountUI(account: account)
} else { } else {

View File

@ -12,7 +12,6 @@ import SwiftUI
private var converter = HTMLConverter( private var converter = HTMLConverter(
font: .preferredFont(forTextStyle: .body), font: .preferredFont(forTextStyle: .body),
monospaceFont: UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular)), monospaceFont: UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular)),
fontMetrics: .default,
color: .label, color: .label,
paragraphStyle: .default paragraphStyle: .default
) )

View File

@ -1,57 +0,0 @@
//
// AccountDisplayAndUserNameLabel.swift
// Tusker
//
// Created by Shadowfacts on 7/20/24.
// Copyright © 2024 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
class AccountDisplayAndUserNameLabel: EmojiLabel {
var baseFont: UIFontDescriptor = .preferredFontDescriptor(withTextStyle: .body)
private var state: State?
func updateUI(account: some AccountProtocol) {
let state = State(accountID: account.id, displayName: account.displayName, acct: account.acct)
guard state != self.state || Preferences.shared.hideCustomEmojiInUsernames != hasEmojis else {
return
}
self.state = state
self.attributedText = makeAttributedText(state: state)
if Preferences.shared.hideCustomEmojiInUsernames {
self.removeEmojis()
} else {
self.setEmojis(account.emojis, identifier: state.accountID)
}
}
private func makeAttributedText(state: State) -> NSAttributedString {
let s = NSMutableAttributedString()
s.append(NSAttributedString(string: state.displayName, attributes: [
.font: UIFont(descriptor: baseFont.addingAttributes([
.traits: [
UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold.rawValue,
]
]), size: 0),
]))
s.append(NSAttributedString(string: " "))
s.append(NSAttributedString(string: "@\(state.acct)", attributes: [
.font: UIFont(descriptor: baseFont.addingAttributes([
.traits: [
UIFontDescriptor.TraitKey.weight: UIFont.Weight.light.rawValue,
]
]), size: 0),
.foregroundColor: UIColor.secondaryLabel,
]))
return s
}
private struct State: Equatable {
var accountID: String
var displayName: String
var acct: String
}
}

View File

@ -16,7 +16,7 @@ class AccountDisplayNameLabel: EmojiLabel {
private var accountDisplayName: String? private var accountDisplayName: String?
func updateForAccountDisplayName(account: some AccountProtocol) { func updateForAccountDisplayName(account: some AccountProtocol) {
guard accountID != account.id || accountDisplayName != account.displayName || Preferences.shared.hideCustomEmojiInUsernames != hasEmojis else { guard accountID != account.id || accountDisplayName != account.displayName || Preferences.shared.hideCustomEmojiInUsernames == hasEmojis else {
return return
} }
accountID = account.id accountID = account.id

View File

@ -13,7 +13,6 @@ class ConfirmReblogStatusPreviewView: UIView {
private static let htmlConverter = HTMLConverter( private static let htmlConverter = HTMLConverter(
font: .preferredFont(forTextStyle: .caption2), font: .preferredFont(forTextStyle: .caption2),
monospaceFont: UIFontMetrics(forTextStyle: .caption2).scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular)), monospaceFont: UIFontMetrics(forTextStyle: .caption2).scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular)),
fontMetrics: UIFontMetrics(forTextStyle: .caption2),
color: .label, color: .label,
paragraphStyle: .default paragraphStyle: .default
) )

View File

@ -25,7 +25,6 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
private static let defaultBodyHTMLConverter = HTMLConverter( private static let defaultBodyHTMLConverter = HTMLConverter(
font: .preferredFont(forTextStyle: .body), font: .preferredFont(forTextStyle: .body),
monospaceFont: UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular)), monospaceFont: UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular)),
fontMetrics: .default,
color: .label, color: .label,
paragraphStyle: .default paragraphStyle: .default
) )

View File

@ -21,7 +21,6 @@ class ProfileFieldValueView: UIView {
private static let converter = HTMLConverter( private static let converter = HTMLConverter(
font: .preferredFont(forTextStyle: .body), font: .preferredFont(forTextStyle: .body),
monospaceFont: UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular)), monospaceFont: UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular)),
fontMetrics: .default,
color: .label, color: .label,
paragraphStyle: .default paragraphStyle: .default
) )
@ -55,8 +54,8 @@ class ProfileFieldValueView: UIView {
textView.isScrollEnabled = false textView.isScrollEnabled = false
textView.isSelectable = false textView.isSelectable = false
textView.isEditable = false textView.isEditable = false
textView.textContainerInset = .zero
textView.font = .preferredFont(forTextStyle: .body) textView.font = .preferredFont(forTextStyle: .body)
updateTextContainerInset()
textView.adjustsFontForContentSizeCategory = true textView.adjustsFontForContentSizeCategory = true
textView.attributedText = converted textView.attributedText = converted
textView.setEmojis(account.emojis, identifier: account.id) textView.setEmojis(account.emojis, identifier: account.id)
@ -109,27 +108,6 @@ class ProfileFieldValueView: UIView {
return size return size
} }
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.preferredContentSizeCategory != previousTraitCollection?.preferredContentSizeCategory {
updateTextContainerInset()
}
}
private func updateTextContainerInset() {
// blergh
switch traitCollection.preferredContentSizeCategory {
case .extraSmall:
textView.textContainerInset = UIEdgeInsets(top: 4, left: 0, bottom: 0, right: 0)
case .small:
textView.textContainerInset = UIEdgeInsets(top: 3, left: 0, bottom: 0, right: 0)
case .medium, .large:
textView.textContainerInset = UIEdgeInsets(top: 2, left: 0, bottom: 0, right: 0)
default:
textView.textContainerInset = .zero
}
}
func setTextAlignment(_ alignment: NSTextAlignment) { func setTextAlignment(_ alignment: NSTextAlignment) {
textView.textAlignment = alignment textView.textAlignment = alignment
} }

View File

@ -14,8 +14,7 @@ class ProfileHeaderMovedOverlayView: UIView {
weak var delegate: TuskerNavigationDelegate? weak var delegate: TuskerNavigationDelegate?
var collapse: (() -> Void)? var collapse: (() -> Void)?
var hide: (() -> Void)?
private var avatarImageView: CachedImageView! private var avatarImageView: CachedImageView!
private var displayNameLabel: EmojiLabel! private var displayNameLabel: EmojiLabel!
private var usernameLabel: UILabel! private var usernameLabel: UILabel!
@ -145,46 +144,7 @@ class ProfileHeaderMovedOverlayView: UIView {
@objc private func accountTapped() { @objc private func accountTapped() {
delegate?.selected(account: movedToID) delegate?.selected(account: movedToID)
} }
// MARK: Accessibility
override var isAccessibilityElement: Bool {
get { true }
set {}
}
override var accessibilityLabel: String? {
get {
guard let movedToID,
let account = delegate?.apiController?.persistentContainer.account(for: movedToID) else {
return "This account has moved"
}
return "This account has moved to @\(account.acct)"
}
set {}
}
override func accessibilityActivate() -> Bool {
guard let movedToID,
let delegate else {
return false
}
delegate.selected(account: movedToID)
return true
}
override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
get {
[
UIAccessibilityCustomAction(name: "Hide banner", actionHandler: { [unowned self] _ in
self.hide?()
return true
})
]
}
set {}
}
} }
extension ProfileHeaderMovedOverlayView: UIPointerInteractionDelegate { extension ProfileHeaderMovedOverlayView: UIPointerInteractionDelegate {

View File

@ -41,7 +41,6 @@ class ProfileHeaderView: UIView {
@IBOutlet weak var followersCountButton: UIButton! @IBOutlet weak var followersCountButton: UIButton!
private(set) var pagesSegmentedControl: ScrollingSegmentedControl<ProfileViewController.Page>! private(set) var pagesSegmentedControl: ScrollingSegmentedControl<ProfileViewController.Page>!
private var movedOverlayView: ProfileHeaderMovedOverlayView? private var movedOverlayView: ProfileHeaderMovedOverlayView?
private var hideMovedOverlayView = false
var accountID: String! var accountID: String!
@ -179,8 +178,7 @@ class ProfileHeaderView: UIView {
followersCountButton.setAttributedTitle(followersCountTitle, for: .normal) followersCountButton.setAttributedTitle(followersCountTitle, for: .normal)
followersCountButton.accessibilityLabel = "\(followersSpelledOut) followers" followersCountButton.accessibilityLabel = "\(followersSpelledOut) followers"
if let movedTo = account.movedTo, if let movedTo = account.movedTo {
!hideMovedOverlayView {
if let movedOverlayView { if let movedOverlayView {
movedOverlayView.updateUI(movedTo: movedTo) movedOverlayView.updateUI(movedTo: movedTo)
} else { } else {
@ -209,7 +207,6 @@ class ProfileHeaderView: UIView {
private func createMovedOverlayView(movedTo: AccountMO) -> ProfileHeaderMovedOverlayView { private func createMovedOverlayView(movedTo: AccountMO) -> ProfileHeaderMovedOverlayView {
let overlay = ProfileHeaderMovedOverlayView() let overlay = ProfileHeaderMovedOverlayView()
overlay.layer.zPosition = 1000
overlay.delegate = delegate overlay.delegate = delegate
overlay.updateUI(movedTo: movedTo) overlay.updateUI(movedTo: movedTo)
overlay.translatesAutoresizingMaskIntoConstraints = false overlay.translatesAutoresizingMaskIntoConstraints = false
@ -237,12 +234,6 @@ class ProfileHeaderView: UIView {
} }
animator.startAnimation() animator.startAnimation()
} }
overlay.hide = { [weak self] in
guard let self else { return }
self.hideMovedOverlayView = true
self.updateUI(for: self.accountID)
UIAccessibility.post(notification: .layoutChanged, argument: self)
}
return overlay return overlay
} }

View File

@ -20,7 +20,6 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
private static let htmlConverter = HTMLConverter( private static let htmlConverter = HTMLConverter(
font: ConversationMainStatusCollectionViewCell.contentFont, font: ConversationMainStatusCollectionViewCell.contentFont,
monospaceFont: ConversationMainStatusCollectionViewCell.monospaceFont, monospaceFont: ConversationMainStatusCollectionViewCell.monospaceFont,
fontMetrics: .default,
color: .label, color: .label,
paragraphStyle: ConversationMainStatusCollectionViewCell.contentParagraphStyle paragraphStyle: ConversationMainStatusCollectionViewCell.contentParagraphStyle
) )
@ -515,15 +514,8 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
return contentContainer.estimateVisibleSubviewHeight(effectiveWidth: width) return contentContainer.estimateVisibleSubviewHeight(effectiveWidth: width)
} }
func updateAccountUI(account: AccountMO) {
baseUpdateAccountUI(account: account)
displayNameLabel.updateForAccountDisplayName(account: account)
usernameLabel.text = "@\(account.acct)"
}
func updateUIForPreferences(status: StatusMO) { func updateUIForPreferences(status: StatusMO) {
baseUpdateUIForPreferences(status: status) baseUpdateUIForPreferences(status: status)
displayNameLabel.updateForAccountDisplayName(account: status.account)
} }
@objc private func preferencesChanged() { @objc private func preferencesChanged() {

View File

@ -9,7 +9,6 @@
import UIKit import UIKit
import Pachyderm import Pachyderm
import SafariServices import SafariServices
import WebURL
import WebURLFoundationExtras import WebURLFoundationExtras
import HTMLStreamer import HTMLStreamer
@ -19,7 +18,7 @@ class StatusCardView: UIView {
weak var actionProvider: MenuActionProvider? weak var actionProvider: MenuActionProvider?
private var statusID: String? private var statusID: String?
private(set) var card: CardData? private(set) var card: Card?
private static let activeBackgroundColor = UIColor.secondarySystemFill private static let activeBackgroundColor = UIColor.secondarySystemFill
private static let inactiveBackgroundColor = UIColor.secondarySystemBackground private static let inactiveBackgroundColor = UIColor.secondarySystemBackground
@ -164,22 +163,20 @@ class StatusCardView: UIView {
} }
func updateUI(status: StatusMO) { func updateUI(status: StatusMO) {
let newData = status.card.map { CardData(card: $0) } guard status.id != statusID else {
guard self.card != newData else {
return return
} }
self.card = newData self.card = status.card
self.statusID = status.id self.statusID = status.id
guard let newData else { guard let card = status.card else {
return return
} }
updateUI(card: newData, sensitive: status.sensitive) updateUI(card: card, sensitive: status.sensitive)
} }
// This method is internal for use by MockStatusView func updateUI(card: Card, sensitive: Bool) {
func updateUI(card: CardData, sensitive: Bool) {
if let image = card.image { if let image = card.image {
if sensitive { if sensitive {
if let blurhash = card.blurhash { if let blurhash = card.blurhash {
@ -246,30 +243,6 @@ class StatusCardView: UIView {
hStack.backgroundColor = StatusCardView.inactiveBackgroundColor hStack.backgroundColor = StatusCardView.inactiveBackgroundColor
setNeedsDisplay() setNeedsDisplay()
} }
struct CardData: Equatable {
let url: WebURL
let image: WebURL?
let title: String
let description: String
let blurhash: String?
init(card: Card) {
self.url = card.url
self.image = card.image
self.title = card.title
self.description = card.description
self.blurhash = card.blurhash
}
init(url: WebURL, image: WebURL? = nil, title: String, description: String, blurhash: String? = nil) {
self.url = url
self.image = image
self.title = title
self.description = description
self.blurhash = blurhash
}
}
} }

View File

@ -21,6 +21,8 @@ protocol StatusCollectionViewCellDelegate: AnyObject, TuskerNavigationDelegate,
protocol StatusCollectionViewCell: UICollectionViewCell, AttachmentViewDelegate { protocol StatusCollectionViewCell: UICollectionViewCell, AttachmentViewDelegate {
// MARK: Subviews // MARK: Subviews
var avatarImageView: CachedImageView { get } var avatarImageView: CachedImageView { get }
var displayNameLabel: AccountDisplayNameLabel { get }
var usernameLabel: UILabel { get }
var contentWarningLabel: EmojiLabel { get } var contentWarningLabel: EmojiLabel { get }
var collapseButton: StatusCollapseButton { get } var collapseButton: StatusCollapseButton { get }
var contentContainer: StatusContentContainer { get } var contentContainer: StatusContentContainer { get }
@ -47,7 +49,6 @@ protocol StatusCollectionViewCell: UICollectionViewCell, AttachmentViewDelegate
var isGrayscale: Bool { get set } var isGrayscale: Bool { get set }
var cancellables: Set<AnyCancellable> { get set } var cancellables: Set<AnyCancellable> { get set }
func updateAccountUI(account: AccountMO)
func updateAttachmentsUI(status: StatusMO) func updateAttachmentsUI(status: StatusMO)
func updateUIForPreferences(status: StatusMO) func updateUIForPreferences(status: StatusMO)
func updateStatusState(status: StatusMO) func updateStatusState(status: StatusMO)
@ -176,8 +177,10 @@ extension StatusCollectionViewCell {
attachmentsView.updateUI(attachments: status.attachments) attachmentsView.updateUI(attachments: status.attachments)
} }
func baseUpdateAccountUI(account: AccountMO) { func updateAccountUI(account: AccountMO) {
avatarImageView.update(for: account.avatar) avatarImageView.update(for: account.avatar)
displayNameLabel.updateForAccountDisplayName(account: account)
usernameLabel.text = "@\(account.acct)"
} }
func baseUpdateUIForPreferences(status: StatusMO) { func baseUpdateUIForPreferences(status: StatusMO) {
@ -216,6 +219,7 @@ extension StatusCollectionViewCell {
if contentTextView.hasEmojis { if contentTextView.hasEmojis {
contentTextView.setEmojis(status.emojis, identifier: status.id) contentTextView.setEmojis(status.emojis, identifier: status.id)
} }
displayNameLabel.updateForAccountDisplayName(account: status.account)
} }
func baseUpdateStatusState(status: StatusMO) { func baseUpdateStatusState(status: StatusMO) {

View File

@ -70,7 +70,8 @@ class StatusMetaIndicatorsView: UIView {
private func configureImageView(_ imageView: UIImageView) { private func configureImageView(_ imageView: UIImageView) {
let weight: UIImage.SymbolWeight = UIAccessibility.isBoldTextEnabled ? .regular : traitCollection.preferredContentSizeCategory > .large ? .light : .thin let weight: UIImage.SymbolWeight = UIAccessibility.isBoldTextEnabled ? .regular : traitCollection.preferredContentSizeCategory > .large ? .light : .thin
imageView.preferredSymbolConfiguration = .init(pointSize: 0, weight: weight, scale: .default) let scale: UIImage.SymbolScale = traitCollection.preferredContentSizeCategory > .extraLarge ? .large : .default
imageView.preferredSymbolConfiguration = .init(pointSize: 0, weight: weight, scale: scale)
} }
func updateUI(status: StatusMO) { func updateUI(status: StatusMO) {

View File

@ -23,7 +23,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
static let htmlConverter = HTMLConverter( static let htmlConverter = HTMLConverter(
font: TimelineStatusCollectionViewCell.contentFont, font: TimelineStatusCollectionViewCell.contentFont,
monospaceFont: TimelineStatusCollectionViewCell.monospaceFont, monospaceFont: TimelineStatusCollectionViewCell.monospaceFont,
fontMetrics: .default,
color: .label, color: .label,
paragraphStyle: TimelineStatusCollectionViewCell.contentParagraphStyle paragraphStyle: TimelineStatusCollectionViewCell.contentParagraphStyle
) )
@ -122,7 +121,8 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
} }
private lazy var nameHStack = UIStackView(arrangedSubviews: [ private lazy var nameHStack = UIStackView(arrangedSubviews: [
displayAndUserNameLabel, displayNameLabel,
usernameLabel,
pinImageView, pinImageView,
timestampLabel, timestampLabel,
]).configure { ]).configure {
@ -130,10 +130,27 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
$0.spacing = 4 $0.spacing = 4
} }
let displayAndUserNameLabel = AccountDisplayAndUserNameLabel().configure { let displayNameLabel = AccountDisplayNameLabel().configure {
$0.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .body).addingAttributes([
.traits: [
UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold.rawValue,
]
]), size: 0)
$0.adjustsFontForContentSizeCategory = true
$0.setContentHuggingPriority(.init(251), for: .horizontal)
$0.setContentCompressionResistancePriority(.init(749), for: .horizontal)
}
let usernameLabel = UILabel().configure {
$0.textColor = .secondaryLabel
$0.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .body).addingAttributes([
.traits: [
UIFontDescriptor.TraitKey.weight: UIFont.Weight.light.rawValue,
]
]), size: 0)
$0.adjustsFontForContentSizeCategory = true $0.adjustsFontForContentSizeCategory = true
$0.setContentHuggingPriority(.init(249), for: .horizontal) $0.setContentHuggingPriority(.init(249), for: .horizontal)
$0.setContentCompressionResistancePriority(.init(749), for: .horizontal) $0.setContentCompressionResistancePriority(.init(748), for: .horizontal)
} }
private let pinImageView = UIImageView(image: UIImage(systemName: "pin.fill")).configure { private let pinImageView = UIImageView(image: UIImage(systemName: "pin.fill")).configure {
@ -676,11 +693,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
.store(in: &cancellables) .store(in: &cancellables)
} }
func updateAccountUI(account: AccountMO) {
baseUpdateAccountUI(account: account)
displayAndUserNameLabel.updateUI(account: account)
}
func updateUIForPreferences(status: StatusMO) { func updateUIForPreferences(status: StatusMO) {
baseUpdateUIForPreferences(status: status) baseUpdateUIForPreferences(status: status)
@ -692,8 +704,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
metaIndicatorsView.updateUI(status: status) metaIndicatorsView.updateUI(status: status)
timelineReasonIcon.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * Self.timelineReasonIconSize timelineReasonIcon.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * Self.timelineReasonIconSize
displayAndUserNameLabel.updateUI(account: status.account)
} }
func updateStatusState(status: StatusMO) { func updateStatusState(status: StatusMO) {

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 = 129 CURRENT_PROJECT_VERSION = 128
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