Compare commits

...

10 Commits

20 changed files with 165 additions and 59 deletions

View File

@ -1,5 +1,16 @@
# 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)
Features/Improvements:
- Add report UI

View File

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

View File

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

View File

@ -39,7 +39,9 @@ class OpenInSafariActivity: UIActivity {
static func completionHandler(navigator: TuskerNavigationDelegate, url: URL) -> UIActivityViewController.CompletionWithItemsHandler {
return { (activityType, _, _, _) in
if activityType == .openInSafari {
navigator.show(SFSafariViewController(url: url))
let vc = 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"?>
<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">
<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">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -22,10 +22,10 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="a8U-KI-8PM">
<rect key="frame" x="0.0" y="44" width="414" height="818"/>
<rect key="frame" x="0.0" y="48" width="414" height="814"/>
<subviews>
<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="722"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="718"/>
<subviews>
<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"/>
@ -46,11 +46,12 @@
<viewLayoutGuide key="frameLayoutGuide" id="Rgd-t7-8QN"/>
</scrollView>
<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="730" width="310.5" height="50"/>
<color key="backgroundColor" systemColor="systemBlueColor"/>
<rect key="frame" x="52" y="726" width="310.5" height="50"/>
<color key="backgroundColor" systemColor="tintColor"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="jHf-W0-qQn"/>
</constraints>
<color key="tintColor" systemColor="tintColor"/>
<state key="normal" title="Send Report">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
@ -62,7 +63,7 @@
</connections>
</button>
<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="788" width="77" height="30"/>
<rect key="frame" x="168.5" y="784" width="77" height="30"/>
<state key="normal" title="Don't Send"/>
<connections>
<action selector="cancelPressed:" destination="-1" eventType="touchUpInside" id="o4R-0Q-STS"/>
@ -87,12 +88,12 @@
</objects>
<resources>
<systemColor name="labelColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBlueColor">
<systemColor name="tintColor">
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>

View File

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

View File

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

View File

@ -60,7 +60,11 @@ class TrendingStatusesViewController: UIViewController {
}
return config
}
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
}
view = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dragDelegate = self

View File

@ -10,19 +10,25 @@ import UIKit
class FastSwitchingAccountView: UIView {
private static let selectedColor = UIColor { (traits) in
if traits.userInterfaceStyle == .dark {
return UIColor(hue: 211 / 360, saturation: 85 / 100, brightness: 100 / 100, alpha: 1)
} else {
return UIColor(hue: 211 / 360, saturation: 70 / 100, brightness: 100 / 100, alpha: 1)
}
private lazy var selectedColor = UIColor { [unowned self] (traits) in
var hue: CGFloat = 0
var saturation: CGFloat = 0
var brightness: CGFloat = 0
var alpha: CGFloat = 0
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 static let currentColor = UIColor { (traits) in
if traits.userInterfaceStyle == .dark {
return UIColor(hue: 211 / 360, saturation: 85 / 100, brightness: 85 / 100, alpha: 1)
} else {
return UIColor(hue: 211 / 360, saturation: 50 / 100, brightness: 100 / 100, alpha: 1)
}
private lazy var currentColor = UIColor { [unowned self] (traits) in
var hue: CGFloat = 0
var saturation: CGFloat = 0
var brightness: CGFloat = 0
var alpha: CGFloat = 0
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 {
didSet {
@ -139,9 +145,9 @@ class FastSwitchingAccountView: UIView {
private func updateLabelColors() {
let color: UIColor
if isSelected {
color = FastSwitchingAccountView.selectedColor
color = selectedColor
} else if isCurrent {
color = FastSwitchingAccountView.currentColor
color = currentColor
} else {
color = .white
}

View File

@ -80,7 +80,15 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
}
return config
}
let layout = UICollectionViewCompositionalLayout.list(using: config)
let layout = UICollectionViewCompositionalLayout { [unowned self] sectionIndex, environment in
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)
collectionView.delegate = self
collectionView.dragDelegate = self

View File

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

View File

@ -9,6 +9,7 @@
import UIKit
import Pachyderm
import Combine
import Sentry
class TimelineViewController: UIViewController, TimelineLikeCollectionViewController, CollectionViewController, RefreshableViewController {
let timeline: Timeline
@ -86,7 +87,12 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
}
return config
}
let layout = UICollectionViewCompositionalLayout.list(using: config)
// just setting layout.configuration.contentInsetsReference doesn't work with UICollectionViewCompositionalLayout.list
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.delegate = self
collectionView.dragDelegate = self
@ -335,7 +341,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
}
loadViewIfNeeded()
var loaded = false
await controller.restoreInitial {
await controller.restoreInitial { @MainActor in
let hasStatusesToRestore = await loadStatusesToRestore(position: position)
if hasStatusesToRestore {
applyItemsToRestore(position: position)
@ -351,26 +357,45 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
guard !unloaded.isEmpty else {
return true
}
let statuses = await withTaskGroup(of: Status?.self) { group -> [Status] in
let results = await withTaskGroup(of: (String, Result<Status, Swift.Error>).self) { group -> [(String, Result<Status, Swift.Error>)] in
for id in unloaded {
group.addTask {
do {
let (status, _) = try await self.mastodonController.run(Client.getStatus(id: id))
return status
return (id, .success(status))
} catch {
print(error)
return nil
return (id, .failure(error))
}
}
}
return await group.reduce(into: []) { partialResult, status in
if let status {
partialResult.append(status)
}
return await group.reduce(into: []) { partialResult, result in
partialResult.append(result)
}
}
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)
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
if let center = position.centerStatusID {
let nearestLoadedStatusToCenter = position.statusIDs[position.statusIDs.firstIndex(of: center)!...].first(where: { id in
@ -383,9 +408,17 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
!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
}
@MainActor
private func applyItemsToRestore(position: TimelinePosition) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.statuses])
@ -393,6 +426,12 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
let centerStatusID = position.centerStatusID
let items = position.statusIDs.map { Item.status(id: $0, collapseState: .unknown, filterState: .unknown) }
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) {
if let centerStatusID,
let index = statusIDs.firstIndex(of: centerStatusID),
@ -426,7 +465,12 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
private func filterResult(state: FilterState, statusID: String) -> (Filterer.Result, NSAttributedString?) {
let status = {
let status = self.mastodonController.persistentContainer.status(for: statusID)!
guard let status = self.mastodonController.persistentContainer.status(for: statusID) else {
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 let reblogged = status.reblog {
return (reblogged, true)

View File

@ -70,8 +70,8 @@ extension MenuActionProvider {
accountID != ownAccount.id {
actionsSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.followAction(for: $0, mastodonController: $1) }))
actionsSection.append(UIDeferredMenuElement.uncached({ elementHandler in
let listActions = mastodonController.lists.map { list in
UIAction(title: list.title, image: UIImage(systemName: "plus")) { [unowned self] _ in
var listActions = mastodonController.lists.map { list in
UIAction(title: list.title, image: UIImage(systemName: "list.bullet")) { [unowned self] _ in
let req = List.add(list, accounts: [accountID])
mastodonController.run(req) { response in
if case .failure(let error) = response {
@ -80,6 +80,21 @@ 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)])
}))
suppressSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.blockAction(for: $0, mastodonController: $1) }))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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