Compare commits
9 Commits
ac0dedfd3d
...
0653d695d9
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 0653d695d9 | |
Shadowfacts | 4811747790 | |
Shadowfacts | ed2519848c | |
Shadowfacts | b1374b12a3 | |
Shadowfacts | c5a25eecf1 | |
Shadowfacts | a4dbf3ddbb | |
Shadowfacts | be3a61ebc7 | |
Shadowfacts | ababa4b428 | |
Shadowfacts | d75c2558ca |
|
@ -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) { [unowned self] toast in
|
config = ToastConfiguration(from: error, with: "Error Unfollowing Hashtag", in: presenter) { 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) { [unowned self] toast in
|
config = ToastConfiguration(from: error, with: "Error Following Hashtag", in: presenter) { toast in
|
||||||
toast.dismissToast(animated: true)
|
toast.dismissToast(animated: true)
|
||||||
await self.toggleFollow()
|
await self.toggleFollow()
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,9 @@ 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 {
|
||||||
navigator.show(SFSafariViewController(url: url))
|
let vc = SFSafariViewController(url: url)
|
||||||
|
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||||
|
navigator.show(vc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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="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"/>
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<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="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="44" width="414" height="818"/>
|
<rect key="frame" x="0.0" y="48" width="414" height="814"/>
|
||||||
<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="722"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="718"/>
|
||||||
<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,11 +46,12 @@
|
||||||
<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="730" width="310.5" height="50"/>
|
<rect key="frame" x="52" y="726" width="310.5" height="50"/>
|
||||||
<color key="backgroundColor" systemColor="systemBlueColor"/>
|
<color key="backgroundColor" systemColor="tintColor"/>
|
||||||
<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>
|
||||||
|
@ -62,7 +63,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="788" width="77" height="30"/>
|
<rect key="frame" x="168.5" y="784" 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"/>
|
||||||
|
@ -87,12 +88,12 @@
|
||||||
</objects>
|
</objects>
|
||||||
<resources>
|
<resources>
|
||||||
<systemColor name="labelColor">
|
<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>
|
||||||
<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="systemBlueColor">
|
<systemColor name="tintColor">
|
||||||
<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>
|
||||||
|
|
|
@ -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 { indices in
|
.onDelete(perform: pinnedTimelines.count == 1 ? nil : { 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
|
||||||
|
|
|
@ -75,7 +75,9 @@ class TrendingLinksViewController: EnhancedTableViewController {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return UIContextMenuConfiguration(identifier: nil) {
|
return UIContextMenuConfiguration(identifier: nil) {
|
||||||
return SFSafariViewController(url: url)
|
let vc = 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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,11 @@ class TrendingStatusesViewController: UIViewController {
|
||||||
}
|
}
|
||||||
return config
|
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)
|
view = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||||
collectionView.delegate = self
|
collectionView.delegate = self
|
||||||
collectionView.dragDelegate = self
|
collectionView.dragDelegate = self
|
||||||
|
|
|
@ -10,19 +10,25 @@ import UIKit
|
||||||
|
|
||||||
class FastSwitchingAccountView: UIView {
|
class FastSwitchingAccountView: UIView {
|
||||||
|
|
||||||
private static let selectedColor = UIColor { (traits) in
|
private lazy var selectedColor = UIColor { [unowned self] (traits) in
|
||||||
if traits.userInterfaceStyle == .dark {
|
var hue: CGFloat = 0
|
||||||
return UIColor(hue: 211 / 360, saturation: 85 / 100, brightness: 100 / 100, alpha: 1)
|
var saturation: CGFloat = 0
|
||||||
} else {
|
var brightness: CGFloat = 0
|
||||||
return UIColor(hue: 211 / 360, saturation: 70 / 100, brightness: 100 / 100, alpha: 1)
|
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
|
private lazy var currentColor = UIColor { [unowned self] (traits) in
|
||||||
if traits.userInterfaceStyle == .dark {
|
var hue: CGFloat = 0
|
||||||
return UIColor(hue: 211 / 360, saturation: 85 / 100, brightness: 85 / 100, alpha: 1)
|
var saturation: CGFloat = 0
|
||||||
} else {
|
var brightness: CGFloat = 0
|
||||||
return UIColor(hue: 211 / 360, saturation: 50 / 100, brightness: 100 / 100, alpha: 1)
|
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 {
|
var isSelected = false {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -139,9 +145,9 @@ class FastSwitchingAccountView: UIView {
|
||||||
private func updateLabelColors() {
|
private func updateLabelColors() {
|
||||||
let color: UIColor
|
let color: UIColor
|
||||||
if isSelected {
|
if isSelected {
|
||||||
color = FastSwitchingAccountView.selectedColor
|
color = selectedColor
|
||||||
} else if isCurrent {
|
} else if isCurrent {
|
||||||
color = FastSwitchingAccountView.currentColor
|
color = currentColor
|
||||||
} else {
|
} else {
|
||||||
color = .white
|
color = .white
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,15 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
||||||
}
|
}
|
||||||
return config
|
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)
|
view = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||||
collectionView.delegate = self
|
collectionView.delegate = self
|
||||||
collectionView.dragDelegate = self
|
collectionView.dragDelegate = self
|
||||||
|
|
|
@ -278,7 +278,9 @@ extension SearchViewController: UICollectionViewDelegate {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return UIContextMenuConfiguration {
|
return UIContextMenuConfiguration {
|
||||||
SFSafariViewController(url: url)
|
let vc = 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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
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
|
||||||
|
@ -86,7 +87,12 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
}
|
}
|
||||||
return config
|
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 = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||||
collectionView.delegate = self
|
collectionView.delegate = self
|
||||||
collectionView.dragDelegate = self
|
collectionView.dragDelegate = self
|
||||||
|
@ -335,7 +341,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
}
|
}
|
||||||
loadViewIfNeeded()
|
loadViewIfNeeded()
|
||||||
var loaded = false
|
var loaded = false
|
||||||
await controller.restoreInitial {
|
await controller.restoreInitial { @MainActor in
|
||||||
let hasStatusesToRestore = await loadStatusesToRestore(position: position)
|
let hasStatusesToRestore = await loadStatusesToRestore(position: position)
|
||||||
if hasStatusesToRestore {
|
if hasStatusesToRestore {
|
||||||
applyItemsToRestore(position: position)
|
applyItemsToRestore(position: position)
|
||||||
|
@ -351,26 +357,45 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
guard !unloaded.isEmpty else {
|
guard !unloaded.isEmpty else {
|
||||||
return true
|
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 {
|
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 status
|
return (id, .success(status))
|
||||||
} catch {
|
} catch {
|
||||||
print(error)
|
return (id, .failure(error))
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await group.reduce(into: []) { partialResult, status in
|
return await group.reduce(into: []) { partialResult, result in
|
||||||
if let status {
|
partialResult.append(result)
|
||||||
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
|
||||||
|
@ -383,9 +408,17 @@ 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])
|
||||||
|
@ -393,6 +426,12 @@ 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),
|
||||||
|
@ -426,7 +465,12 @@ 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 = {
|
||||||
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 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)
|
||||||
|
|
|
@ -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
|
||||||
let listActions = mastodonController.lists.map { list in
|
var listActions = mastodonController.lists.map { list in
|
||||||
UIAction(title: list.title, image: UIImage(systemName: "plus")) { [unowned self] _ in
|
UIAction(title: list.title, image: UIImage(systemName: "list.bullet")) { [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,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)])
|
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) }))
|
||||||
|
|
|
@ -257,6 +257,7 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,9 @@ 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
|
||||||
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) {
|
} else if UIApplication.shared.canOpenURL(url) {
|
||||||
UIApplication.shared.open(url, options: [:])
|
UIApplication.shared.open(url, options: [:])
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -185,7 +185,9 @@ 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" {
|
||||||
return SFSafariViewController(url: url)
|
let vc = SFSafariViewController(url: url)
|
||||||
|
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||||
|
return vc
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -229,7 +229,9 @@ 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) {
|
||||||
return SFSafariViewController(url: URL(card.url)!)
|
let vc = 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)
|
||||||
|
|
|
@ -71,11 +71,15 @@ 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.tags!["underlying_error"] = String(describing: error)
|
event.extra = [
|
||||||
|
"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.tags!["underlying_error"] = error
|
event.extra = [
|
||||||
|
"underlying_error": String(describing: error)
|
||||||
|
]
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue