mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-22 08:20:59 +00:00
Swiftformat
This commit is contained in:
parent
94d50fafc4
commit
b259b6739e
31 changed files with 164 additions and 165 deletions
|
@ -3,12 +3,12 @@ import AppAccount
|
||||||
import Conversations
|
import Conversations
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
|
import LinkPresentation
|
||||||
import Lists
|
import Lists
|
||||||
|
import Models
|
||||||
import Status
|
import Status
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Timeline
|
import Timeline
|
||||||
import LinkPresentation
|
|
||||||
import Models
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
extension View {
|
extension View {
|
||||||
|
@ -46,7 +46,7 @@ extension View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func withSheetDestinations(sheetDestinations: Binding<SheetDestinations?>) -> some View {
|
func withSheetDestinations(sheetDestinations: Binding<SheetDestinations?>) -> some View {
|
||||||
sheet(item: sheetDestinations) { destination in
|
sheet(item: sheetDestinations) { destination in
|
||||||
switch destination {
|
switch destination {
|
||||||
|
@ -99,7 +99,7 @@ extension View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func withEnvironments() -> some View {
|
func withEnvironments() -> some View {
|
||||||
environmentObject(CurrentAccount.shared)
|
environmentObject(CurrentAccount.shared)
|
||||||
.environmentObject(UserPreferences.shared)
|
.environmentObject(UserPreferences.shared)
|
||||||
|
@ -114,38 +114,39 @@ extension View {
|
||||||
struct ActivityView: UIViewControllerRepresentable {
|
struct ActivityView: UIViewControllerRepresentable {
|
||||||
let image: UIImage
|
let image: UIImage
|
||||||
let status: Status
|
let status: Status
|
||||||
|
|
||||||
class LinkDelegate: NSObject, UIActivityItemSource {
|
class LinkDelegate: NSObject, UIActivityItemSource {
|
||||||
let image: UIImage
|
let image: UIImage
|
||||||
let status: Status
|
let status: Status
|
||||||
|
|
||||||
init(image: UIImage, status: Status) {
|
init(image: UIImage, status: Status) {
|
||||||
self.image = image
|
self.image = image
|
||||||
self.status = status
|
self.status = status
|
||||||
}
|
}
|
||||||
|
|
||||||
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
|
func activityViewControllerLinkMetadata(_: UIActivityViewController) -> LPLinkMetadata? {
|
||||||
let imageProvider = NSItemProvider(object: image)
|
let imageProvider = NSItemProvider(object: image)
|
||||||
let metadata = LPLinkMetadata()
|
let metadata = LPLinkMetadata()
|
||||||
metadata.imageProvider = imageProvider
|
metadata.imageProvider = imageProvider
|
||||||
metadata.title = status.reblog?.content.asRawText ?? status.content.asRawText
|
metadata.title = status.reblog?.content.asRawText ?? status.content.asRawText
|
||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
func activityViewControllerPlaceholderItem(_: UIActivityViewController) -> Any {
|
||||||
image
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
func activityViewController(_ activityViewController: UIActivityViewController,
|
func activityViewController(_: UIActivityViewController,
|
||||||
itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
itemForActivityType _: UIActivity.ActivityType?) -> Any?
|
||||||
|
{
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
|
func makeUIViewController(context _: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
|
||||||
return UIActivityViewController(activityItems: [image, LinkDelegate(image: image, status: status)],
|
return UIActivityViewController(activityItems: [image, LinkDelegate(image: image, status: status)],
|
||||||
applicationActivities: nil)
|
applicationActivities: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityView>) {}
|
func updateUIViewController(_: UIActivityViewController, context _: UIViewControllerRepresentableContext<ActivityView>) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,8 @@ struct IceCubesApp: App {
|
||||||
|
|
||||||
private func badgeFor(tab: Tab) -> Int {
|
private func badgeFor(tab: Tab) -> Int {
|
||||||
if tab == .notifications && selectedTab != tab,
|
if tab == .notifications && selectedTab != tab,
|
||||||
let token = appAccountsManager.currentAccount.oauthToken {
|
let token = appAccountsManager.currentAccount.oauthToken
|
||||||
|
{
|
||||||
return watcher.unreadNotificationsCount + userPreferences.getNotificationsCount(for: token)
|
return watcher.unreadNotificationsCount + userPreferences.getNotificationsCount(for: token)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
@ -169,7 +170,8 @@ struct IceCubesApp: App {
|
||||||
}
|
}
|
||||||
selectedTab = newTab
|
selectedTab = newTab
|
||||||
if selectedTab == .notifications,
|
if selectedTab == .notifications,
|
||||||
let token = appAccountsManager.currentAccount.oauthToken {
|
let token = appAccountsManager.currentAccount.oauthToken
|
||||||
|
{
|
||||||
userPreferences.setNotification(count: 0, token: token)
|
userPreferences.setNotification(count: 0, token: token)
|
||||||
watcher.unreadNotificationsCount = 0
|
watcher.unreadNotificationsCount = 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ private struct SafariRouter: ViewModifier {
|
||||||
@EnvironmentObject private var routerPath: RouterPath
|
@EnvironmentObject private var routerPath: RouterPath
|
||||||
|
|
||||||
@StateObject private var safariManager = InAppSafariManager()
|
@StateObject private var safariManager = InAppSafariManager()
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
content
|
content
|
||||||
.environment(\.openURL, OpenURLAction { url in
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
|
@ -58,48 +58,48 @@ private struct SafariRouter: ViewModifier {
|
||||||
|
|
||||||
private class InAppSafariManager: NSObject, ObservableObject, SFSafariViewControllerDelegate {
|
private class InAppSafariManager: NSObject, ObservableObject, SFSafariViewControllerDelegate {
|
||||||
var windowScene: UIWindowScene?
|
var windowScene: UIWindowScene?
|
||||||
let viewController: UIViewController = UIViewController()
|
let viewController: UIViewController = .init()
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func open(_ url: URL) -> OpenURLAction.Result {
|
func open(_ url: URL) -> OpenURLAction.Result {
|
||||||
guard let windowScene = windowScene else { return .systemAction }
|
guard let windowScene = windowScene else { return .systemAction }
|
||||||
|
|
||||||
self.window = setupWindow(windowScene: windowScene)
|
window = setupWindow(windowScene: windowScene)
|
||||||
|
|
||||||
let configuration = SFSafariViewController.Configuration()
|
let configuration = SFSafariViewController.Configuration()
|
||||||
configuration.entersReaderIfAvailable = UserPreferences.shared.inAppBrowserReaderView
|
configuration.entersReaderIfAvailable = UserPreferences.shared.inAppBrowserReaderView
|
||||||
|
|
||||||
let safari = SFSafariViewController(url: url, configuration: configuration)
|
let safari = SFSafariViewController(url: url, configuration: configuration)
|
||||||
safari.preferredBarTintColor = UIColor(Theme.shared.primaryBackgroundColor)
|
safari.preferredBarTintColor = UIColor(Theme.shared.primaryBackgroundColor)
|
||||||
safari.preferredControlTintColor = UIColor(Theme.shared.tintColor)
|
safari.preferredControlTintColor = UIColor(Theme.shared.tintColor)
|
||||||
safari.delegate = self
|
safari.delegate = self
|
||||||
|
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
self?.viewController.present(safari, animated: true)
|
self?.viewController.present(safari, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .handled
|
return .handled
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupWindow(windowScene: UIWindowScene) -> UIWindow {
|
func setupWindow(windowScene: UIWindowScene) -> UIWindow {
|
||||||
let window = self.window ?? UIWindow(windowScene: windowScene)
|
let window = self.window ?? UIWindow(windowScene: windowScene)
|
||||||
|
|
||||||
window.rootViewController = viewController
|
window.rootViewController = viewController
|
||||||
window.makeKeyAndVisible()
|
window.makeKeyAndVisible()
|
||||||
|
|
||||||
switch Theme.shared.selectedScheme {
|
switch Theme.shared.selectedScheme {
|
||||||
case .dark:
|
case .dark:
|
||||||
window.overrideUserInterfaceStyle = .dark
|
window.overrideUserInterfaceStyle = .dark
|
||||||
case .light:
|
case .light:
|
||||||
window.overrideUserInterfaceStyle = .light
|
window.overrideUserInterfaceStyle = .light
|
||||||
}
|
}
|
||||||
|
|
||||||
self.window = window
|
self.window = window
|
||||||
return window
|
return window
|
||||||
}
|
}
|
||||||
|
|
||||||
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
|
func safariViewControllerDidFinish(_: SFSafariViewController) {
|
||||||
window?.resignKey()
|
window?.resignKey()
|
||||||
window?.isHidden = false
|
window?.isHidden = false
|
||||||
window = nil
|
window = nil
|
||||||
|
@ -109,31 +109,30 @@ private class InAppSafariManager: NSObject, ObservableObject, SFSafariViewContro
|
||||||
private struct WindowReader: UIViewRepresentable {
|
private struct WindowReader: UIViewRepresentable {
|
||||||
var onUpdate: (UIWindow) -> Void
|
var onUpdate: (UIWindow) -> Void
|
||||||
|
|
||||||
func makeUIView(context: Context) -> InjectView {
|
func makeUIView(context _: Context) -> InjectView {
|
||||||
InjectView(onUpdate: onUpdate)
|
InjectView(onUpdate: onUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIView(_ uiView: InjectView, context: Context) {
|
func updateUIView(_: InjectView, context _: Context) {}
|
||||||
}
|
|
||||||
|
|
||||||
class InjectView: UIView {
|
class InjectView: UIView {
|
||||||
var onUpdate: (UIWindow) -> Void
|
var onUpdate: (UIWindow) -> Void
|
||||||
|
|
||||||
init(onUpdate: @escaping (UIWindow) -> Void) {
|
init(onUpdate: @escaping (UIWindow) -> Void) {
|
||||||
self.onUpdate = onUpdate
|
self.onUpdate = onUpdate
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
isHidden = true
|
isHidden = true
|
||||||
isUserInteractionEnabled = false
|
isUserInteractionEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder _: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willMove(toWindow newWindow: UIWindow?) {
|
override func willMove(toWindow newWindow: UIWindow?) {
|
||||||
super.willMove(toWindow: newWindow)
|
super.willMove(toWindow: newWindow)
|
||||||
|
|
||||||
if let window = newWindow {
|
if let window = newWindow {
|
||||||
onUpdate(window)
|
onUpdate(window)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -20,12 +20,13 @@ struct SideBarView<Content: View>: View {
|
||||||
|
|
||||||
private func badgeFor(tab: Tab) -> Int {
|
private func badgeFor(tab: Tab) -> Int {
|
||||||
if tab == .notifications && selectedTab != tab,
|
if tab == .notifications && selectedTab != tab,
|
||||||
let token = appAccounts.currentAccount.oauthToken {
|
let token = appAccounts.currentAccount.oauthToken
|
||||||
|
{
|
||||||
return watcher.unreadNotificationsCount + userPreferences.getNotificationsCount(for: token)
|
return watcher.unreadNotificationsCount + userPreferences.getNotificationsCount(for: token)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeIconForTab(tab: Tab) -> some View {
|
private func makeIconForTab(tab: Tab) -> some View {
|
||||||
ZStack(alignment: .topTrailing) {
|
ZStack(alignment: .topTrailing) {
|
||||||
SideBarIcon(systemIconName: tab.iconName,
|
SideBarIcon(systemIconName: tab.iconName,
|
||||||
|
@ -37,7 +38,7 @@ struct SideBarView<Content: View>: View {
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.frame(width: .sidebarWidth, height: 50)
|
.frame(width: .sidebarWidth, height: 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeBadgeView(count: Int) -> some View {
|
private func makeBadgeView(count: Int) -> some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Circle()
|
Circle()
|
||||||
|
@ -78,8 +79,9 @@ struct SideBarView<Content: View>: View {
|
||||||
ZStack(alignment: .topTrailing) {
|
ZStack(alignment: .topTrailing) {
|
||||||
AppAccountView(viewModel: .init(appAccount: account, isCompact: true))
|
AppAccountView(viewModel: .init(appAccount: account, isCompact: true))
|
||||||
if showBadge,
|
if showBadge,
|
||||||
let token = account.oauthToken,
|
let token = account.oauthToken,
|
||||||
userPreferences.getNotificationsCount(for: token) > 0 {
|
userPreferences.getNotificationsCount(for: token) > 0
|
||||||
|
{
|
||||||
makeBadgeView(count: userPreferences.getNotificationsCount(for: token))
|
makeBadgeView(count: userPreferences.getNotificationsCount(for: token))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ struct DisplaySettingsView: View {
|
||||||
theme.chosenFont = UIFont(name: "OpenDyslexic", size: 1)
|
theme.chosenFont = UIFont(name: "OpenDyslexic", size: 1)
|
||||||
case .hyperLegible:
|
case .hyperLegible:
|
||||||
theme.chosenFont = UIFont(name: "Atkinson Hyperlegible", size: 1)
|
theme.chosenFont = UIFont(name: "Atkinson Hyperlegible", size: 1)
|
||||||
case.SFRounded:
|
case .SFRounded:
|
||||||
theme.chosenFont = UIFont.systemFont(ofSize: 1).rounded()
|
theme.chosenFont = UIFont.systemFont(ofSize: 1).rounded()
|
||||||
case .custom:
|
case .custom:
|
||||||
isFontSelectorPresented = true
|
isFontSelectorPresented = true
|
||||||
|
|
|
@ -64,7 +64,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||||
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "glass.caf"))
|
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "glass.caf"))
|
||||||
|
|
||||||
let preferences = UserPreferences.shared
|
let preferences = UserPreferences.shared
|
||||||
if let token = AppAccountsManager.shared.availableAccounts.first(where: { $0.oauthToken?.accessToken == notification.accessToken})?.oauthToken {
|
if let token = AppAccountsManager.shared.availableAccounts.first(where: { $0.oauthToken?.accessToken == notification.accessToken })?.oauthToken {
|
||||||
var currentCount = preferences.getNotificationsCount(for: token)
|
var currentCount = preferences.getNotificationsCount(for: token)
|
||||||
currentCount += 1
|
currentCount += 1
|
||||||
preferences.setNotification(count: currentCount, token: token)
|
preferences.setNotification(count: currentCount, token: token)
|
||||||
|
|
|
@ -150,9 +150,10 @@ struct AccountDetailHeaderView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let note = viewModel.relationship?.note, !note.isEmpty,
|
if let note = viewModel.relationship?.note, !note.isEmpty,
|
||||||
!viewModel.isCurrentUser {
|
!viewModel.isCurrentUser
|
||||||
|
{
|
||||||
makeNoteView(note)
|
makeNoteView(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +163,7 @@ struct AccountDetailHeaderView: View {
|
||||||
.environment(\.openURL, OpenURLAction { url in
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
routerPath.handle(url: url)
|
routerPath.handle(url: url)
|
||||||
})
|
})
|
||||||
|
|
||||||
fieldsView
|
fieldsView
|
||||||
}
|
}
|
||||||
.padding(.horizontal, .layoutPadding)
|
.padding(.horizontal, .layoutPadding)
|
||||||
|
@ -204,7 +205,7 @@ struct AccountDetailHeaderView: View {
|
||||||
.padding(.top, 6)
|
.padding(.top, 6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func makeNoteView(_ note: String) -> some View {
|
private func makeNoteView(_ note: String) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
@ -221,7 +222,7 @@ struct AccountDetailHeaderView: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var fieldsView: some View {
|
private var fieldsView: some View {
|
||||||
if !viewModel.fields.isEmpty {
|
if !viewModel.fields.isEmpty {
|
||||||
|
|
|
@ -485,8 +485,7 @@ public struct AccountDetailView: View {
|
||||||
|
|
||||||
private extension View {
|
private extension View {
|
||||||
func applyAccountDetailsRowStyle(theme: Theme) -> some View {
|
func applyAccountDetailsRowStyle(theme: Theme) -> some View {
|
||||||
self
|
listRowInsets(.init())
|
||||||
.listRowInsets(.init())
|
|
||||||
.listRowSeparator(.hidden)
|
.listRowSeparator(.hidden)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,12 @@ public struct EditRelationshipNoteView: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
@EnvironmentObject private var client: Client
|
@EnvironmentObject private var client: Client
|
||||||
|
|
||||||
// need this model to refresh after storing the new note on mastodon
|
// need this model to refresh after storing the new note on mastodon
|
||||||
var accountDetailViewModel: AccountDetailViewModel
|
var accountDetailViewModel: AccountDetailViewModel
|
||||||
|
|
||||||
@StateObject private var viewModel = EditRelationshipNoteViewModel()
|
@StateObject private var viewModel = EditRelationshipNoteViewModel()
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
|
@ -31,8 +31,8 @@ public struct EditRelationshipNoteView: View {
|
||||||
.alert("account.relation.note.edit.error.save.title",
|
.alert("account.relation.note.edit.error.save.title",
|
||||||
isPresented: $viewModel.saveError,
|
isPresented: $viewModel.saveError,
|
||||||
actions: {
|
actions: {
|
||||||
Button("alert.button.ok", action: {})
|
Button("alert.button.ok", action: {})
|
||||||
}, message: { Text("account.relation.note.edit.error.save.message") })
|
}, message: { Text("account.relation.note.edit.error.save.message") })
|
||||||
.task {
|
.task {
|
||||||
viewModel.client = client
|
viewModel.client = client
|
||||||
viewModel.relatedAccountId = accountDetailViewModel.accountId
|
viewModel.relatedAccountId = accountDetailViewModel.accountId
|
||||||
|
@ -40,7 +40,7 @@ public struct EditRelationshipNoteView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ToolbarContentBuilder
|
@ToolbarContentBuilder
|
||||||
private var toolbarContent: some ToolbarContent {
|
private var toolbarContent: some ToolbarContent {
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
|
@ -48,7 +48,7 @@ public struct EditRelationshipNoteView: View {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
Button {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
|
|
|
@ -6,15 +6,16 @@ class EditRelationshipNoteViewModel: ObservableObject {
|
||||||
public var note: String = ""
|
public var note: String = ""
|
||||||
public var relatedAccountId: String?
|
public var relatedAccountId: String?
|
||||||
public var client: Client?
|
public var client: Client?
|
||||||
|
|
||||||
@Published var isSaving: Bool = false
|
@Published var isSaving: Bool = false
|
||||||
@Published var saveError: Bool = false
|
@Published var saveError: Bool = false
|
||||||
|
|
||||||
init() {}
|
init() {}
|
||||||
|
|
||||||
func save() async {
|
func save() async {
|
||||||
if relatedAccountId != nil,
|
if relatedAccountId != nil,
|
||||||
client != nil {
|
client != nil
|
||||||
|
{
|
||||||
isSaving = true
|
isSaving = true
|
||||||
do {
|
do {
|
||||||
let _ = try await client!.post(endpoint: Accounts.relationshipNote(id: relatedAccountId!, json: RelationshipNoteData(note: note)))
|
let _ = try await client!.post(endpoint: Accounts.relationshipNote(id: relatedAccountId!, json: RelationshipNoteData(note: note)))
|
||||||
|
|
|
@ -92,7 +92,8 @@ public struct AppAccountsSelectorView: View {
|
||||||
Image(uiImage: image)
|
Image(uiImage: image)
|
||||||
}
|
}
|
||||||
if let token = viewModel.appAccount.oauthToken,
|
if let token = viewModel.appAccount.oauthToken,
|
||||||
preferences.getNotificationsCount(for: token) > 0 {
|
preferences.getNotificationsCount(for: token) > 0
|
||||||
|
{
|
||||||
Text("\(viewModel.account?.displayName ?? "") (\(preferences.getNotificationsCount(for: token)))")
|
Text("\(viewModel.account?.displayName ?? "") (\(preferences.getNotificationsCount(for: token)))")
|
||||||
} else {
|
} else {
|
||||||
Text("\(viewModel.account?.displayName ?? "")")
|
Text("\(viewModel.account?.displayName ?? "")")
|
||||||
|
|
|
@ -15,12 +15,10 @@ public extension Font {
|
||||||
private static let onMac = ProcessInfo.processInfo.isiOSAppOnMac
|
private static let onMac = ProcessInfo.processInfo.isiOSAppOnMac
|
||||||
|
|
||||||
private static func customFont(size: CGFloat, relativeTo textStyle: TextStyle) -> Font {
|
private static func customFont(size: CGFloat, relativeTo textStyle: TextStyle) -> Font {
|
||||||
|
|
||||||
if let chosenFont = Theme.shared.chosenFont {
|
if let chosenFont = Theme.shared.chosenFont {
|
||||||
if chosenFont.fontName == ".AppleSystemUIFontRounded-Regular" {
|
if chosenFont.fontName == ".AppleSystemUIFontRounded-Regular" {
|
||||||
return .system(size: size, design: .rounded)
|
return .system(size: size, design: .rounded)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return .custom(chosenFont.fontName, size: size, relativeTo: textStyle)
|
return .custom(chosenFont.fontName, size: size, relativeTo: textStyle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,13 +70,11 @@ public extension Font {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension UIFont {
|
||||||
|
func rounded() -> UIFont {
|
||||||
extension UIFont {
|
guard let descriptor = fontDescriptor.withDesign(.rounded) else {
|
||||||
public func rounded() -> UIFont {
|
return self
|
||||||
guard let descriptor = fontDescriptor.withDesign(.rounded) else {
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
return UIFont(descriptor: descriptor, size: pointSize)
|
|
||||||
}
|
}
|
||||||
|
return UIFont(descriptor: descriptor, size: pointSize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class Theme: ObservableObject {
|
||||||
case .hyperLegible:
|
case .hyperLegible:
|
||||||
return "Hyper Legible"
|
return "Hyper Legible"
|
||||||
case .SFRounded:
|
case .SFRounded:
|
||||||
return "SF Rounded"
|
return "SF Rounded"
|
||||||
case .custom:
|
case .custom:
|
||||||
return "settings.display.font.custom"
|
return "settings.display.font.custom"
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ public class Theme: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var chosenFont: UIFont? {
|
public var chosenFont: UIFont? {
|
||||||
get {
|
get {
|
||||||
guard let chosenFontData,
|
guard let chosenFontData,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import NukeUI
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
import NukeUI
|
||||||
import Shimmer
|
import Shimmer
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
@ -97,17 +97,17 @@ public struct AvatarView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct AvatarPlaceholderView: View {
|
private struct AvatarPlaceholderView: View {
|
||||||
let size: AvatarView.Size
|
let size: AvatarView.Size
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if size == .badge {
|
if size == .badge {
|
||||||
Circle()
|
Circle()
|
||||||
.fill(.gray)
|
.fill(.gray)
|
||||||
.frame(width: size.size.width, height: size.size.height)
|
.frame(width: size.size.width, height: size.size.height)
|
||||||
} else {
|
} else {
|
||||||
RoundedRectangle(cornerRadius: size.cornerRadius)
|
RoundedRectangle(cornerRadius: size.cornerRadius)
|
||||||
.fill(.gray)
|
.fill(.gray)
|
||||||
.frame(width: size.size.width, height: size.size.height)
|
.frame(width: size.size.width, height: size.size.height)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ public extension EnvironmentValues {
|
||||||
get { self[IsCompact.self] }
|
get { self[IsCompact.self] }
|
||||||
set { self[IsCompact.self] = newValue }
|
set { self[IsCompact.self] = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
var isInCaptureMode: Bool {
|
var isInCaptureMode: Bool {
|
||||||
get { self[IsInCaptureMode.self] }
|
get { self[IsInCaptureMode.self] }
|
||||||
set { self[IsInCaptureMode.self] = newValue }
|
set { self[IsInCaptureMode.self] = newValue }
|
||||||
|
|
|
@ -96,13 +96,13 @@ public class UserPreferences: ObservableObject {
|
||||||
public func setNotification(count: Int, token: OauthToken) {
|
public func setNotification(count: Int, token: OauthToken) {
|
||||||
Self.sharedDefault?.set(count, forKey: "push_notifications_count_\(token.createdAt)")
|
Self.sharedDefault?.set(count, forKey: "push_notifications_count_\(token.createdAt)")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getNotificationsCount(for token: OauthToken) -> Int {
|
public func getNotificationsCount(for token: OauthToken) -> Int {
|
||||||
Self.sharedDefault?.integer(forKey: "push_notifications_count_\(token.createdAt)") ?? 0
|
Self.sharedDefault?.integer(forKey: "push_notifications_count_\(token.createdAt)") ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getNotificationsTotalCount(for tokens: [OauthToken]) -> Int {
|
public func getNotificationsTotalCount(for tokens: [OauthToken]) -> Int {
|
||||||
var count: Int = 0
|
var count = 0
|
||||||
for token in tokens {
|
for token in tokens {
|
||||||
count += getNotificationsCount(for: token)
|
count += getNotificationsCount(for: token)
|
||||||
}
|
}
|
||||||
|
@ -134,6 +134,3 @@ public class UserPreferences: ObservableObject {
|
||||||
recentlyUsedLanguages = Array(copy.prefix(3))
|
recentlyUsedLanguages = Array(copy.prefix(3))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,13 +28,11 @@ public struct Poll: Codable, Equatable, Hashable {
|
||||||
public let voted: Bool?
|
public let voted: Bool?
|
||||||
public let ownVotes: [Int]?
|
public let ownVotes: [Int]?
|
||||||
public let options: [Option]
|
public let options: [Option]
|
||||||
|
|
||||||
// the votersCount can be null according to the docs when multiple is false.
|
// the votersCount can be null according to the docs when multiple is false.
|
||||||
// Didn't find that to be true, but we make sure
|
// Didn't find that to be true, but we make sure
|
||||||
public var safeVotersCount: Int {
|
public var safeVotersCount: Int {
|
||||||
get {
|
return votersCount ?? votesCount
|
||||||
return votersCount ?? votesCount
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,8 +56,8 @@ public protocol AnyStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct StatusViewId: Hashable {
|
public struct StatusViewId: Hashable {
|
||||||
let id: String
|
let id: String
|
||||||
let editedAt: ServerDate?
|
let editedAt: ServerDate?
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension AnyStatus {
|
public extension AnyStatus {
|
||||||
|
|
|
@ -164,7 +164,7 @@ public struct MuteData: Encodable, Sendable {
|
||||||
|
|
||||||
public struct RelationshipNoteData: Encodable, Sendable {
|
public struct RelationshipNoteData: Encodable, Sendable {
|
||||||
public let comment: String
|
public let comment: String
|
||||||
|
|
||||||
public init(note comment: String) {
|
public init(note comment: String) {
|
||||||
self.comment = comment
|
self.comment = comment
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ public struct StatusPollView: View {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func percentForOption(option: Poll.Option) -> Int {
|
private func percentForOption(option: Poll.Option) -> Int {
|
||||||
let percent = ratioForOption(option: option) * 100
|
let percent = ratioForOption(option: option) * 100
|
||||||
return Int(round(percent))
|
return Int(round(percent))
|
||||||
|
|
|
@ -43,10 +43,10 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
var filter: Filtered? {
|
var filter: Filtered? {
|
||||||
status.reblog?.filtered?.first ?? status.filtered?.first
|
status.reblog?.filtered?.first ?? status.filtered?.first
|
||||||
}
|
}
|
||||||
|
|
||||||
var isThread: Bool {
|
var isThread: Bool {
|
||||||
status.reblog?.inReplyToId != nil || status.reblog?.inReplyToAccountId != nil ||
|
status.reblog?.inReplyToId != nil || status.reblog?.inReplyToAccountId != nil ||
|
||||||
status.inReplyToId != nil || status.inReplyToAccountId != nil
|
status.inReplyToId != nil || status.inReplyToAccountId != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var highlightRowColor: Color {
|
var highlightRowColor: Color {
|
||||||
|
|
|
@ -8,11 +8,11 @@ struct StatusRowActionsView: View {
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
@EnvironmentObject private var currentAccount: CurrentAccount
|
@EnvironmentObject private var currentAccount: CurrentAccount
|
||||||
@ObservedObject var viewModel: StatusRowViewModel
|
@ObservedObject var viewModel: StatusRowViewModel
|
||||||
|
|
||||||
func privateBoost() -> Bool {
|
func privateBoost() -> Bool {
|
||||||
return self.viewModel.status.visibility == .priv && self.viewModel.status.account.id == self.currentAccount.account?.id
|
return viewModel.status.visibility == .priv && viewModel.status.account.id == currentAccount.account?.id
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
enum Actions: CaseIterable {
|
enum Actions: CaseIterable {
|
||||||
case respond, boost, favorite, bookmark, share
|
case respond, boost, favorite, bookmark, share
|
||||||
|
@ -22,10 +22,10 @@ struct StatusRowActionsView: View {
|
||||||
case .respond:
|
case .respond:
|
||||||
return "arrowshape.turn.up.left"
|
return "arrowshape.turn.up.left"
|
||||||
case .boost:
|
case .boost:
|
||||||
if (privateBoost) {
|
if privateBoost {
|
||||||
return viewModel.isReblogged ? "arrow.left.arrow.right.circle.fill" : "lock.rotation"
|
return viewModel.isReblogged ? "arrow.left.arrow.right.circle.fill" : "lock.rotation"
|
||||||
}
|
}
|
||||||
|
|
||||||
return viewModel.isReblogged ? "arrow.left.arrow.right.circle.fill" : "arrow.left.arrow.right.circle"
|
return viewModel.isReblogged ? "arrow.left.arrow.right.circle.fill" : "arrow.left.arrow.right.circle"
|
||||||
case .favorite:
|
case .favorite:
|
||||||
return viewModel.isFavorited ? "star.fill" : "star"
|
return viewModel.isFavorited ? "star.fill" : "star"
|
||||||
|
@ -96,7 +96,7 @@ struct StatusRowActionsView: View {
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderless)
|
.buttonStyle(.borderless)
|
||||||
.disabled(action == .boost &&
|
.disabled(action == .boost &&
|
||||||
(viewModel.status.visibility == .direct || viewModel.status.visibility == .priv && viewModel.status.account.id != currentAccount.account?.id))
|
(viewModel.status.visibility == .direct || viewModel.status.visibility == .priv && viewModel.status.account.id != currentAccount.account?.id))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import SwiftUI
|
||||||
public struct StatusRowCardView: View {
|
public struct StatusRowCardView: View {
|
||||||
@Environment(\.openURL) private var openURL
|
@Environment(\.openURL) private var openURL
|
||||||
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
||||||
|
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
let card: Card
|
let card: Card
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
|
||||||
import DesignSystem
|
|
||||||
import Network
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusRowContextMenu: View {
|
struct StatusRowContextMenu: View {
|
||||||
@Environment(\.displayScale) var displayScale
|
@Environment(\.displayScale) var displayScale
|
||||||
|
|
||||||
@EnvironmentObject private var sceneDelegate: SceneDelegate
|
@EnvironmentObject private var sceneDelegate: SceneDelegate
|
||||||
@EnvironmentObject private var preferences: UserPreferences
|
@EnvironmentObject private var preferences: UserPreferences
|
||||||
@EnvironmentObject private var account: CurrentAccount
|
@EnvironmentObject private var account: CurrentAccount
|
||||||
@EnvironmentObject private var currentInstance: CurrentInstance
|
@EnvironmentObject private var currentInstance: CurrentInstance
|
||||||
|
|
||||||
@ObservedObject var viewModel: StatusRowViewModel
|
@ObservedObject var viewModel: StatusRowViewModel
|
||||||
|
|
||||||
var boostLabel: some View {
|
var boostLabel: some View {
|
||||||
if self.viewModel.status.visibility == .priv && self.viewModel.status.account.id == self.account.account?.id {
|
if self.viewModel.status.visibility == .priv && self.viewModel.status.account.id == self.account.account?.id {
|
||||||
if self.viewModel.isReblogged {
|
if self.viewModel.isReblogged {
|
||||||
|
@ -21,13 +21,13 @@ struct StatusRowContextMenu: View {
|
||||||
}
|
}
|
||||||
return Label("status.action.boost-to-followers", systemImage: "lock.rotation")
|
return Label("status.action.boost-to-followers", systemImage: "lock.rotation")
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.viewModel.isReblogged {
|
if self.viewModel.isReblogged {
|
||||||
return Label("status.action.unboost", systemImage: "arrow.left.arrow.right.circle")
|
return Label("status.action.unboost", systemImage: "arrow.left.arrow.right.circle")
|
||||||
}
|
}
|
||||||
return Label("status.action.boost", systemImage: "arrow.left.arrow.right.circle")
|
return Label("status.action.boost", systemImage: "arrow.left.arrow.right.circle")
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if !viewModel.isRemote {
|
if !viewModel.isRemote {
|
||||||
Button { Task {
|
Button { Task {
|
||||||
|
@ -85,11 +85,11 @@ struct StatusRowContextMenu: View {
|
||||||
message: Text(viewModel.status.reblog?.content.asRawText ?? viewModel.status.content.asRawText)) {
|
message: Text(viewModel.status.reblog?.content.asRawText ?? viewModel.status.content.asRawText)) {
|
||||||
Label("status.action.share", systemImage: "square.and.arrow.up")
|
Label("status.action.share", systemImage: "square.and.arrow.up")
|
||||||
}
|
}
|
||||||
|
|
||||||
ShareLink(item: url) {
|
ShareLink(item: url) {
|
||||||
Label("status.action.share-link", systemImage: "link")
|
Label("status.action.share-link", systemImage: "link")
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
let view = HStack {
|
let view = HStack {
|
||||||
StatusRowView(viewModel: viewModel)
|
StatusRowView(viewModel: viewModel)
|
||||||
|
@ -200,10 +200,10 @@ struct StatusRowContextMenu: View {
|
||||||
|
|
||||||
struct ActivityView: UIViewControllerRepresentable {
|
struct ActivityView: UIViewControllerRepresentable {
|
||||||
let image: Image
|
let image: Image
|
||||||
|
|
||||||
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
|
func makeUIViewController(context _: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
|
||||||
return UIActivityViewController(activityItems: [image], applicationActivities: nil)
|
return UIActivityViewController(activityItems: [image], applicationActivities: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityView>) {}
|
func updateUIViewController(_: UIActivityViewController, context _: UIViewControllerRepresentableContext<ActivityView>) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
|
import Env
|
||||||
import Models
|
import Models
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Env
|
|
||||||
|
|
||||||
struct StatusRowHeaderView: View {
|
struct StatusRowHeaderView: View {
|
||||||
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
||||||
|
@ -57,19 +57,19 @@ struct StatusRowHeaderView: View {
|
||||||
}
|
}
|
||||||
if theme.avatarPosition == .top {
|
if theme.avatarPosition == .top {
|
||||||
dateView
|
dateView
|
||||||
.font(.scaledFootnote)
|
.font(.scaledFootnote)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var dateView: Text {
|
private var dateView: Text {
|
||||||
Text(viewModel.status.account.bot ? "🤖 " : "") +
|
Text(viewModel.status.account.bot ? "🤖 " : "") +
|
||||||
Text(status.createdAt.relativeFormatted) +
|
Text(status.createdAt.relativeFormatted) +
|
||||||
Text(" ⸱ ") +
|
Text(" ⸱ ") +
|
||||||
Text(Image(systemName: viewModel.status.visibility.iconName))
|
Text(Image(systemName: viewModel.status.visibility.iconName))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -156,7 +156,8 @@ public struct StatusRowMediaPreviewView: View {
|
||||||
case .image:
|
case .image:
|
||||||
if isInCaptureMode,
|
if isInCaptureMode,
|
||||||
let image = Nuke.ImagePipeline.shared.cache.cachedImage(for: .init(url: attachment.url,
|
let image = Nuke.ImagePipeline.shared.cache.cachedImage(for: .init(url: attachment.url,
|
||||||
processors: processors))?.image {
|
processors: processors))?.image
|
||||||
|
{
|
||||||
Image(uiImage: image)
|
Image(uiImage: image)
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
|
@ -243,10 +244,11 @@ public struct StatusRowMediaPreviewView: View {
|
||||||
cornerSensitiveButton
|
cornerSensitiveButton
|
||||||
}
|
}
|
||||||
if !isInCaptureMode,
|
if !isInCaptureMode,
|
||||||
let alt = attachment.description,
|
let alt = attachment.description,
|
||||||
!alt.isEmpty,
|
!alt.isEmpty,
|
||||||
!isNotifications,
|
!isNotifications,
|
||||||
preferences.showAltTextForMedia {
|
preferences.showAltTextForMedia
|
||||||
|
{
|
||||||
Button {
|
Button {
|
||||||
altTextDisplayed = alt
|
altTextDisplayed = alt
|
||||||
isAltAlertDisplayed = true
|
isAltAlertDisplayed = true
|
||||||
|
|
|
@ -4,7 +4,7 @@ import SwiftUI
|
||||||
|
|
||||||
struct StatusRowTextView: View {
|
struct StatusRowTextView: View {
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
||||||
let status: AnyStatus
|
let status: AnyStatus
|
||||||
let viewModel: StatusRowViewModel
|
let viewModel: StatusRowViewModel
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import SwiftUI
|
||||||
|
|
||||||
struct StatusRowTranslateView: View {
|
struct StatusRowTranslateView: View {
|
||||||
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
||||||
|
|
||||||
@EnvironmentObject private var preferences: UserPreferences
|
@EnvironmentObject private var preferences: UserPreferences
|
||||||
|
|
||||||
let status: AnyStatus
|
let status: AnyStatus
|
||||||
|
@ -27,7 +27,7 @@ struct StatusRowTranslateView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if !isInCaptureMode,
|
if !isInCaptureMode,
|
||||||
let userLang = preferences.serverPreferences?.postLanguage,
|
let userLang = preferences.serverPreferences?.postLanguage,
|
||||||
shouldShowTranslateButton
|
shouldShowTranslateButton
|
||||||
{
|
{
|
||||||
Button {
|
Button {
|
||||||
|
|
|
@ -3,51 +3,51 @@ import Models
|
||||||
|
|
||||||
actor TimelineDatasource {
|
actor TimelineDatasource {
|
||||||
private var statuses: [Status] = []
|
private var statuses: [Status] = []
|
||||||
|
|
||||||
var isEmpty: Bool {
|
var isEmpty: Bool {
|
||||||
statuses.isEmpty
|
statuses.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
func get() -> [Status] {
|
func get() -> [Status] {
|
||||||
statuses
|
statuses
|
||||||
}
|
}
|
||||||
|
|
||||||
func reset() {
|
func reset() {
|
||||||
statuses = []
|
statuses = []
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexOf(statusId: String) -> Int? {
|
func indexOf(statusId: String) -> Int? {
|
||||||
statuses.firstIndex(where: { $0.id == statusId })
|
statuses.firstIndex(where: { $0.id == statusId })
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(statusId: String) -> Bool {
|
func contains(statusId: String) -> Bool {
|
||||||
statuses.contains(where: { $0.id == statusId })
|
statuses.contains(where: { $0.id == statusId })
|
||||||
}
|
}
|
||||||
|
|
||||||
func set(_ statuses: [Status]) {
|
func set(_ statuses: [Status]) {
|
||||||
self.statuses = statuses
|
self.statuses = statuses
|
||||||
}
|
}
|
||||||
|
|
||||||
func append(_ status: Status) {
|
func append(_ status: Status) {
|
||||||
statuses.append(status)
|
statuses.append(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
func append(contentOf: [Status]) {
|
func append(contentOf: [Status]) {
|
||||||
statuses.append(contentsOf: contentOf)
|
statuses.append(contentsOf: contentOf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func insert(_ status: Status, at: Int) {
|
func insert(_ status: Status, at: Int) {
|
||||||
statuses.insert(status, at: at)
|
statuses.insert(status, at: at)
|
||||||
}
|
}
|
||||||
|
|
||||||
func insert(contentOf: [Status], at: Int) {
|
func insert(contentOf: [Status], at: Int) {
|
||||||
statuses.insert(contentsOf: contentOf, at: at)
|
statuses.insert(contentsOf: contentOf, at: at)
|
||||||
}
|
}
|
||||||
|
|
||||||
func replace(_ status: Status, at: Int) {
|
func replace(_ status: Status, at: Int) {
|
||||||
statuses[at] = status
|
statuses[at] = status
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(_ statusId: String) {
|
func remove(_ statusId: String) {
|
||||||
statuses.removeAll(where: { $0.id == statusId })
|
statuses.removeAll(where: { $0.id == statusId })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
import SwiftUI
|
|
||||||
import UIKit
|
|
||||||
import Models
|
import Models
|
||||||
import Nuke
|
import Nuke
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
final class TimelinePrefetcher: NSObject, ObservableObject, UICollectionViewDataSourcePrefetching {
|
final class TimelinePrefetcher: NSObject, ObservableObject, UICollectionViewDataSourcePrefetching {
|
||||||
private let prefetcher = ImagePrefetcher()
|
private let prefetcher = ImagePrefetcher()
|
||||||
|
|
||||||
weak var viewModel: TimelineViewModel?
|
weak var viewModel: TimelineViewModel?
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
|
func collectionView(_: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
|
||||||
let imageURLs = getImageURLs(for: indexPaths)
|
let imageURLs = getImageURLs(for: indexPaths)
|
||||||
prefetcher.startPrefetching(with: imageURLs)
|
prefetcher.startPrefetching(with: imageURLs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
|
func collectionView(_: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
|
||||||
let imageURLs = getImageURLs(for: indexPaths)
|
let imageURLs = getImageURLs(for: indexPaths)
|
||||||
prefetcher.stopPrefetching(with: imageURLs)
|
prefetcher.stopPrefetching(with: imageURLs)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getImageURLs(for indexPaths: [IndexPath]) -> [URL] {
|
private func getImageURLs(for indexPaths: [IndexPath]) -> [URL] {
|
||||||
guard let viewModel, case .display(let statuses, _) = viewModel.statusesState else {
|
guard let viewModel, case let .display(statuses, _) = viewModel.statusesState else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return indexPaths.compactMap {
|
return indexPaths.compactMap {
|
||||||
|
|
|
@ -92,7 +92,7 @@ class TimelineViewModel: ObservableObject {
|
||||||
tag = try await client.get(endpoint: Tags.tag(id: id))
|
tag = try await client.get(endpoint: Tags.tag(id: id))
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reset() async {
|
func reset() async {
|
||||||
await datasource.reset()
|
await datasource.reset()
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,7 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
|
|
||||||
updateMentionsToBeHighlighted(&statuses)
|
updateMentionsToBeHighlighted(&statuses)
|
||||||
ReblogCache.shared.removeDuplicateReblogs(&statuses)
|
ReblogCache.shared.removeDuplicateReblogs(&statuses)
|
||||||
|
|
||||||
await datasource.set(statuses)
|
await datasource.set(statuses)
|
||||||
await cacheHome()
|
await cacheHome()
|
||||||
|
|
||||||
|
@ -231,7 +231,7 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
var newStatuses: [Status] = await fetchNewPages(minId: latestStatus.id, maxPages: 10)
|
var newStatuses: [Status] = await fetchNewPages(minId: latestStatus.id, maxPages: 10)
|
||||||
|
|
||||||
// Dedup statuses, a status with the same id could have been streamed in.
|
// Dedup statuses, a status with the same id could have been streamed in.
|
||||||
let ids = await datasource.get().map{ $0.id }
|
let ids = await datasource.get().map { $0.id }
|
||||||
newStatuses = newStatuses.filter { status in
|
newStatuses = newStatuses.filter { status in
|
||||||
!ids.contains(where: { $0 == status.id })
|
!ids.contains(where: { $0 == status.id })
|
||||||
}
|
}
|
||||||
|
@ -322,10 +322,10 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
var latestMinId = minId
|
var latestMinId = minId
|
||||||
do {
|
do {
|
||||||
while var newStatuses: [Status] =
|
while var newStatuses: [Status] =
|
||||||
try await client.get(endpoint: timeline.endpoint(sinceId: nil,
|
try await client.get(endpoint: timeline.endpoint(sinceId: nil,
|
||||||
maxId: nil,
|
maxId: nil,
|
||||||
minId: latestMinId,
|
minId: latestMinId,
|
||||||
offset: datasource.get().count)),
|
offset: datasource.get().count)),
|
||||||
!newStatuses.isEmpty,
|
!newStatuses.isEmpty,
|
||||||
pagesLoaded < maxPages
|
pagesLoaded < maxPages
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue