Compare commits

..

No commits in common. "566df3e285bb48437e971aa941876bd5c447aa9c" and "ac0dedfd3d6c6b6414be35204d7246b4e6f9ecc9" have entirely different histories.

20 changed files with 59 additions and 165 deletions

View File

@ -1,16 +1,5 @@
# Changelog # Changelog
## 2023.1 (62)
Features/Improvements:
- Add New List action in Add to List menu
Bugfixes:
- Fix crash when retrying follow hashtag
- Fix separators on timeline not being properly inset
- Fix various elements not adjusting to the accent color preference
- Prevent all pinned timelines from being removed, which would previously crash
- Fix crash when handling search activity
## 2023.1 (61) ## 2023.1 (61)
Features/Improvements: Features/Improvements:
- Add report UI - Add report UI

View File

@ -2314,7 +2314,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 62; CURRENT_PROJECT_VERSION = 61;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2382,7 +2382,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 62; CURRENT_PROJECT_VERSION = 61;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
@ -2533,7 +2533,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 62; CURRENT_PROJECT_VERSION = 61;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2562,7 +2562,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 62; CURRENT_PROJECT_VERSION = 61;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2672,7 +2672,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 62; CURRENT_PROJECT_VERSION = 61;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
@ -2698,7 +2698,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 62; CURRENT_PROJECT_VERSION = 61;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;

View File

@ -37,7 +37,7 @@ class ToggleFollowHashtagService {
config.systemImageName = "checkmark" config.systemImageName = "checkmark"
config.dismissAutomaticallyAfter = 2 config.dismissAutomaticallyAfter = 2
} catch { } catch {
config = ToastConfiguration(from: error, with: "Error Unfollowing Hashtag", in: presenter) { toast in config = ToastConfiguration(from: error, with: "Error Unfollowing Hashtag", in: presenter) { [unowned self] toast in
toast.dismissToast(animated: true) toast.dismissToast(animated: true)
await self.toggleFollow() await self.toggleFollow()
} }
@ -54,7 +54,7 @@ class ToggleFollowHashtagService {
config.systemImageName = "checkmark" config.systemImageName = "checkmark"
config.dismissAutomaticallyAfter = 2 config.dismissAutomaticallyAfter = 2
} catch { } catch {
config = ToastConfiguration(from: error, with: "Error Following Hashtag", in: presenter) { toast in config = ToastConfiguration(from: error, with: "Error Following Hashtag", in: presenter) { [unowned self] toast in
toast.dismissToast(animated: true) toast.dismissToast(animated: true)
await self.toggleFollow() await self.toggleFollow()
} }

View File

@ -39,9 +39,7 @@ class OpenInSafariActivity: UIActivity {
static func completionHandler(navigator: TuskerNavigationDelegate, url: URL) -> UIActivityViewController.CompletionWithItemsHandler { static func completionHandler(navigator: TuskerNavigationDelegate, url: URL) -> UIActivityViewController.CompletionWithItemsHandler {
return { (activityType, _, _, _) in return { (activityType, _, _, _) in
if activityType == .openInSafari { if activityType == .openInSafari {
let vc = SFSafariViewController(url: url) navigator.show(SFSafariViewController(url: url))
vc.preferredControlTintColor = Preferences.shared.accentColor.color
navigator.show(vc)
} }
} }
} }

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -22,10 +22,10 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="a8U-KI-8PM"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="a8U-KI-8PM">
<rect key="frame" x="0.0" y="48" width="414" height="814"/> <rect key="frame" x="0.0" y="44" width="414" height="818"/>
<subviews> <subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="uQy-Yw-Dba"> <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="uQy-Yw-Dba">
<rect key="frame" x="0.0" y="0.0" width="414" height="718"/> <rect key="frame" x="0.0" y="0.0" width="414" height="722"/>
<subviews> <subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" verticalHuggingPriority="249" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="hxN-7J-Usc"> <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" verticalHuggingPriority="249" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="hxN-7J-Usc">
<rect key="frame" x="0.0" y="0.0" width="414" height="166.5"/> <rect key="frame" x="0.0" y="0.0" width="414" height="166.5"/>
@ -46,12 +46,11 @@
<viewLayoutGuide key="frameLayoutGuide" id="Rgd-t7-8QN"/> <viewLayoutGuide key="frameLayoutGuide" id="Rgd-t7-8QN"/>
</scrollView> </scrollView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ofm-5l-nAp"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ofm-5l-nAp">
<rect key="frame" x="52" y="726" width="310.5" height="50"/> <rect key="frame" x="52" y="730" width="310.5" height="50"/>
<color key="backgroundColor" systemColor="tintColor"/> <color key="backgroundColor" systemColor="systemBlueColor"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="50" id="jHf-W0-qQn"/> <constraint firstAttribute="height" constant="50" id="jHf-W0-qQn"/>
</constraints> </constraints>
<color key="tintColor" systemColor="tintColor"/>
<state key="normal" title="Send Report"> <state key="normal" title="Send Report">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state> </state>
@ -63,7 +62,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JiJ-Ng-jOz"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JiJ-Ng-jOz">
<rect key="frame" x="168.5" y="784" width="77" height="30"/> <rect key="frame" x="168.5" y="788" width="77" height="30"/>
<state key="normal" title="Don't Send"/> <state key="normal" title="Don't Send"/>
<connections> <connections>
<action selector="cancelPressed:" destination="-1" eventType="touchUpInside" id="o4R-0Q-STS"/> <action selector="cancelPressed:" destination="-1" eventType="touchUpInside" id="o4R-0Q-STS"/>
@ -88,12 +87,12 @@
</objects> </objects>
<resources> <resources>
<systemColor name="labelColor"> <systemColor name="labelColor">
<color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor> </systemColor>
<systemColor name="systemBackgroundColor"> <systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor> </systemColor>
<systemColor name="tintColor"> <systemColor name="systemBlueColor">
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor> </systemColor>
</resources> </resources>

View File

@ -49,9 +49,9 @@ struct PinnedTimelinesView: View {
.onMove { indices, newOffset in .onMove { indices, newOffset in
pinnedTimelines.move(fromOffsets: indices, toOffset: newOffset) pinnedTimelines.move(fromOffsets: indices, toOffset: newOffset)
} }
.onDelete(perform: pinnedTimelines.count == 1 ? nil : { indices in .onDelete { indices in
pinnedTimelines.remove(atOffsets: indices) pinnedTimelines.remove(atOffsets: indices)
}) }
Menu { Menu {
ForEach([Timeline.home, .public(local: true), .public(local: false)], id: \.id) { timeline in ForEach([Timeline.home, .public(local: true), .public(local: false)], id: \.id) { timeline in

View File

@ -75,9 +75,7 @@ class TrendingLinksViewController: EnhancedTableViewController {
return nil return nil
} }
return UIContextMenuConfiguration(identifier: nil) { return UIContextMenuConfiguration(identifier: nil) {
let vc = SFSafariViewController(url: url) return SFSafariViewController(url: url)
vc.preferredControlTintColor = Preferences.shared.accentColor.color
return vc
} actionProvider: { _ in } actionProvider: { _ in
return UIMenu(children: self.actionsForTrendingLink(card: item.card)) return UIMenu(children: self.actionsForTrendingLink(card: item.card))
} }

View File

@ -60,11 +60,7 @@ class TrendingStatusesViewController: UIViewController {
} }
return config return config
} }
let layout = UICollectionViewCompositionalLayout { sectionIndex, environment in let layout = UICollectionViewCompositionalLayout.list(using: config)
let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment)
section.contentInsetsReference = .readableContent
return section
}
view = UICollectionView(frame: .zero, collectionViewLayout: layout) view = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self

View File

@ -10,25 +10,19 @@ import UIKit
class FastSwitchingAccountView: UIView { class FastSwitchingAccountView: UIView {
private lazy var selectedColor = UIColor { [unowned self] (traits) in private static let selectedColor = UIColor { (traits) in
var hue: CGFloat = 0 if traits.userInterfaceStyle == .dark {
var saturation: CGFloat = 0 return UIColor(hue: 211 / 360, saturation: 85 / 100, brightness: 100 / 100, alpha: 1)
var brightness: CGFloat = 0 } else {
var alpha: CGFloat = 0 return UIColor(hue: 211 / 360, saturation: 70 / 100, brightness: 100 / 100, alpha: 1)
self.tintColor.resolvedColor(with: traits).getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) }
brightness = min(1, brightness + 0.4)
saturation = max(0, saturation - 0.3)
return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
} }
private lazy var currentColor = UIColor { [unowned self] (traits) in private static let currentColor = UIColor { (traits) in
var hue: CGFloat = 0 if traits.userInterfaceStyle == .dark {
var saturation: CGFloat = 0 return UIColor(hue: 211 / 360, saturation: 85 / 100, brightness: 85 / 100, alpha: 1)
var brightness: CGFloat = 0 } else {
var alpha: CGFloat = 0 return UIColor(hue: 211 / 360, saturation: 50 / 100, brightness: 100 / 100, alpha: 1)
self.tintColor.resolvedColor(with: traits).getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) }
brightness = min(1, brightness + 0.3)
saturation = max(0, saturation - 0.2)
return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
} }
var isSelected = false { var isSelected = false {
didSet { didSet {
@ -145,9 +139,9 @@ class FastSwitchingAccountView: UIView {
private func updateLabelColors() { private func updateLabelColors() {
let color: UIColor let color: UIColor
if isSelected { if isSelected {
color = selectedColor color = FastSwitchingAccountView.selectedColor
} else if isCurrent { } else if isCurrent {
color = currentColor color = FastSwitchingAccountView.currentColor
} else { } else {
color = .white color = .white
} }

View File

@ -80,15 +80,7 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
} }
return config return config
} }
let layout = UICollectionViewCompositionalLayout { [unowned self] sectionIndex, environment in let layout = UICollectionViewCompositionalLayout.list(using: config)
if case .header = dataSource.sectionIdentifier(for: sectionIndex) {
return .list(using: .init(appearance: .plain), layoutEnvironment: environment)
} else {
let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment)
section.contentInsetsReference = .readableContent
return section
}
}
view = UICollectionView(frame: .zero, collectionViewLayout: layout) view = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self

View File

@ -278,9 +278,7 @@ extension SearchViewController: UICollectionViewDelegate {
return nil return nil
} }
return UIContextMenuConfiguration { return UIContextMenuConfiguration {
let vc = SFSafariViewController(url: url) SFSafariViewController(url: url)
vc.preferredControlTintColor = Preferences.shared.accentColor.color
return vc
} actionProvider: { _ in } actionProvider: { _ in
UIMenu(children: self.actionsForTrendingLink(card: card)) UIMenu(children: self.actionsForTrendingLink(card: card))
} }

View File

@ -9,7 +9,6 @@
import UIKit import UIKit
import Pachyderm import Pachyderm
import Combine import Combine
import Sentry
class TimelineViewController: UIViewController, TimelineLikeCollectionViewController, CollectionViewController, RefreshableViewController { class TimelineViewController: UIViewController, TimelineLikeCollectionViewController, CollectionViewController, RefreshableViewController {
let timeline: Timeline let timeline: Timeline
@ -87,12 +86,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
} }
return config return config
} }
// just setting layout.configuration.contentInsetsReference doesn't work with UICollectionViewCompositionalLayout.list let layout = UICollectionViewCompositionalLayout.list(using: config)
let layout = UICollectionViewCompositionalLayout { sectionIndex, environment in
let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment)
section.contentInsetsReference = .readableContent
return section
}
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self
@ -341,7 +335,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
} }
loadViewIfNeeded() loadViewIfNeeded()
var loaded = false var loaded = false
await controller.restoreInitial { @MainActor in await controller.restoreInitial {
let hasStatusesToRestore = await loadStatusesToRestore(position: position) let hasStatusesToRestore = await loadStatusesToRestore(position: position)
if hasStatusesToRestore { if hasStatusesToRestore {
applyItemsToRestore(position: position) applyItemsToRestore(position: position)
@ -357,45 +351,26 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
guard !unloaded.isEmpty else { guard !unloaded.isEmpty else {
return true return true
} }
let results = await withTaskGroup(of: (String, Result<Status, Swift.Error>).self) { group -> [(String, Result<Status, Swift.Error>)] in let statuses = await withTaskGroup(of: Status?.self) { group -> [Status] in
for id in unloaded { for id in unloaded {
group.addTask { group.addTask {
do { do {
let (status, _) = try await self.mastodonController.run(Client.getStatus(id: id)) let (status, _) = try await self.mastodonController.run(Client.getStatus(id: id))
return (id, .success(status)) return status
} catch { } catch {
return (id, .failure(error)) print(error)
return nil
} }
} }
} }
return await group.reduce(into: []) { partialResult, result in return await group.reduce(into: []) { partialResult, status in
partialResult.append(result) if let status {
} partialResult.append(status)
} }
var statuses = [Status]()
for (id, result) in results {
switch result {
case .success(let status):
statuses.append(status)
case .failure(let error):
let crumb = Breadcrumb(level: .error, category: "TimelineViewController")
crumb.message = "Error loading status"
crumb.data = [
"error": String(describing: error),
"id": id
]
SentrySDK.addBreadcrumb(crumb: crumb)
} }
} }
await mastodonController.persistentContainer.addAll(statuses: statuses, in: mastodonController.persistentContainer.viewContext) await mastodonController.persistentContainer.addAll(statuses: statuses, in: mastodonController.persistentContainer.viewContext)
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
crumb.message = "Original position statusIDs"
crumb.data = [
"statusIDs": position.statusIDs,
]
SentrySDK.addBreadcrumb(crumb: crumb)
// 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 nearestLoadedStatusToCenter = position.statusIDs[position.statusIDs.firstIndex(of: center)!...].first(where: { id in let nearestLoadedStatusToCenter = position.statusIDs[position.statusIDs.firstIndex(of: center)!...].first(where: { id in
@ -408,17 +383,9 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
!unloaded.contains(id) || statuses.contains(where: { $0.id == id }) !unloaded.contains(id) || statuses.contains(where: { $0.id == id })
} }
let crumb2 = Breadcrumb(level: .info, category: "TimelineViewController")
crumb2.message = "Filtered position statusIDs"
crumb2.data = [
"statusIDs": position.statusIDs,
]
SentrySDK.addBreadcrumb(crumb: crumb2)
return !position.statusIDs.isEmpty return !position.statusIDs.isEmpty
} }
@MainActor
private func applyItemsToRestore(position: TimelinePosition) { private func applyItemsToRestore(position: TimelinePosition) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>() var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.statuses]) snapshot.appendSections([.statuses])
@ -426,12 +393,6 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
let centerStatusID = position.centerStatusID let centerStatusID = position.centerStatusID
let items = position.statusIDs.map { Item.status(id: $0, collapseState: .unknown, filterState: .unknown) } let items = position.statusIDs.map { Item.status(id: $0, collapseState: .unknown, filterState: .unknown) }
snapshot.appendItems(items, toSection: .statuses) snapshot.appendItems(items, toSection: .statuses)
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
crumb.message = "Restoring statuses"
crumb.data = [
"statusIDs": position.statusIDs
]
SentrySDK.addBreadcrumb(crumb: crumb)
dataSource.apply(snapshot, animatingDifferences: false) { dataSource.apply(snapshot, animatingDifferences: false) {
if let centerStatusID, if let centerStatusID,
let index = statusIDs.firstIndex(of: centerStatusID), let index = statusIDs.firstIndex(of: centerStatusID),
@ -465,12 +426,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
private func filterResult(state: FilterState, statusID: String) -> (Filterer.Result, NSAttributedString?) { private func filterResult(state: FilterState, statusID: String) -> (Filterer.Result, NSAttributedString?) {
let status = { let status = {
guard let status = self.mastodonController.persistentContainer.status(for: statusID) else { let status = self.mastodonController.persistentContainer.status(for: statusID)!
let crumb = Breadcrumb(level: .fatal, category: "TimelineViewController")
crumb.message = "Looking up status \(statusID)"
SentrySDK.addBreadcrumb(crumb: crumb)
preconditionFailure("Missing status for filtering")
}
// if the status is a reblog of another one, filter based on that one // if the status is a reblog of another one, filter based on that one
if let reblogged = status.reblog { if let reblogged = status.reblog {
return (reblogged, true) return (reblogged, true)

View File

@ -70,8 +70,8 @@ extension MenuActionProvider {
accountID != ownAccount.id { accountID != ownAccount.id {
actionsSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.followAction(for: $0, mastodonController: $1) })) actionsSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.followAction(for: $0, mastodonController: $1) }))
actionsSection.append(UIDeferredMenuElement.uncached({ elementHandler in actionsSection.append(UIDeferredMenuElement.uncached({ elementHandler in
var listActions = mastodonController.lists.map { list in let listActions = mastodonController.lists.map { list in
UIAction(title: list.title, image: UIImage(systemName: "list.bullet")) { [unowned self] _ in UIAction(title: list.title, image: UIImage(systemName: "plus")) { [unowned self] _ in
let req = List.add(list, accounts: [accountID]) let req = List.add(list, accounts: [accountID])
mastodonController.run(req) { response in mastodonController.run(req) { response in
if case .failure(let error) = response { if case .failure(let error) = response {
@ -80,21 +80,6 @@ extension MenuActionProvider {
} }
} }
} }
listActions.append(UIAction(title: "New List…", image: UIImage(systemName: "plus"), handler: { [unowned self] _ in
Task { @MainActor in
let service = CreateListService(mastodonController: mastodonController, present: { [unowned self] in
self.navigationDelegate!.present($0, animated: true)
}) { list in
let req = List.add(list, accounts: [accountID])
mastodonController.run(req) { response in
if case .failure(let error) = response {
self.handleError(error, title: "Error Adding to List")
}
}
}
service.run()
}
}))
elementHandler([UIMenu(title: "Add to List", image: UIImage(systemName: "list.bullet"), children: listActions)]) elementHandler([UIMenu(title: "Add to List", image: UIImage(systemName: "list.bullet"), children: listActions)])
})) }))
suppressSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.blockAction(for: $0, mastodonController: $1) })) suppressSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.blockAction(for: $0, mastodonController: $1) }))

View File

@ -257,7 +257,6 @@ class UserActivityManager {
if let navigationController = mainViewController.getTabController(tab: .explore) as? UINavigationController, if let navigationController = mainViewController.getTabController(tab: .explore) as? UINavigationController,
let exploreController = navigationController.viewControllers.first as? ExploreViewController { let exploreController = navigationController.viewControllers.first as? ExploreViewController {
navigationController.popToRootViewController(animated: false) navigationController.popToRootViewController(animated: false)
exploreController.loadViewIfNeeded()
exploreController.searchController.isActive = true exploreController.searchController.isActive = true
exploreController.searchController.searchBar.becomeFirstResponder() exploreController.searchController.searchBar.becomeFirstResponder()
} }

View File

@ -50,9 +50,7 @@ extension TuskerNavigationDelegate {
url.scheme == "https" || url.scheme == "http" { url.scheme == "https" || url.scheme == "http" {
let config = SFSafariViewController.Configuration() let config = SFSafariViewController.Configuration()
config.entersReaderIfAvailable = Preferences.shared.inAppSafariAutomaticReaderMode config.entersReaderIfAvailable = Preferences.shared.inAppSafariAutomaticReaderMode
let vc = SFSafariViewController(url: url, configuration: config) present(SFSafariViewController(url: url, configuration: config), animated: true)
vc.preferredControlTintColor = Preferences.shared.accentColor.color
present(vc, animated: true)
} else if UIApplication.shared.canOpenURL(url) { } else if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:]) UIApplication.shared.open(url, options: [:])
} else { } else {

View File

@ -185,9 +185,7 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
} else if let tag = getHashtag(for: url, text: text) { } else if let tag = getHashtag(for: url, text: text) {
return HashtagTimelineViewController(for: tag, mastodonController: mastodonController!) return HashtagTimelineViewController(for: tag, mastodonController: mastodonController!)
} else if url.scheme == "https" || url.scheme == "http" { } else if url.scheme == "https" || url.scheme == "http" {
let vc = SFSafariViewController(url: url) return SFSafariViewController(url: url)
vc.preferredControlTintColor = Preferences.shared.accentColor.color
return vc
} else { } else {
return nil return nil
} }

View File

@ -56,7 +56,7 @@ class PollOptionView: UIView {
let fillView = UIView() let fillView = UIView()
fillView.translatesAutoresizingMaskIntoConstraints = false fillView.translatesAutoresizingMaskIntoConstraints = false
fillView.backgroundColor = .tintColor.withAlphaComponent(0.6) fillView.backgroundColor = tintColor.withAlphaComponent(0.6)
fillView.layer.zPosition = -1 fillView.layer.zPosition = -1
fillView.layer.cornerRadius = layer.cornerRadius fillView.layer.cornerRadius = layer.cornerRadius
addSubview(fillView) addSubview(fillView)

View File

@ -294,7 +294,7 @@ class BaseStatusTableViewCell: UITableViewCell {
} else { } else {
let view = UIView() let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .tintColor.withAlphaComponent(0.5) view.backgroundColor = tintColor.withAlphaComponent(0.5)
view.layer.cornerRadius = 2.5 view.layer.cornerRadius = 2.5
view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
prevThreadLinkView = view prevThreadLinkView = view
@ -316,7 +316,7 @@ class BaseStatusTableViewCell: UITableViewCell {
} else { } else {
let view = UIView() let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .tintColor.withAlphaComponent(0.5) view.backgroundColor = tintColor.withAlphaComponent(0.5)
view.layer.cornerRadius = 2.5 view.layer.cornerRadius = 2.5
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
nextThreadLinkView = view nextThreadLinkView = view

View File

@ -229,9 +229,7 @@ extension StatusCardView: UIContextMenuInteractionDelegate {
guard let card = card else { return nil } guard let card = card else { return nil }
return UIContextMenuConfiguration(identifier: nil) { return UIContextMenuConfiguration(identifier: nil) {
let vc = SFSafariViewController(url: URL(card.url)!) return SFSafariViewController(url: URL(card.url)!)
vc.preferredControlTintColor = Preferences.shared.accentColor.color
return vc
} actionProvider: { (_) in } actionProvider: { (_) in
let actions = self.actionProvider?.actionsForURL(URL(card.url)!, source: .view(self)) ?? [] let actions = self.actionProvider?.actionsForURL(URL(card.url)!, source: .view(self)) ?? []
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions) return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)

View File

@ -71,15 +71,11 @@ extension ToastConfiguration {
event.tags!["error_type"] = "invalid_response" event.tags!["error_type"] = "invalid_response"
case .invalidModel(let error): case .invalidModel(let error):
event.tags!["error_type"] = "invalid_model" event.tags!["error_type"] = "invalid_model"
event.extra = [ event.tags!["underlying_error"] = String(describing: error)
"underlying_error": String(describing: error)
]
case .mastodonError(let code, let error): case .mastodonError(let code, let error):
event.tags!["error_type"] = "mastodon_error" event.tags!["error_type"] = "mastodon_error"
event.tags!["response_code"] = "\(code)" event.tags!["response_code"] = "\(code)"
event.extra = [ event.tags!["underlying_error"] = error
"underlying_error": String(describing: error)
]
default: default:
break break
} }