Swiftformat

This commit is contained in:
Thomas Ricouard 2023-09-16 14:15:03 +02:00
parent 584a0d0432
commit 8a3c971402
120 changed files with 573 additions and 579 deletions

View file

@ -155,8 +155,8 @@ struct ActivityView: UIViewControllerRepresentable {
} }
func makeUIViewController(context _: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController { func makeUIViewController(context _: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
return UIActivityViewController(activityItems: [image, LinkDelegate(image: image, status: status)], UIActivityViewController(activityItems: [image, LinkDelegate(image: image, status: status)],
applicationActivities: nil) applicationActivities: nil)
} }
func updateUIViewController(_: UIActivityViewController, context _: UIViewControllerRepresentableContext<ActivityView>) {} func updateUIViewController(_: UIActivityViewController, context _: UIViewControllerRepresentableContext<ActivityView>) {}

View file

@ -100,7 +100,7 @@ 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)

View file

@ -13,7 +13,7 @@ struct QuickLookPreview: UIViewControllerRepresentable {
let urls: [URL] let urls: [URL]
func makeUIViewController(context _: Context) -> UIViewController { func makeUIViewController(context _: Context) -> UIViewController {
return AppQLPreviewController(selectedURL: selectedURL, urls: urls) AppQLPreviewController(selectedURL: selectedURL, urls: urls)
} }
func updateUIViewController( func updateUIViewController(
@ -53,11 +53,11 @@ class AppQLPreviewController: UIViewController {
extension AppQLPreviewController: QLPreviewControllerDataSource { extension AppQLPreviewController: QLPreviewControllerDataSource {
nonisolated func numberOfPreviewItems(in _: QLPreviewController) -> Int { nonisolated func numberOfPreviewItems(in _: QLPreviewController) -> Int {
return urls.count urls.count
} }
nonisolated func previewController(_: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { nonisolated func previewController(_: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return urls[index] as QLPreviewItem urls[index] as QLPreviewItem
} }
} }
@ -81,7 +81,7 @@ extension AppQLPreviewController: QLPreviewControllerDelegate {
struct TransparentBackground: UIViewControllerRepresentable { struct TransparentBackground: UIViewControllerRepresentable {
public func makeUIViewController(context _: Context) -> UIViewController { public func makeUIViewController(context _: Context) -> UIViewController {
return TransparentController() TransparentController()
} }
public func updateUIViewController(_: UIViewController, context _: Context) {} public func updateUIViewController(_: UIViewController, context _: Context) {}

View file

@ -51,7 +51,7 @@ private struct SafariRouter: ViewModifier {
} }
.background { .background {
WindowReader { window in WindowReader { window in
self.safariManager.windowScene = window.windowScene safariManager.windowScene = window.windowScene
} }
} }
} }
@ -65,7 +65,7 @@ private class InAppSafariManager: NSObject, ObservableObject, SFSafariViewContro
@MainActor @MainActor
func open(_ url: URL) -> OpenURLAction.Result { func open(_ url: URL) -> OpenURLAction.Result {
guard let windowScene = windowScene else { return .systemAction } guard let windowScene else { return .systemAction }
window = setupWindow(windowScene: windowScene) window = setupWindow(windowScene: windowScene)
@ -85,7 +85,7 @@ private class InAppSafariManager: NSObject, ObservableObject, SFSafariViewContro
} }
func setupWindow(windowScene: UIWindowScene) -> UIWindow { func setupWindow(windowScene: UIWindowScene) -> UIWindow {
let window = self.window ?? UIWindow(windowScene: windowScene) let window = window ?? UIWindow(windowScene: windowScene)
window.rootViewController = viewController window.rootViewController = viewController
window.makeKeyAndVisible() window.makeKeyAndVisible()

View file

@ -19,7 +19,7 @@ struct SideBarView<Content: View>: View {
@ViewBuilder var content: () -> Content @ViewBuilder var content: () -> Content
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)

View file

@ -29,7 +29,7 @@ struct ExploreTab: View {
AppAccountsSelectorView(routerPath: routerPath) AppAccountsSelectorView(routerPath: routerPath)
} }
} }
if UIDevice.current.userInterfaceIdiom == .pad && !preferences.showiPadSecondaryColumn { if UIDevice.current.userInterfaceIdiom == .pad, !preferences.showiPadSecondaryColumn {
SecondaryColumnToolbarItem() SecondaryColumnToolbarItem()
} }
} }

View file

@ -75,7 +75,7 @@ struct AboutView: View {
[SwiftUI-Introspect](https://github.com/siteline/SwiftUI-Introspect) [SwiftUI-Introspect](https://github.com/siteline/SwiftUI-Introspect)
[RevenueCat](https://github.com/RevenueCat/purchases-ios) [RevenueCat](https://github.com/RevenueCat/purchases-ios)
[SFSafeSymbols](https://github.com/SFSafeSymbols/SFSafeSymbols) [SFSafeSymbols](https://github.com/SFSafeSymbols/SFSafeSymbols)
""") """)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)

View file

@ -100,11 +100,11 @@ struct AddAccountView: View {
Task { Task {
do { do {
// bare bones preflight for domain validity // bare bones preflight for domain validity
if client.server.contains(".") && client.server.last != "." { if client.server.contains("."), client.server.last != "." {
let instance: Instance = try await client.get(endpoint: Instances.instance) let instance: Instance = try await client.get(endpoint: Instances.instance)
withAnimation { withAnimation {
self.instance = instance self.instance = instance
self.instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box
} }
instanceFetchError = nil instanceFetchError = nil
} else { } else {
@ -178,7 +178,7 @@ struct AddAccountView: View {
} else { } else {
ForEach(sanitizedName.isEmpty ? instances : instances.filter { $0.name.contains(sanitizedName.lowercased()) }) { instance in ForEach(sanitizedName.isEmpty ? instances : instances.filter { $0.name.contains(sanitizedName.lowercased()) }) { instance in
Button { Button {
self.instanceName = instance.name instanceName = instance.name
} label: { } label: {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text(instance.name) Text(instance.name)

View file

@ -83,19 +83,20 @@ struct ContentSettingsView: View {
} }
} }
.disabled(userPreferences.useInstanceContentSettings) .disabled(userPreferences.useInstanceContentSettings)
Picker("settings.content.default-reply-visibility", selection: $userPreferences.appDefaultReplyVisibility) { Picker("settings.content.default-reply-visibility", selection: $userPreferences.appDefaultReplyVisibility) {
ForEach(Visibility.allCases, id: \.rawValue) { vis in ForEach(Visibility.allCases, id: \.rawValue) { vis in
if UserPreferences.getIntOfVisibility(vis) <= if UserPreferences.getIntOfVisibility(vis) <=
UserPreferences.getIntOfVisibility(userPreferences.postVisibility) { UserPreferences.getIntOfVisibility(userPreferences.postVisibility)
{
Text(vis.title).tag(vis) Text(vis.title).tag(vis)
} }
} }
} }
.onChange(of: userPreferences.postVisibility) { newValue in .onChange(of: userPreferences.postVisibility) { _ in
userPreferences.conformReplyVisibilityConstraints() userPreferences.conformReplyVisibilityConstraints()
} }
Toggle(isOn: $userPreferences.appDefaultPostsSensitive) { Toggle(isOn: $userPreferences.appDefaultPostsSensitive) {
Text("settings.content.default-sensitive") Text("settings.content.default-sensitive")
} }

View file

@ -32,9 +32,9 @@ struct IconSelectorView: View {
var appIconName: String { var appIconName: String {
switch self { switch self {
case .primary: case .primary:
return "AppIcon" "AppIcon"
default: default:
return "AppIconAlternate\(rawValue)" "AppIconAlternate\(rawValue)"
} }
} }
@ -120,6 +120,6 @@ struct IconSelectorView: View {
extension String { extension String {
var localized: String { var localized: String {
return NSLocalizedString(self, comment: "") NSLocalizedString(self, comment: "")
} }
} }

View file

@ -51,7 +51,7 @@ struct SettingsTabs: View {
} }
} }
} }
if UIDevice.current.userInterfaceIdiom == .pad && !preferences.showiPadSecondaryColumn { if UIDevice.current.userInterfaceIdiom == .pad, !preferences.showiPadSecondaryColumn {
SecondaryColumnToolbarItem() SecondaryColumnToolbarItem()
} }
} }
@ -326,7 +326,6 @@ struct SettingsTabs: View {
} }
} }
private func moveTimelineItems(from source: IndexSet, to destination: Int) { private func moveTimelineItems(from source: IndexSet, to destination: Int) {
preferences.remoteLocalTimelines.move(fromOffsets: source, toOffset: destination) preferences.remoteLocalTimelines.move(fromOffsets: source, toOffset: destination)
} }

View file

@ -19,30 +19,30 @@ struct SupportAppView: View {
var title: LocalizedStringKey { var title: LocalizedStringKey {
switch self { switch self {
case .one: case .one:
return "settings.support.one.title" "settings.support.one.title"
case .two: case .two:
return "settings.support.two.title" "settings.support.two.title"
case .three: case .three:
return "settings.support.three.title" "settings.support.three.title"
case .four: case .four:
return "settings.support.four.title" "settings.support.four.title"
case .supporter: case .supporter:
return "settings.support.supporter.title" "settings.support.supporter.title"
} }
} }
var subtitle: LocalizedStringKey { var subtitle: LocalizedStringKey {
switch self { switch self {
case .one: case .one:
return "settings.support.one.subtitle" "settings.support.one.subtitle"
case .two: case .two:
return "settings.support.two.subtitle" "settings.support.two.subtitle"
case .three: case .three:
return "settings.support.three.subtitle" "settings.support.three.subtitle"
case .four: case .four:
return "settings.support.four.subtitle" "settings.support.four.subtitle"
case .supporter: case .supporter:
return "settings.support.supporter.subtitle" "settings.support.supporter.subtitle"
} }
} }
} }
@ -103,8 +103,8 @@ struct SupportAppView: View {
} }
private func fetchStoreProducts() { private func fetchStoreProducts() {
Purchases.shared.getProducts(Tip.allCases.map { $0.productId }) { products in Purchases.shared.getProducts(Tip.allCases.map(\.productId)) { products in
self.subscription = products.first(where: { $0.productIdentifier == Tip.supporter.productId }) subscription = products.first(where: { $0.productIdentifier == Tip.supporter.productId })
self.products = products.filter { $0.productIdentifier != Tip.supporter.productId }.sorted(by: { $0.price < $1.price }) self.products = products.filter { $0.productIdentifier != Tip.supporter.productId }.sorted(by: { $0.price < $1.price })
withAnimation { withAnimation {
loadingProducts = false loadingProducts = false
@ -114,7 +114,7 @@ struct SupportAppView: View {
private func refreshUserInfo() { private func refreshUserInfo() {
Purchases.shared.getCustomerInfo { info, _ in Purchases.shared.getCustomerInfo { info, _ in
self.customerInfo = info customerInfo = info
} }
} }
@ -221,7 +221,7 @@ struct SupportAppView: View {
Spacer() Spacer()
Button { Button {
Purchases.shared.restorePurchases { info, _ in Purchases.shared.restorePurchases { info, _ in
self.customerInfo = info customerInfo = info
} }
} label: { } label: {
Text("settings.support.restore-purchase.button") Text("settings.support.restore-purchase.button")

View file

@ -68,7 +68,7 @@ struct SwipeActionsSettingsView: View {
} }
private func createStatusActionPicker(selection: Binding<StatusAction>, label: LocalizedStringKey) -> some View { private func createStatusActionPicker(selection: Binding<StatusAction>, label: LocalizedStringKey) -> some View {
return Picker(selection: selection, label: Text(label)) { Picker(selection: selection, label: Text(label)) {
Section { Section {
Text(StatusAction.none.displayName()).tag(StatusAction.none) Text(StatusAction.none.displayName()).tag(StatusAction.none)
} }

View file

@ -86,27 +86,27 @@ enum Tab: Int, Identifiable, Hashable {
var iconName: String { var iconName: String {
switch self { switch self {
case .timeline: case .timeline:
return "rectangle.stack" "rectangle.stack"
case .trending: case .trending:
return "chart.line.uptrend.xyaxis" "chart.line.uptrend.xyaxis"
case .local: case .local:
return "person.2" "person.2"
case .federated: case .federated:
return "globe.americas" "globe.americas"
case .notifications: case .notifications:
return "bell" "bell"
case .mentions: case .mentions:
return "at" "at"
case .explore: case .explore:
return "magnifyingglass" "magnifyingglass"
case .messages: case .messages:
return "tray" "tray"
case .settings: case .settings:
return "gear" "gear"
case .profile: case .profile:
return "person.crop.circle" "person.crop.circle"
case .other: case .other:
return "" ""
} }
} }
} }

View file

@ -71,7 +71,7 @@ struct AddRemoteTimelineView: View {
isInstanceURLFieldFocused = true isInstanceURLFieldFocused = true
let client = InstanceSocialClient() let client = InstanceSocialClient()
Task { Task {
self.instances = await client.fetchInstances() instances = await client.fetchInstances()
} }
} }
} }
@ -85,7 +85,7 @@ struct AddRemoteTimelineView: View {
} else { } else {
ForEach(instanceName.isEmpty ? instances : instances.filter { $0.name.contains(instanceName.lowercased()) }) { instance in ForEach(instanceName.isEmpty ? instances : instances.filter { $0.name.contains(instanceName.lowercased()) }) { instance in
Button { Button {
self.instanceName = instance.name instanceName = instance.name
} label: { } label: {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text(instance.name) Text(instance.name)

View file

@ -35,7 +35,7 @@ struct EditTagGroupView: View {
case symbol case symbol
case new case new
} }
init(editingTagGroup: TagGroup? = nil, onSaved: ((TagGroup) -> Void)? = nil) { init(editingTagGroup: TagGroup? = nil, onSaved: ((TagGroup) -> Void)? = nil) {
self.editingTagGroup = editingTagGroup self.editingTagGroup = editingTagGroup
self.onSaved = onSaved self.onSaved = onSaved
@ -163,7 +163,7 @@ struct EditTagGroupView: View {
private func save() { private func save() {
var toSave = tags var toSave = tags
let main = toSave.removeFirst() let main = toSave.removeFirst()
let tagGroup: TagGroup = .init( let tagGroup: TagGroup = .init(
title: title.trimmingCharacters(in: .whitespaces), title: title.trimmingCharacters(in: .whitespaces),
sfSymbolName: sfSymbolName, sfSymbolName: sfSymbolName,
@ -171,7 +171,8 @@ struct EditTagGroupView: View {
additional: toSave additional: toSave
) )
if let editingTagGroup, if let editingTagGroup,
let index = preferences.tagGroups.firstIndex(of: editingTagGroup) { let index = preferences.tagGroups.firstIndex(of: editingTagGroup)
{
preferences.tagGroups[index] = tagGroup preferences.tagGroups[index] = tagGroup
} else { } else {
preferences.tagGroups.append(tagGroup) preferences.tagGroups.append(tagGroup)
@ -183,7 +184,7 @@ struct EditTagGroupView: View {
@ViewBuilder @ViewBuilder
private var symbolsSuggestionView: some View { private var symbolsSuggestionView: some View {
if focusedField == .symbol && !sfSymbolName.isEmpty { if focusedField == .symbol, !sfSymbolName.isEmpty {
let filteredMatches = allSymbols let filteredMatches = allSymbols
.filter { $0.contains(sfSymbolName) } .filter { $0.contains(sfSymbolName) }
if !filteredMatches.isEmpty { if !filteredMatches.isEmpty {

View file

@ -9,5 +9,5 @@ import Foundation
import SFSafeSymbols import SFSafeSymbols
let allSymbols: [String] = SFSymbol.allSymbols.map { symbol in let allSymbols: [String] = SFSymbol.allSymbols.map { symbol in
symbol.rawValue symbol.rawValue
} }

View file

@ -43,7 +43,7 @@ struct TimelineTab: View {
} }
.onAppear { .onAppear {
routerPath.client = client routerPath.client = client
if !didAppear && canFilterTimeline { if !didAppear, canFilterTimeline {
didAppear = true didAppear = true
if client.isAuth { if client.isAuth {
timeline = lastTimelineFilter timeline = lastTimelineFilter
@ -97,7 +97,7 @@ struct TimelineTab: View {
private var timelineFilterButton: some View { private var timelineFilterButton: some View {
if timeline.supportNewestPagination { if timeline.supportNewestPagination {
Button { Button {
self.timeline = .latest timeline = .latest
} label: { } label: {
Label(TimelineFilter.latest.localizedTitle(), systemImage: TimelineFilter.latest.iconName() ?? "") Label(TimelineFilter.latest.localizedTitle(), systemImage: TimelineFilter.latest.iconName() ?? "")
} }
@ -197,7 +197,7 @@ struct TimelineTab: View {
} }
statusEditorToolbarItem(routerPath: routerPath, statusEditorToolbarItem(routerPath: routerPath,
visibility: preferences.postVisibility) visibility: preferences.postVisibility)
if UIDevice.current.userInterfaceIdiom == .pad && !preferences.showiPadSecondaryColumn { if UIDevice.current.userInterfaceIdiom == .pad, !preferences.showiPadSecondaryColumn {
SecondaryColumnToolbarItem() SecondaryColumnToolbarItem()
} }
} else { } else {

View file

@ -70,7 +70,7 @@ class NotificationService: UNNotificationServiceExtension {
preferences.setNotification(count: currentCount, token: token) preferences.setNotification(count: currentCount, token: token)
} }
let tokens = AppAccountsManager.shared.pushAccounts.map { $0.token } let tokens = AppAccountsManager.shared.pushAccounts.map(\.token)
bestAttemptContent.badge = .init(integerLiteral: preferences.getNotificationsTotalCount(for: tokens)) bestAttemptContent.badge = .init(integerLiteral: preferences.getNotificationsTotalCount(for: tokens))
if let urlString = notification.icon, if let urlString = notification.icon,

View file

@ -29,7 +29,7 @@ let package = Package(
.product(name: "Status", package: "Status"), .product(name: "Status", package: "Status"),
], ],
swiftSettings: [ swiftSettings: [
.enableExperimentalFeature("StrictConcurrency") .enableExperimentalFeature("StrictConcurrency"),
] ]
), ),
.testTarget( .testTarget(

View file

@ -89,7 +89,7 @@ public struct AccountDetailView: View {
viewModel.client = client viewModel.client = client
// Avoid capturing non-Sendable `self` just to access the view model. // Avoid capturing non-Sendable `self` just to access the view model.
let viewModel = self.viewModel let viewModel = viewModel
Task { Task {
await withTaskGroup(of: Void.self) { group in await withTaskGroup(of: Void.self) { group in
group.addTask { await viewModel.fetchAccount() } group.addTask { await viewModel.fetchAccount() }
@ -334,7 +334,7 @@ public struct AccountDetailView: View {
} label: { } label: {
Label("account.action.edit-info", systemImage: "pencil") Label("account.action.edit-info", systemImage: "pencil")
} }
Button { Button {
if let url = URL(string: "https://\(client.server)/settings/privacy") { if let url = URL(string: "https://\(client.server)/settings/privacy") {
openURL(url) openURL(url)

View file

@ -27,25 +27,25 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
var iconName: String { var iconName: String {
switch self { switch self {
case .statuses: return "bubble.right" case .statuses: "bubble.right"
case .favorites: return "star" case .favorites: "star"
case .bookmarks: return "bookmark" case .bookmarks: "bookmark"
case .followedTags: return "tag" case .followedTags: "tag"
case .postsAndReplies: return "bubble.left.and.bubble.right" case .postsAndReplies: "bubble.left.and.bubble.right"
case .media: return "photo.on.rectangle.angled" case .media: "photo.on.rectangle.angled"
case .lists: return "list.bullet" case .lists: "list.bullet"
} }
} }
var accessibilityLabel: LocalizedStringKey { var accessibilityLabel: LocalizedStringKey {
switch self { switch self {
case .statuses: return "accessibility.tabs.profile.picker.statuses" case .statuses: "accessibility.tabs.profile.picker.statuses"
case .favorites: return "accessibility.tabs.profile.picker.favorites" case .favorites: "accessibility.tabs.profile.picker.favorites"
case .bookmarks: return "accessibility.tabs.profile.picker.bookmarks" case .bookmarks: "accessibility.tabs.profile.picker.bookmarks"
case .followedTags: return "accessibility.tabs.profile.picker.followed-tags" case .followedTags: "accessibility.tabs.profile.picker.followed-tags"
case .postsAndReplies: return "accessibility.tabs.profile.picker.posts-and-replies" case .postsAndReplies: "accessibility.tabs.profile.picker.posts-and-replies"
case .media: return "accessibility.tabs.profile.picker.media" case .media: "accessibility.tabs.profile.picker.media"
case .lists: return "accessibility.tabs.profile.picker.lists" case .lists: "accessibility.tabs.profile.picker.lists"
} }
} }
} }
@ -145,7 +145,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
private func fetchAccountData(accountId: String, client: Client) async throws -> AccountData { private func fetchAccountData(accountId: String, client: Client) async throws -> AccountData {
async let account: Account = client.get(endpoint: Accounts.accounts(id: accountId)) async let account: Account = client.get(endpoint: Accounts.accounts(id: accountId))
async let featuredTags: [FeaturedTag] = client.get(endpoint: Accounts.featuredTags(id: accountId)) async let featuredTags: [FeaturedTag] = client.get(endpoint: Accounts.featuredTags(id: accountId))
if client.isAuth && !isCurrentUser { if client.isAuth, !isCurrentUser {
async let relationships: [Relationship] = client.get(endpoint: Accounts.relationships(ids: [accountId])) async let relationships: [Relationship] = client.get(endpoint: Accounts.relationships(ids: [accountId]))
do { do {
return try await .init(account: account, return try await .init(account: account,

View file

@ -10,15 +10,15 @@ public enum AccountsListMode {
var title: LocalizedStringKey { var title: LocalizedStringKey {
switch self { switch self {
case .following: case .following:
return "account.following" "account.following"
case .followers: case .followers:
return "account.followers" "account.followers"
case .favoritedBy: case .favoritedBy:
return "account.favorited-by" "account.favorited-by"
case .rebloggedBy: case .rebloggedBy:
return "account.boosted-by" "account.boosted-by"
case .accountsList: case .accountsList:
return "" ""
} }
} }
} }
@ -76,7 +76,7 @@ class AccountsListViewModel: ObservableObject {
} }
nextPageId = link?.maxId nextPageId = link?.maxId
relationships = try await client.get(endpoint: relationships = try await client.get(endpoint:
Accounts.relationships(ids: accounts.map { $0.id })) Accounts.relationships(ids: accounts.map(\.id)))
state = .display(accounts: accounts, state = .display(accounts: accounts,
relationships: relationships, relationships: relationships,
nextPageState: link?.maxId != nil ? .hasNextPage : .none) nextPageState: link?.maxId != nil ? .hasNextPage : .none)
@ -108,7 +108,7 @@ class AccountsListViewModel: ObservableObject {
} }
accounts.append(contentsOf: newAccounts) accounts.append(contentsOf: newAccounts)
let newRelationships: [Relationship] = let newRelationships: [Relationship] =
try await client.get(endpoint: Accounts.relationships(ids: newAccounts.map { $0.id })) try await client.get(endpoint: Accounts.relationships(ids: newAccounts.map(\.id)))
relationships.append(contentsOf: newRelationships) relationships.append(contentsOf: newRelationships)
self.nextPageId = link?.maxId self.nextPageId = link?.maxId

View file

@ -20,23 +20,21 @@ struct EditFilterView: View {
@State private var filterAction: ServerFilter.Action @State private var filterAction: ServerFilter.Action
@State private var expiresAt: Date? @State private var expiresAt: Date?
@State private var expirySelection: Duration @State private var expirySelection: Duration
enum Fields { enum Fields {
case title, newKeyword case title, newKeyword
} }
@FocusState private var focusedField: Fields? @FocusState private var focusedField: Fields?
private var data: ServerFilterData { private var data: ServerFilterData {
var expiresIn: String? let expiresIn: String? = switch expirySelection {
// we add 50 seconds, otherwise we immediately show 6d for a 7d filter (6d, 23h, 59s)
switch expirySelection {
case .infinite: case .infinite:
expiresIn = "" // need to send an empty value in order for the server to clear this field in the filter "" // need to send an empty value in order for the server to clear this field in the filter
case .custom: case .custom:
expiresIn = String(Int(expiresAt?.timeIntervalSince(Date()) ?? 0) + 50) String(Int(expiresAt?.timeIntervalSince(Date()) ?? 0) + 50)
default: default:
expiresIn = String(expirySelection.rawValue + 50) String(expirySelection.rawValue + 50)
} }
return ServerFilterData(title: title, return ServerFilterData(title: title,
@ -100,7 +98,7 @@ struct EditFilterView: View {
} }
if expirySelection != .infinite { if expirySelection != .infinite {
DatePicker("filter.edit.expiry.date-time", DatePicker("filter.edit.expiry.date-time",
selection: Binding<Date>(get: { self.expiresAt ?? Date() }, set: { self.expiresAt = $0 }), selection: Binding<Date>(get: { expiresAt ?? Date() }, set: { expiresAt = $0 }),
displayedComponents: [.date, .hourAndMinute]) displayedComponents: [.date, .hourAndMinute])
.disabled(expirySelection != .custom) .disabled(expirySelection != .custom)
} }

View file

@ -19,11 +19,11 @@ public struct FiltersListView: View {
public var body: some View { public var body: some View {
NavigationStack { NavigationStack {
Form { Form {
if !isLoading && filters.isEmpty { if !isLoading, filters.isEmpty {
EmptyView() EmptyView()
} else { } else {
Section { Section {
if isLoading && filters.isEmpty { if isLoading, filters.isEmpty {
ProgressView() ProgressView()
} else { } else {
ForEach(filters) { filter in ForEach(filters) { filter in
@ -31,7 +31,7 @@ public struct FiltersListView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(filter.title) Text(filter.title)
.font(.scaledSubheadline) .font(.scaledSubheadline)
Text("\(filter.context.map { $0.name }.joined(separator: ", "))") Text("\(filter.context.map(\.name).joined(separator: ", "))")
.font(.scaledBody) .font(.scaledBody)
.foregroundColor(.gray) .foregroundColor(.gray)
if filter.hasExpiry() { if filter.hasExpiry() {

View file

@ -31,7 +31,7 @@ let package = Package(
.product(name: "DesignSystem", package: "DesignSystem"), .product(name: "DesignSystem", package: "DesignSystem"),
], ],
swiftSettings: [ swiftSettings: [
.enableExperimentalFeature("StrictConcurrency") .enableExperimentalFeature("StrictConcurrency"),
] ]
), ),
] ]

View file

@ -25,9 +25,9 @@ public class AppAccountViewModel: ObservableObject {
var acct: String { var acct: String {
if let acct = appAccount.accountName { if let acct = appAccount.accountName {
return acct acct
} else { } else {
return "@\(account?.acct ?? "...")@\(appAccount.server)" "@\(account?.acct ?? "...")@\(appAccount.server)"
} }
} }

View file

@ -27,7 +27,7 @@ public class AppAccountsManager: ObservableObject {
public static var shared = AppAccountsManager() public static var shared = AppAccountsManager()
internal init() { init() {
var defaultAccount = AppAccount(server: AppInfo.defaultServer, accountName: nil, oauthToken: nil) var defaultAccount = AppAccount(server: AppInfo.defaultServer, accountName: nil, oauthToken: nil)
let keychainAccounts = AppAccount.retrieveAll() let keychainAccounts = AppAccount.retrieveAll()
availableAccounts = keychainAccounts availableAccounts = keychainAccounts

View file

@ -19,7 +19,7 @@ public struct AppAccountsSelectorView: View {
private var showNotificationBadge: Bool { private var showNotificationBadge: Bool {
accountsViewModel accountsViewModel
.filter { $0.account?.id != currentAccount.account?.id } .filter { $0.account?.id != currentAccount.account?.id }
.compactMap { $0.appAccount.oauthToken } .compactMap(\.appAccount.oauthToken)
.map { preferences.getNotificationsCount(for: $0) } .map { preferences.getNotificationsCount(for: $0) }
.reduce(0, +) > 0 .reduce(0, +) > 0
} }
@ -84,7 +84,7 @@ public struct AppAccountsSelectorView: View {
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
} }
}.overlay(alignment: .topTrailing) { }.overlay(alignment: .topTrailing) {
if (!currentAccount.followRequests.isEmpty || showNotificationBadge) && accountCreationEnabled { if !currentAccount.followRequests.isEmpty || showNotificationBadge, accountCreationEnabled {
Circle() Circle()
.fill(Color.red) .fill(Color.red)
.frame(width: 9, height: 9) .frame(width: 9, height: 9)

View file

@ -31,7 +31,7 @@ let package = Package(
.product(name: "DesignSystem", package: "DesignSystem"), .product(name: "DesignSystem", package: "DesignSystem"),
], ],
swiftSettings: [ swiftSettings: [
.enableExperimentalFeature("StrictConcurrency") .enableExperimentalFeature("StrictConcurrency"),
] ]
), ),
] ]

View file

@ -27,8 +27,8 @@ struct ConversationsListRow: View {
.accessibilityHidden(true) .accessibilityHidden(true)
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
HStack { HStack {
EmojiTextApp(.init(stringValue: conversation.accounts.map { $0.safeDisplayName }.joined(separator: ", ")), EmojiTextApp(.init(stringValue: conversation.accounts.map(\.safeDisplayName).joined(separator: ", ")),
emojis: conversation.accounts.flatMap { $0.emojis }) emojis: conversation.accounts.flatMap(\.emojis))
.font(.scaledSubheadline) .font(.scaledSubheadline)
.foregroundColor(theme.labelColor) .foregroundColor(theme.labelColor)
.emojiSize(Font.scaledSubheadlineFont.emojiSize) .emojiSize(Font.scaledSubheadlineFont.emojiSize)

View file

@ -18,9 +18,9 @@ public struct ConversationsListView: View {
private var conversations: Binding<[Conversation]> { private var conversations: Binding<[Conversation]> {
if viewModel.isLoadingFirstPage { if viewModel.isLoadingFirstPage {
return Binding.constant(Conversation.placeholders()) Binding.constant(Conversation.placeholders())
} else { } else {
return $viewModel.conversations $viewModel.conversations
} }
} }
@ -40,7 +40,7 @@ public struct ConversationsListView: View {
} }
Divider() Divider()
} }
} else if conversations.isEmpty && !viewModel.isLoadingFirstPage && !viewModel.isError { } else if conversations.isEmpty, !viewModel.isLoadingFirstPage, !viewModel.isError {
EmptyView(iconName: "tray", EmptyView(iconName: "tray",
title: "conversations.empty.title", title: "conversations.empty.title",
message: "conversations.empty.message") message: "conversations.empty.message")
@ -79,7 +79,7 @@ public struct ConversationsListView: View {
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
StatusEditorToolbarItem(visibility: .direct) StatusEditorToolbarItem(visibility: .direct)
if UIDevice.current.userInterfaceIdiom == .pad && !preferences.showiPadSecondaryColumn { if UIDevice.current.userInterfaceIdiom == .pad, !preferences.showiPadSecondaryColumn {
SecondaryColumnToolbarItem() SecondaryColumnToolbarItem()
} }
} }

View file

@ -60,11 +60,10 @@ class ConversationsListViewModel: ObservableObject {
func favorite(conversation: Conversation) async { func favorite(conversation: Conversation) async {
guard let client, let message = conversation.lastStatus else { return } guard let client, let message = conversation.lastStatus else { return }
let endpoint: Endpoint let endpoint: Endpoint = if message.favourited ?? false {
if message.favourited ?? false { Statuses.unfavorite(id: message.id)
endpoint = Statuses.unfavorite(id: message.id)
} else { } else {
endpoint = Statuses.favorite(id: message.id) Statuses.favorite(id: message.id)
} }
do { do {
let status: Status = try await client.post(endpoint: endpoint) let status: Status = try await client.post(endpoint: endpoint)
@ -74,11 +73,10 @@ class ConversationsListViewModel: ObservableObject {
func bookmark(conversation: Conversation) async { func bookmark(conversation: Conversation) async {
guard let client, let message = conversation.lastStatus else { return } guard let client, let message = conversation.lastStatus else { return }
let endpoint: Endpoint let endpoint: Endpoint = if message.bookmarked ?? false {
if message.bookmarked ?? false { Statuses.unbookmark(id: message.id)
endpoint = Statuses.unbookmark(id: message.id)
} else { } else {
endpoint = Statuses.bookmark(id: message.id) Statuses.bookmark(id: message.id)
} }
do { do {
let status: Status = try await client.post(endpoint: endpoint) let status: Status = try await client.post(endpoint: endpoint)

View file

@ -34,7 +34,7 @@ let package = Package(
.product(name: "EmojiText", package: "EmojiText"), .product(name: "EmojiText", package: "EmojiText"),
], ],
swiftSettings: [ swiftSettings: [
.enableExperimentalFeature("StrictConcurrency") .enableExperimentalFeature("StrictConcurrency"),
] ]
), ),
] ]

View file

@ -160,19 +160,18 @@ public struct ConstellationDark: ColorSet {
public var scheme: ColorScheme = .dark public var scheme: ColorScheme = .dark
public var tintColor: Color = .init(hex: 0xFFD966) public var tintColor: Color = .init(hex: 0xFFD966)
public var primaryBackgroundColor: Color = .init(hex: 0x09192C) public var primaryBackgroundColor: Color = .init(hex: 0x09192C)
public var secondaryBackgroundColor: Color = .init(hex: 0x304c7a) public var secondaryBackgroundColor: Color = .init(hex: 0x304C7A)
public var labelColor: Color = .init(hex: 0xE2E4E2) public var labelColor: Color = .init(hex: 0xE2E4E2)
public init() {} public init() {}
} }
public struct ConstellationLight: ColorSet { public struct ConstellationLight: ColorSet {
public var name: ColorSetName = .constellationLight public var name: ColorSetName = .constellationLight
public var scheme: ColorScheme = .light public var scheme: ColorScheme = .light
public var tintColor: Color = .init(hex: 0xc82238) public var tintColor: Color = .init(hex: 0xC82238)
public var primaryBackgroundColor: Color = .init(hex: 0xf4f5f7) public var primaryBackgroundColor: Color = .init(hex: 0xF4F5F7)
public var secondaryBackgroundColor: Color = .init(hex: 0xacc7e5) public var secondaryBackgroundColor: Color = .init(hex: 0xACC7E5)
public var labelColor: Color = .black public var labelColor: Color = .black
public init() {} public init() {}

View file

@ -27,7 +27,7 @@ extension Color: RawRepresentable {
} }
public var rawValue: Int { public var rawValue: Int {
guard let coreImageColor = coreImageColor else { guard let coreImageColor else {
return 0 return 0
} }
let red = Int(coreImageColor.red * 255 + 0.5) let red = Int(coreImageColor.red * 255 + 0.5)
@ -37,7 +37,7 @@ extension Color: RawRepresentable {
} }
private var coreImageColor: CIColor? { private var coreImageColor: CIColor? {
return CIColor(color: .init(self)) CIColor(color: .init(self))
} }
} }

View file

@ -22,15 +22,15 @@ public class Theme: ObservableObject {
public var title: LocalizedStringKey { public var title: LocalizedStringKey {
switch self { switch self {
case .system: case .system:
return "settings.display.font.system" "settings.display.font.system"
case .openDyslexic: case .openDyslexic:
return "Open Dyslexic" "Open Dyslexic"
case .hyperLegible: case .hyperLegible:
return "Hyper Legible" "Hyper Legible"
case .SFRounded: case .SFRounded:
return "SF Rounded" "SF Rounded"
case .custom: case .custom:
return "settings.display.font.custom" "settings.display.font.custom"
} }
} }
} }
@ -41,9 +41,9 @@ public class Theme: ObservableObject {
public var description: LocalizedStringKey { public var description: LocalizedStringKey {
switch self { switch self {
case .leading: case .leading:
return "enum.avatar-position.leading" "enum.avatar-position.leading"
case .top: case .top:
return "enum.avatar-position.top" "enum.avatar-position.top"
} }
} }
} }
@ -54,9 +54,9 @@ public class Theme: ObservableObject {
public var description: LocalizedStringKey { public var description: LocalizedStringKey {
switch self { switch self {
case .circle: case .circle:
return "enum.avatar-shape.circle" "enum.avatar-shape.circle"
case .rounded: case .rounded:
return "enum.avatar-shape.rounded" "enum.avatar-shape.rounded"
} }
} }
} }
@ -67,11 +67,11 @@ public class Theme: ObservableObject {
public var description: LocalizedStringKey { public var description: LocalizedStringKey {
switch self { switch self {
case .full: case .full:
return "enum.status-actions-display.all" "enum.status-actions-display.all"
case .discret: case .discret:
return "enum.status-actions-display.only-buttons" "enum.status-actions-display.only-buttons"
case .none: case .none:
return "enum.status-actions-display.no-buttons" "enum.status-actions-display.no-buttons"
} }
} }
} }
@ -82,11 +82,11 @@ public class Theme: ObservableObject {
public var description: LocalizedStringKey { public var description: LocalizedStringKey {
switch self { switch self {
case .large: case .large:
return "enum.status-display-style.large" "enum.status-display-style.large"
case .medium: case .medium:
return "enum.status-display-style.medium" "enum.status-display-style.medium"
case .compact: case .compact:
return "enum.status-display-style.compact" "enum.status-display-style.compact"
} }
} }
} }

View file

@ -97,7 +97,7 @@ struct ThemeApplier: ViewModifier {
private func allWindows() -> [UIWindow] { private func allWindows() -> [UIWindow] {
UIApplication.shared.connectedScenes UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene } .compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows } .flatMap(\.windows)
} }
#endif #endif
} }

View file

@ -33,9 +33,9 @@ public struct AvatarView: View {
var cornerRadius: CGFloat { var cornerRadius: CGFloat {
switch self { switch self {
case .badge, .boost, .list: case .badge, .boost, .list:
return size.width / 2 size.width / 2
default: default:
return 4 4
} }
} }
} }
@ -55,7 +55,7 @@ public struct AvatarView: View {
.fill(.gray) .fill(.gray)
.frame(width: size.size.width, height: size.size.height) .frame(width: size.size.width, height: size.size.height)
} else { } else {
LazyImage(request: url.map{ makeImageRequest(for: $0) }) { state in LazyImage(request: url.map { makeImageRequest(for: $0) }) { state in
if let image = state.image { if let image = state.image {
image image
.resizable() .resizable()
@ -80,9 +80,9 @@ public struct AvatarView: View {
private var clipShape: some Shape { private var clipShape: some Shape {
switch theme.avatarShape { switch theme.avatarShape {
case .circle: case .circle:
return AnyShape(Circle()) AnyShape(Circle())
case .rounded: case .rounded:
return AnyShape(RoundedRectangle(cornerRadius: size.cornerRadius)) AnyShape(RoundedRectangle(cornerRadius: size.cornerRadius))
} }
} }
} }

View file

@ -38,6 +38,6 @@ public struct EmojiTextApp: View {
private func isRTL() -> Bool { private func isRTL() -> Bool {
// Arabic, Hebrew, Persian, Urdu, Kurdish, Azeri, Dhivehi // Arabic, Hebrew, Persian, Urdu, Kurdish, Azeri, Dhivehi
return ["ar", "he", "fa", "ur", "ku", "az", "dv"].contains(language) ["ar", "he", "fa", "ur", "ku", "az", "dv"].contains(language)
} }
} }

View file

@ -59,7 +59,7 @@ public struct SecondaryColumnToolbarItem: ToolbarContent {
public init() {} public init() {}
public var body: some ToolbarContent { public var body: some ToolbarContent {
ToolbarItem(placement: isSecondaryColumn ? .navigationBarLeading : .navigationBarTrailing) { ToolbarItem(placement: isSecondaryColumn ? .navigationBarLeading : .navigationBarTrailing) {
Button { Button {
withAnimation { withAnimation {

View file

@ -29,7 +29,7 @@ let package = Package(
.product(name: "KeychainSwift", package: "keychain-swift"), .product(name: "KeychainSwift", package: "keychain-swift"),
], ],
swiftSettings: [ swiftSettings: [
.enableExperimentalFeature("StrictConcurrency") .enableExperimentalFeature("StrictConcurrency"),
] ]
), ),
] ]

View file

@ -48,7 +48,7 @@ public class CurrentAccount: ObservableObject {
} }
public func fetchConnections() async { public func fetchConnections() async {
guard let client = client else { return } guard let client else { return }
do { do {
let connections: [String] = try await client.get(endpoint: Instances.peers) let connections: [String] = try await client.get(endpoint: Instances.peers)
client.addConnections(connections) client.addConnections(connections)
@ -56,7 +56,7 @@ public class CurrentAccount: ObservableObject {
} }
public func fetchCurrentAccount() async { public func fetchCurrentAccount() async {
guard let client = client, client.isAuth else { guard let client, client.isAuth else {
account = nil account = nil
return return
} }

View file

@ -41,7 +41,7 @@ public class CurrentInstance: ObservableObject {
} }
public func fetchCurrentInstance() async { public func fetchCurrentInstance() async {
guard let client = client else { return } guard let client else { return }
instance = try? await client.get(endpoint: Instances.instance) instance = try? await client.get(endpoint: Instances.instance)
} }
} }

View file

@ -15,37 +15,37 @@ public enum Duration: Int, CaseIterable {
public var description: LocalizedStringKey { public var description: LocalizedStringKey {
switch self { switch self {
case .infinite: case .infinite:
return "enum.durations.infinite" "enum.durations.infinite"
case .fiveMinutes: case .fiveMinutes:
return "enum.durations.fiveMinutes" "enum.durations.fiveMinutes"
case .thirtyMinutes: case .thirtyMinutes:
return "enum.durations.thirtyMinutes" "enum.durations.thirtyMinutes"
case .oneHour: case .oneHour:
return "enum.durations.oneHour" "enum.durations.oneHour"
case .sixHours: case .sixHours:
return "enum.durations.sixHours" "enum.durations.sixHours"
case .twelveHours: case .twelveHours:
return "enum.durations.twelveHours" "enum.durations.twelveHours"
case .oneDay: case .oneDay:
return "enum.durations.oneDay" "enum.durations.oneDay"
case .threeDays: case .threeDays:
return "enum.durations.threeDays" "enum.durations.threeDays"
case .sevenDays: case .sevenDays:
return "enum.durations.sevenDays" "enum.durations.sevenDays"
case .custom: case .custom:
return "enum.durations.custom" "enum.durations.custom"
} }
} }
public static func mutingDurations() -> [Duration] { public static func mutingDurations() -> [Duration] {
return Self.allCases.filter { $0 != .custom } allCases.filter { $0 != .custom }
} }
public static func filterDurations() -> [Duration] { public static func filterDurations() -> [Duration] {
return [.infinite, .thirtyMinutes, .oneHour, .sixHours, .twelveHours, .oneDay, .sevenDays, .custom] [.infinite, .thirtyMinutes, .oneHour, .sixHours, .twelveHours, .oneDay, .sevenDays, .custom]
} }
public static func pollDurations() -> [Duration] { public static func pollDurations() -> [Duration] {
return [.fiveMinutes, .thirtyMinutes, .oneHour, .sixHours, .twelveHours, .oneDay, .threeDays, .sevenDays] [.fiveMinutes, .thirtyMinutes, .oneHour, .sixHours, .twelveHours, .oneDay, .threeDays, .sevenDays]
} }
} }

View file

@ -7,15 +7,15 @@ public enum PollVotingFrequency: String, CaseIterable {
public var canVoteMultipleTimes: Bool { public var canVoteMultipleTimes: Bool {
switch self { switch self {
case .multipleVotes: return true case .multipleVotes: true
case .oneVote: return false case .oneVote: false
} }
} }
public var displayString: LocalizedStringKey { public var displayString: LocalizedStringKey {
switch self { switch self {
case .oneVote: return "env.poll-vote-frequency.one" case .oneVote: "env.poll-vote-frequency.one"
case .multipleVotes: return "env.poll-vote-frequency.multiple" case .multipleVotes: "env.poll-vote-frequency.multiple"
} }
} }
} }

View file

@ -7,8 +7,8 @@ public enum PreferredShareButtonBehavior: Int, CaseIterable, Codable {
public var title: LocalizedStringKey { public var title: LocalizedStringKey {
switch self { switch self {
case .linkOnly: return "settings.content.sharing.share-behavior.link-only" case .linkOnly: "settings.content.sharing.share-behavior.link-only"
case .linkAndText: return "settings.content.sharing.share-behavior.link-and-text" case .linkAndText: "settings.content.sharing.share-behavior.link-and-text"
} }
} }
} }

View file

@ -7,8 +7,8 @@ import Network
import SwiftUI import SwiftUI
import UserNotifications import UserNotifications
extension UNNotificationResponse: @unchecked Sendable { } extension UNNotificationResponse: @unchecked Sendable {}
extension UNUserNotificationCenter: @unchecked Sendable { } extension UNUserNotificationCenter: @unchecked Sendable {}
public struct PushAccount: Equatable { public struct PushAccount: Equatable {
public let server: String public let server: String
@ -157,7 +157,7 @@ extension PushNotificationsService: UNUserNotificationCenterDelegate {
extension Data { extension Data {
var hexString: String { var hexString: String {
return map { String(format: "%02.2hhx", arguments: [$0]) }.joined() map { String(format: "%02.2hhx", arguments: [$0]) }.joined()
} }
} }
@ -199,7 +199,7 @@ public class PushNotificationSubscriptionSettings: ObservableObject {
} }
public func updateSubscription() async { public func updateSubscription() async {
guard let pushToken = pushToken else { return } guard let pushToken else { return }
let client = Client(server: account.server, oauthToken: account.token) let client = Client(server: account.server, oauthToken: account.token)
do { do {
var listenerURL = PushNotificationsService.Constants.endpoint var listenerURL = PushNotificationsService.Constants.endpoint

View file

@ -45,25 +45,25 @@ public enum SheetDestination: Identifiable {
switch self { switch self {
case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor, case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor,
.mentionStatusEditor, .settings, .accountPushNotficationsSettings: .mentionStatusEditor, .settings, .accountPushNotficationsSettings:
return "statusEditor" "statusEditor"
case .listEdit: case .listEdit:
return "listEdit" "listEdit"
case .listAddAccount: case .listAddAccount:
return "listAddAccount" "listAddAccount"
case .addAccount: case .addAccount:
return "addAccount" "addAccount"
case .addTagGroup: case .addTagGroup:
return "addTagGroup" "addTagGroup"
case .addRemoteLocalTimeline: case .addRemoteLocalTimeline:
return "addRemoteLocalTimeline" "addRemoteLocalTimeline"
case .statusEditHistory: case .statusEditHistory:
return "statusEditHistory" "statusEditHistory"
case .report: case .report:
return "report" "report"
case .shareImage: case .shareImage:
return "shareImage" "shareImage"
case .editTagGroup: case .editTagGroup:
return "editTagGroup" "editTagGroup"
} }
} }
} }
@ -83,9 +83,9 @@ public class RouterPath: ObservableObject {
} }
public func handleStatus(status: AnyStatus, url: URL) -> OpenURLAction.Result { public func handleStatus(status: AnyStatus, url: URL) -> OpenURLAction.Result {
if url.pathComponents.count == 3 && url.pathComponents[1] == "tags" && if url.pathComponents.count == 3, url.pathComponents[1] == "tags",
url.host() == status.account.url?.host(), url.host() == status.account.url?.host(),
let tag = url.pathComponents.last let tag = url.pathComponents.last
{ {
// OK this test looks weird but it's // OK this test looks weird but it's
// A 3 component path i.e. ["/", "tags", "tagname"] // A 3 component path i.e. ["/", "tags", "tagname"]
@ -97,7 +97,7 @@ public class RouterPath: ObservableObject {
} else if let mention = status.mentions.first(where: { $0.url == url }) { } else if let mention = status.mentions.first(where: { $0.url == url }) {
navigate(to: .accountDetail(id: mention.id)) navigate(to: .accountDetail(id: mention.id))
return .handled return .handled
} else if let client = client, } else if let client,
client.isAuth, client.isAuth,
client.hasConnection(with: url), client.hasConnection(with: url),
let id = Int(url.lastPathComponent) let id = Int(url.lastPathComponent)
@ -126,7 +126,7 @@ public class RouterPath: ObservableObject {
await navigateToAccountFrom(acct: acct, url: url) await navigateToAccountFrom(acct: acct, url: url)
} }
return .handled return .handled
} else if let client = client, } else if let client,
client.isAuth, client.isAuth,
client.hasConnection(with: url), client.hasConnection(with: url),
let id = Int(url.lastPathComponent) let id = Int(url.lastPathComponent)

View file

@ -1,16 +1,16 @@
import AudioToolbox
import AVKit import AVKit
import CoreHaptics import CoreHaptics
import UIKit import UIKit
import AudioToolbox
@MainActor @MainActor
public class SoundEffectManager { public class SoundEffectManager {
public static let shared: SoundEffectManager = .init() public static let shared: SoundEffectManager = .init()
public enum SoundEffect: String, CaseIterable { public enum SoundEffect: String, CaseIterable {
case pull, refresh, tootSent, tabSelection, bookmark, boost, favorite, share case pull, refresh, tootSent, tabSelection, bookmark, boost, favorite, share
} }
var pullId: SystemSoundID = 0 var pullId: SystemSoundID = 0
var refreshId: SystemSoundID = 1 var refreshId: SystemSoundID = 1
var tootSentId: SystemSoundID = 2 var tootSentId: SystemSoundID = 2
@ -19,9 +19,9 @@ public class SoundEffectManager {
var boostId: SystemSoundID = 5 var boostId: SystemSoundID = 5
var favoriteId: SystemSoundID = 6 var favoriteId: SystemSoundID = 6
var shareId: SystemSoundID = 7 var shareId: SystemSoundID = 7
private let userPreferences = UserPreferences.shared private let userPreferences = UserPreferences.shared
private init() { private init() {
registerSounds() registerSounds()
} }
@ -50,7 +50,7 @@ public class SoundEffectManager {
} }
} }
} }
public func playSound(of type: SoundEffect) { public func playSound(of type: SoundEffect) {
guard userPreferences.soundEffectEnabled else { return } guard userPreferences.soundEffectEnabled else { return }
switch type { switch type {

View file

@ -73,7 +73,7 @@ public class StreamWatcher: ObservableObject {
private func receiveMessage() { private func receiveMessage() {
task?.receive(completionHandler: { [weak self] result in task?.receive(completionHandler: { [weak self] result in
guard let self = self else { return } guard let self else { return }
switch result { switch result {
case let .success(message): case let .success(message):
switch message { switch message {
@ -83,8 +83,8 @@ public class StreamWatcher: ObservableObject {
print("Error decoding streaming event string") print("Error decoding streaming event string")
return return
} }
let rawEvent = try self.decoder.decode(RawStreamEvent.self, from: data) let rawEvent = try decoder.decode(RawStreamEvent.self, from: data)
if let event = self.rawEventToEvent(rawEvent: rawEvent) { if let event = rawEventToEvent(rawEvent: rawEvent) {
Task { @MainActor in Task { @MainActor in
self.events.append(event) self.events.append(event)
self.latestEvent = event self.latestEvent = event
@ -101,10 +101,10 @@ public class StreamWatcher: ObservableObject {
break break
} }
self.receiveMessage() receiveMessage()
case .failure: case .failure:
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(self.retryDelay)) { DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(retryDelay)) {
self.retryDelay += 30 self.retryDelay += 30
self.stopWatching() self.stopWatching()
self.connect() self.connect()

View file

@ -65,9 +65,9 @@ public class UserPreferences: ObservableObject {
public var description: LocalizedStringKey { public var description: LocalizedStringKey {
switch self { switch self {
case .iconWithText: case .iconWithText:
return "enum.swipeactions.icon-with-text" "enum.swipeactions.icon-with-text"
case .iconOnly: case .iconOnly:
return "enum.swipeactions.icon-only" "enum.swipeactions.icon-only"
} }
} }
@ -84,52 +84,52 @@ public class UserPreferences: ObservableObject {
public var postVisibility: Models.Visibility { public var postVisibility: Models.Visibility {
if useInstanceContentSettings { if useInstanceContentSettings {
return serverPreferences?.postVisibility ?? .pub serverPreferences?.postVisibility ?? .pub
} else { } else {
return appDefaultPostVisibility appDefaultPostVisibility
} }
} }
public func conformReplyVisibilityConstraints() { public func conformReplyVisibilityConstraints() {
appDefaultReplyVisibility = getReplyVisibility() appDefaultReplyVisibility = getReplyVisibility()
} }
private func getReplyVisibility() -> Models.Visibility { private func getReplyVisibility() -> Models.Visibility {
getMinVisibility(postVisibility, appDefaultReplyVisibility) getMinVisibility(postVisibility, appDefaultReplyVisibility)
} }
public func getReplyVisibility(of status: Status) -> Models.Visibility { public func getReplyVisibility(of status: Status) -> Models.Visibility {
getMinVisibility(getReplyVisibility(), status.visibility) getMinVisibility(getReplyVisibility(), status.visibility)
} }
private func getMinVisibility(_ vis1: Models.Visibility, _ vis2: Models.Visibility) -> Models.Visibility { private func getMinVisibility(_ vis1: Models.Visibility, _ vis2: Models.Visibility) -> Models.Visibility {
let no1 = Self.getIntOfVisibility(vis1) let no1 = Self.getIntOfVisibility(vis1)
let no2 = Self.getIntOfVisibility(vis2) let no2 = Self.getIntOfVisibility(vis2)
return no1 < no2 ? vis1 : vis2 return no1 < no2 ? vis1 : vis2
} }
public var postIsSensitive: Bool { public var postIsSensitive: Bool {
if useInstanceContentSettings { if useInstanceContentSettings {
return serverPreferences?.postIsSensitive ?? false serverPreferences?.postIsSensitive ?? false
} else { } else {
return appDefaultPostsSensitive appDefaultPostsSensitive
} }
} }
public var autoExpandSpoilers: Bool { public var autoExpandSpoilers: Bool {
if useInstanceContentSettings { if useInstanceContentSettings {
return serverPreferences?.autoExpandSpoilers ?? true serverPreferences?.autoExpandSpoilers ?? true
} else { } else {
return appAutoExpandSpoilers appAutoExpandSpoilers
} }
} }
public var autoExpandMedia: ServerPreferences.AutoExpandMedia { public var autoExpandMedia: ServerPreferences.AutoExpandMedia {
if useInstanceContentSettings { if useInstanceContentSettings {
return serverPreferences?.autoExpandMedia ?? .hideSensitive serverPreferences?.autoExpandMedia ?? .hideSensitive
} else { } else {
return appAutoExpandMedia appAutoExpandMedia
} }
} }
@ -174,17 +174,17 @@ public class UserPreferences: ObservableObject {
copy.insert(isoCode, at: 0) copy.insert(isoCode, at: 0)
recentlyUsedLanguages = Array(copy.prefix(3)) recentlyUsedLanguages = Array(copy.prefix(3))
} }
public static func getIntOfVisibility(_ vis: Models.Visibility) -> Int { public static func getIntOfVisibility(_ vis: Models.Visibility) -> Int {
switch vis { switch vis {
case .direct: case .direct:
return 0 0
case .priv: case .priv:
return 1 1
case .unlisted: case .unlisted:
return 2 2
case .pub: case .pub:
return 3 3
} }
} }
} }

View file

@ -35,7 +35,7 @@ let package = Package(
.product(name: "DesignSystem", package: "DesignSystem"), .product(name: "DesignSystem", package: "DesignSystem"),
], ],
swiftSettings: [ swiftSettings: [
.enableExperimentalFeature("StrictConcurrency") .enableExperimentalFeature("StrictConcurrency"),
] ]
), ),
] ]

View file

@ -126,7 +126,7 @@ public struct ExploreView: View {
@ViewBuilder @ViewBuilder
private func makeSearchResultsView(results: SearchResults) -> some View { private func makeSearchResultsView(results: SearchResults) -> some View {
if !results.accounts.isEmpty && (viewModel.searchScope == .all || viewModel.searchScope == .people) { if !results.accounts.isEmpty, viewModel.searchScope == .all || viewModel.searchScope == .people {
Section("explore.section.users") { Section("explore.section.users") {
ForEach(results.accounts) { account in ForEach(results.accounts) { account in
if let relationship = results.relationships.first(where: { $0.id == account.id }) { if let relationship = results.relationships.first(where: { $0.id == account.id }) {
@ -136,7 +136,7 @@ public struct ExploreView: View {
} }
} }
} }
if !results.hashtags.isEmpty && (viewModel.searchScope == .all || viewModel.searchScope == .hashtags) { if !results.hashtags.isEmpty, viewModel.searchScope == .all || viewModel.searchScope == .hashtags {
Section("explore.section.tags") { Section("explore.section.tags") {
ForEach(results.hashtags) { tag in ForEach(results.hashtags) { tag in
TagRowView(tag: tag) TagRowView(tag: tag)
@ -145,7 +145,7 @@ public struct ExploreView: View {
} }
} }
} }
if !results.statuses.isEmpty && (viewModel.searchScope == .all || viewModel.searchScope == .posts) { if !results.statuses.isEmpty, viewModel.searchScope == .all || viewModel.searchScope == .posts {
Section("explore.section.posts") { Section("explore.section.posts") {
ForEach(results.statuses) { status in ForEach(results.statuses) { status in
StatusRowView(viewModel: { .init(status: status, client: client, routerPath: routerPath) }) StatusRowView(viewModel: { .init(status: status, client: client, routerPath: routerPath) })

View file

@ -11,13 +11,13 @@ class ExploreViewModel: ObservableObject {
var localizedString: LocalizedStringKey { var localizedString: LocalizedStringKey {
switch self { switch self {
case .all: case .all:
return .init("explore.scope.all") .init("explore.scope.all")
case .people: case .people:
return .init("explore.scope.people") .init("explore.scope.people")
case .hashtags: case .hashtags:
return .init("explore.scope.hashtags") .init("explore.scope.hashtags")
case .posts: case .posts:
return .init("explore.scope.posts") .init("explore.scope.posts")
} }
} }
} }
@ -77,7 +77,7 @@ class ExploreViewModel: ObservableObject {
trendingStatuses = data.trendingStatuses trendingStatuses = data.trendingStatuses
trendingLinks = data.trendingLinks trendingLinks = data.trendingLinks
suggestedAccountsRelationShips = try await client.get(endpoint: Accounts.relationships(ids: suggestedAccounts.map { $0.id })) suggestedAccountsRelationShips = try await client.get(endpoint: Accounts.relationships(ids: suggestedAccounts.map(\.id)))
withAnimation { withAnimation {
isLoaded = true isLoaded = true
} }
@ -118,7 +118,7 @@ class ExploreViewModel: ObservableObject {
following: nil), following: nil),
forceVersion: .v2) forceVersion: .v2)
let relationships: [Relationship] = let relationships: [Relationship] =
try await client.get(endpoint: Accounts.relationships(ids: results.accounts.map { $0.id })) try await client.get(endpoint: Accounts.relationships(ids: results.accounts.map(\.id)))
results.relationships = relationships results.relationships = relationships
withAnimation { withAnimation {
self.results[searchQuery] = results self.results[searchQuery] = results

View file

@ -31,7 +31,7 @@ let package = Package(
.product(name: "DesignSystem", package: "DesignSystem"), .product(name: "DesignSystem", package: "DesignSystem"),
], ],
swiftSettings: [ swiftSettings: [
.enableExperimentalFeature("StrictConcurrency") .enableExperimentalFeature("StrictConcurrency"),
] ]
), ),
] ]

View file

@ -25,7 +25,7 @@ let package = Package(
"SwiftSoup", "SwiftSoup",
], ],
swiftSettings: [ swiftSettings: [
.enableExperimentalFeature("StrictConcurrency") .enableExperimentalFeature("StrictConcurrency"),
] ]
), ),
.testTarget( .testTarget(

View file

@ -60,11 +60,11 @@ public final class Account: Codable, Identifiable, Hashable, Sendable, Equatable
public let discoverable: Bool? public let discoverable: Bool?
public var haveAvatar: Bool { public var haveAvatar: Bool {
return avatar.lastPathComponent != "missing.png" avatar.lastPathComponent != "missing.png"
} }
public var haveHeader: Bool { public var haveHeader: Bool {
return header.lastPathComponent != "missing.png" header.lastPathComponent != "missing.png"
} }
public init(id: String, username: String, displayName: String?, avatar: URL, header: URL, acct: String, note: HTMLString, createdAt: ServerDate, followersCount: Int, followingCount: Int, statusesCount: Int, lastStatusAt: String? = nil, fields: [Account.Field], locked: Bool, emojis: [Emoji], url: URL? = nil, source: Account.Source? = nil, bot: Bool, discoverable: Bool? = nil) { public init(id: String, username: String, displayName: String?, avatar: URL, header: URL, acct: String, note: HTMLString, createdAt: ServerDate, followersCount: Int, followingCount: Int, statusesCount: Int, lastStatusAt: String? = nil, fields: [Account.Field], locked: Bool, emojis: [Emoji], url: URL? = nil, source: Account.Source? = nil, bot: Bool, discoverable: Bool? = nil) {

View file

@ -6,17 +6,17 @@ class DateFormatterCache: @unchecked Sendable {
let createdAtRelativeFormatter: RelativeDateTimeFormatter let createdAtRelativeFormatter: RelativeDateTimeFormatter
let createdAtShortDateFormatted: DateFormatter let createdAtShortDateFormatted: DateFormatter
let createdAtDateFormatter: DateFormatter let createdAtDateFormatter: DateFormatter
init() { init() {
let createdAtRelativeFormatter = RelativeDateTimeFormatter() let createdAtRelativeFormatter = RelativeDateTimeFormatter()
createdAtRelativeFormatter.unitsStyle = .short createdAtRelativeFormatter.unitsStyle = .short
self.createdAtRelativeFormatter = createdAtRelativeFormatter self.createdAtRelativeFormatter = createdAtRelativeFormatter
let createdAtShortDateFormatted = DateFormatter() let createdAtShortDateFormatted = DateFormatter()
createdAtShortDateFormatted.dateStyle = .short createdAtShortDateFormatted.dateStyle = .short
createdAtShortDateFormatted.timeStyle = .none createdAtShortDateFormatted.timeStyle = .none
self.createdAtShortDateFormatted = createdAtShortDateFormatted self.createdAtShortDateFormatted = createdAtShortDateFormatted
let createdAtDateFormatter = DateFormatter() let createdAtDateFormatter = DateFormatter()
createdAtDateFormatter.calendar = .init(identifier: .iso8601) createdAtDateFormatter.calendar = .init(identifier: .iso8601)
createdAtDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" createdAtDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"

View file

@ -11,7 +11,7 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
public var asMarkdown: String = "" public var asMarkdown: String = ""
public var asRawText: String = "" public var asRawText: String = ""
public var statusesURLs = [URL]() public var statusesURLs = [URL]()
private(set) public var links = [Link]() public private(set) var links = [Link]()
public var asSafeMarkdownAttributedString: AttributedString = .init() public var asSafeMarkdownAttributedString: AttributedString = .init()
private var main_regex: NSRegularExpression? private var main_regex: NSRegularExpression?
@ -155,16 +155,16 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
let finish = asMarkdown.endIndex let finish = asMarkdown.endIndex
var linkRef = href var linkRef = href
// Try creating a URL from the string. If it fails, try URL encoding // Try creating a URL from the string. If it fails, try URL encoding
// the string first. // the string first.
var url = URL(string: href) var url = URL(string: href)
if url == nil { if url == nil {
url = URL(string: href, encodePath: true) url = URL(string: href, encodePath: true)
} }
if let linkUrl = url { if let linkUrl = url {
linkRef = linkUrl.absoluteString linkRef = linkUrl.absoluteString
let displayString = asMarkdown[start..<finish] let displayString = asMarkdown[start ..< finish]
links.append(Link(linkUrl, displayString: String(displayString))) links.append(Link(linkUrl, displayString: String(displayString)))
} }
@ -203,19 +203,19 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
self.displayString = displayString self.displayString = displayString
switch displayString.first { switch displayString.first {
case "@": case "@":
self.type = .mention type = .mention
self.title = displayString title = displayString
case "#": case "#":
self.type = .hashtag type = .hashtag
self.title = String(displayString.dropFirst()) title = String(displayString.dropFirst())
default: default:
self.type = .url type = .url
var hostNameUrl = url.host ?? url.absoluteString var hostNameUrl = url.host ?? url.absoluteString
if hostNameUrl.hasPrefix("www.") { if hostNameUrl.hasPrefix("www.") {
hostNameUrl = String(hostNameUrl.dropFirst(4)) hostNameUrl = String(hostNameUrl.dropFirst(4))
} }
self.title = hostNameUrl title = hostNameUrl
} }
} }
@ -227,35 +227,34 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
} }
} }
extension URL { public extension URL {
// It's common to use non-ASCII characters in URLs even though they're technically // It's common to use non-ASCII characters in URLs even though they're technically
// invalid characters. Every modern browser handles this by silently encoding // invalid characters. Every modern browser handles this by silently encoding
// the invalid characters on the user's behalf. However, trying to create a URL // the invalid characters on the user's behalf. However, trying to create a URL
// object with un-encoded characters will result in nil so we need to encode the // object with un-encoded characters will result in nil so we need to encode the
// invalid characters before creating the URL object. The unencoded version // invalid characters before creating the URL object. The unencoded version
// should still be shown in the displayed status. // should still be shown in the displayed status.
public init?(string: String, encodePath: Bool) { init?(string: String, encodePath: Bool) {
var encodedUrlString = "" var encodedUrlString = ""
if encodePath, if encodePath,
string.starts(with: "http://") || string.starts(with: "https://"), string.starts(with: "http://") || string.starts(with: "https://"),
var startIndex = string.firstIndex(of: "/") var startIndex = string.firstIndex(of: "/")
{ {
startIndex = string.index(startIndex, offsetBy: 1) startIndex = string.index(startIndex, offsetBy: 1)
// We don't want to encode the host portion of the URL // We don't want to encode the host portion of the URL
if var startIndex = string[startIndex...].firstIndex(of: "/") { if var startIndex = string[startIndex...].firstIndex(of: "/") {
encodedUrlString = String(string[...startIndex]) encodedUrlString = String(string[...startIndex])
while let endIndex = string[string.index(after: startIndex)...].firstIndex(of: "/") { while let endIndex = string[string.index(after: startIndex)...].firstIndex(of: "/") {
let componentStartIndex = string.index(after: startIndex) let componentStartIndex = string.index(after: startIndex)
encodedUrlString = encodedUrlString + (string[componentStartIndex...endIndex].addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "") encodedUrlString = encodedUrlString + (string[componentStartIndex ... endIndex].addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "")
startIndex = endIndex startIndex = endIndex
} }
// The last part of the path may have a query string appended to it // The last part of the path may have a query string appended to it
let componentStartIndex = string.index(after: startIndex) let componentStartIndex = string.index(after: startIndex)
if let queryStartIndex = string[componentStartIndex...].firstIndex(of: "?") { if let queryStartIndex = string[componentStartIndex...].firstIndex(of: "?") {
encodedUrlString = encodedUrlString + (string[componentStartIndex..<queryStartIndex].addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "") encodedUrlString = encodedUrlString + (string[componentStartIndex ..< queryStartIndex].addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "")
encodedUrlString = encodedUrlString + (string[queryStartIndex...].addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "") encodedUrlString = encodedUrlString + (string[queryStartIndex...].addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")
} else { } else {
encodedUrlString = encodedUrlString + (string[componentStartIndex...].addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "") encodedUrlString = encodedUrlString + (string[componentStartIndex...].addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "")

View file

@ -8,9 +8,9 @@ public struct AppAccount: Codable, Identifiable, Hashable {
public var key: String { public var key: String {
if let oauthToken { if let oauthToken {
return "\(server):\(oauthToken.createdAt)" "\(server):\(oauthToken.createdAt)"
} else { } else {
return "\(server):anonymous" "\(server):anonymous"
} }
} }

View file

@ -2,7 +2,7 @@ import Foundation
@MainActor @MainActor
public struct Language: Identifiable, Equatable, Hashable { public struct Language: Identifiable, Equatable, Hashable {
nonisolated public var id: String { isoCode } public nonisolated var id: String { isoCode }
public let isoCode: String public let isoCode: String
public let nativeName: String? public let nativeName: String?

View file

@ -32,7 +32,7 @@ public struct Poll: Codable, Equatable, Hashable {
// 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 {
return votersCount ?? votesCount votersCount ?? votesCount
} }
} }

View file

@ -24,14 +24,14 @@ public struct ServerFilter: Codable, Identifiable, Hashable, Sendable {
public let expiresAt: ServerDate? public let expiresAt: ServerDate?
public func hasExpiry() -> Bool { public func hasExpiry() -> Bool {
return expiresAt != nil expiresAt != nil
} }
public func isExpired() -> Bool { public func isExpired() -> Bool {
if let expiresAtDate = expiresAt?.asDate { if let expiresAtDate = expiresAt?.asDate {
return expiresAtDate < Date() expiresAtDate < Date()
} else { } else {
return false false
} }
} }
} }
@ -40,30 +40,30 @@ public extension ServerFilter.Context {
var iconName: String { var iconName: String {
switch self { switch self {
case .home: case .home:
return "rectangle.stack" "rectangle.stack"
case .notifications: case .notifications:
return "bell" "bell"
case .public: case .public:
return "globe.americas" "globe.americas"
case .thread: case .thread:
return "bubble.left.and.bubble.right" "bubble.left.and.bubble.right"
case .account: case .account:
return "person.crop.circle" "person.crop.circle"
} }
} }
var name: String { var name: String {
switch self { switch self {
case .home: case .home:
return NSLocalizedString("filter.contexts.home", comment: "") NSLocalizedString("filter.contexts.home", comment: "")
case .notifications: case .notifications:
return NSLocalizedString("filter.contexts.notifications", comment: "") NSLocalizedString("filter.contexts.notifications", comment: "")
case .public: case .public:
return NSLocalizedString("filter.contexts.public", comment: "") NSLocalizedString("filter.contexts.public", comment: "")
case .thread: case .thread:
return NSLocalizedString("filter.contexts.conversations", comment: "") NSLocalizedString("filter.contexts.conversations", comment: "")
case .account: case .account:
return NSLocalizedString("filter.contexts.profiles", comment: "") NSLocalizedString("filter.contexts.profiles", comment: "")
} }
} }
} }
@ -72,9 +72,9 @@ public extension ServerFilter.Action {
var label: String { var label: String {
switch self { switch self {
case .warn: case .warn:
return NSLocalizedString("filter.action.warning", comment: "") NSLocalizedString("filter.action.warning", comment: "")
case .hide: case .hide:
return NSLocalizedString("filter.action.hide", comment: "") NSLocalizedString("filter.action.hide", comment: "")
} }
} }
} }

View file

@ -16,11 +16,11 @@ public struct ServerPreferences: Decodable {
public var description: LocalizedStringKey { public var description: LocalizedStringKey {
switch self { switch self {
case .showAll: case .showAll:
return "enum.expand-media.show" "enum.expand-media.show"
case .hideAll: case .hideAll:
return "enum.expand-media.hide" "enum.expand-media.hide"
case .hideSensitive: case .hideSensitive:
return "enum.expand-media.hide-sensitive" "enum.expand-media.hide-sensitive"
} }
} }
} }

View file

@ -6,28 +6,28 @@ final class HTMLStringTests: XCTestCase {
XCTAssertNil(URL(string: "go to www.google.com", encodePath: true)) XCTAssertNil(URL(string: "go to www.google.com", encodePath: true))
XCTAssertNil(URL(string: "go to www.google.com", encodePath: false)) XCTAssertNil(URL(string: "go to www.google.com", encodePath: false))
XCTAssertNil(URL(string: "", encodePath: true)) XCTAssertNil(URL(string: "", encodePath: true))
let simpleUrl = URL(string: "https://www.google.com", encodePath: true) let simpleUrl = URL(string: "https://www.google.com", encodePath: true)
XCTAssertEqual("https://www.google.com", simpleUrl?.absoluteString) XCTAssertEqual("https://www.google.com", simpleUrl?.absoluteString)
let urlWithTrailingSlash = URL(string: "https://www.google.com/", encodePath: true) let urlWithTrailingSlash = URL(string: "https://www.google.com/", encodePath: true)
XCTAssertEqual("https://www.google.com/", urlWithTrailingSlash?.absoluteString) XCTAssertEqual("https://www.google.com/", urlWithTrailingSlash?.absoluteString)
let extendedCharPath = URL(string: "https://en.wikipedia.org/wiki/Elbbrücken_station", encodePath: true) let extendedCharPath = URL(string: "https://en.wikipedia.org/wiki/Elbbrücken_station", encodePath: true)
XCTAssertEqual("https://en.wikipedia.org/wiki/Elbbr%C3%BCcken_station", extendedCharPath?.absoluteString) XCTAssertEqual("https://en.wikipedia.org/wiki/Elbbr%C3%BCcken_station", extendedCharPath?.absoluteString)
XCTAssertNil(URL(string: "https://en.wikipedia.org/wiki/Elbbrücken_station", encodePath: false)) XCTAssertNil(URL(string: "https://en.wikipedia.org/wiki/Elbbrücken_station", encodePath: false))
let extendedCharQuery = URL(string: "http://test.com/blah/city?name=京都市", encodePath: true) let extendedCharQuery = URL(string: "http://test.com/blah/city?name=京都市", encodePath: true)
XCTAssertEqual("http://test.com/blah/city?name=%E4%BA%AC%E9%83%BD%E5%B8%82", extendedCharQuery?.absoluteString) XCTAssertEqual("http://test.com/blah/city?name=%E4%BA%AC%E9%83%BD%E5%B8%82", extendedCharQuery?.absoluteString)
// Double encoding will happen if you ask to encodePath on an already encoded string // Double encoding will happen if you ask to encodePath on an already encoded string
let alreadyEncodedPath = URL(string: "https://en.wikipedia.org/wiki/Elbbr%C3%BCcken_station", encodePath: true) let alreadyEncodedPath = URL(string: "https://en.wikipedia.org/wiki/Elbbr%C3%BCcken_station", encodePath: true)
XCTAssertEqual("https://en.wikipedia.org/wiki/Elbbr%25C3%25BCcken_station", alreadyEncodedPath?.absoluteString) XCTAssertEqual("https://en.wikipedia.org/wiki/Elbbr%25C3%25BCcken_station", alreadyEncodedPath?.absoluteString)
} }
func testHTMLStringInit() throws { func testHTMLStringInit() throws {
let decoder = JSONDecoder() let decoder = JSONDecoder()
let basicContent = "\"<p>This is a test</p>\"" let basicContent = "\"<p>This is a test</p>\""
var htmlString = try decoder.decode(HTMLString.self, from: Data(basicContent.utf8)) var htmlString = try decoder.decode(HTMLString.self, from: Data(basicContent.utf8))
XCTAssertEqual("This is a test", htmlString.asRawText) XCTAssertEqual("This is a test", htmlString.asRawText)
@ -35,7 +35,7 @@ final class HTMLStringTests: XCTestCase {
XCTAssertEqual("This is a test", htmlString.asMarkdown) XCTAssertEqual("This is a test", htmlString.asMarkdown)
XCTAssertEqual(0, htmlString.statusesURLs.count) XCTAssertEqual(0, htmlString.statusesURLs.count)
XCTAssertEqual(0, htmlString.links.count) XCTAssertEqual(0, htmlString.links.count)
let basicLink = "\"<p>This is a <a href=\\\"https://test.com\\\">test</a></p>\"" let basicLink = "\"<p>This is a <a href=\\\"https://test.com\\\">test</a></p>\""
htmlString = try decoder.decode(HTMLString.self, from: Data(basicLink.utf8)) htmlString = try decoder.decode(HTMLString.self, from: Data(basicLink.utf8))
XCTAssertEqual("This is a test", htmlString.asRawText) XCTAssertEqual("This is a test", htmlString.asRawText)
@ -45,7 +45,7 @@ final class HTMLStringTests: XCTestCase {
XCTAssertEqual(1, htmlString.links.count) XCTAssertEqual(1, htmlString.links.count)
XCTAssertEqual("https://test.com", htmlString.links[0].url.absoluteString) XCTAssertEqual("https://test.com", htmlString.links[0].url.absoluteString)
XCTAssertEqual("test", htmlString.links[0].displayString) XCTAssertEqual("test", htmlString.links[0].displayString)
let extendedCharLink = "\"<p>This is a <a href=\\\"https://test.com/goßëña\\\">test</a></p>\"" let extendedCharLink = "\"<p>This is a <a href=\\\"https://test.com/goßëña\\\">test</a></p>\""
htmlString = try decoder.decode(HTMLString.self, from: Data(extendedCharLink.utf8)) htmlString = try decoder.decode(HTMLString.self, from: Data(extendedCharLink.utf8))
XCTAssertEqual("This is a test", htmlString.asRawText) XCTAssertEqual("This is a test", htmlString.asRawText)
@ -55,7 +55,7 @@ final class HTMLStringTests: XCTestCase {
XCTAssertEqual(1, htmlString.links.count) XCTAssertEqual(1, htmlString.links.count)
XCTAssertEqual("https://test.com/go%C3%9F%C3%AB%C3%B1a", htmlString.links[0].url.absoluteString) XCTAssertEqual("https://test.com/go%C3%9F%C3%AB%C3%B1a", htmlString.links[0].url.absoluteString)
XCTAssertEqual("test", htmlString.links[0].displayString) XCTAssertEqual("test", htmlString.links[0].displayString)
let alreadyEncodedLink = "\"<p>This is a <a href=\\\"https://test.com/go%C3%9F%C3%AB%C3%B1a\\\">test</a></p>\"" let alreadyEncodedLink = "\"<p>This is a <a href=\\\"https://test.com/go%C3%9F%C3%AB%C3%B1a\\\">test</a></p>\""
htmlString = try decoder.decode(HTMLString.self, from: Data(alreadyEncodedLink.utf8)) htmlString = try decoder.decode(HTMLString.self, from: Data(alreadyEncodedLink.utf8))
XCTAssertEqual("This is a test", htmlString.asRawText) XCTAssertEqual("This is a test", htmlString.asRawText)
@ -66,16 +66,16 @@ final class HTMLStringTests: XCTestCase {
XCTAssertEqual("https://test.com/go%C3%9F%C3%AB%C3%B1a", htmlString.links[0].url.absoluteString) XCTAssertEqual("https://test.com/go%C3%9F%C3%AB%C3%B1a", htmlString.links[0].url.absoluteString)
XCTAssertEqual("test", htmlString.links[0].displayString) XCTAssertEqual("test", htmlString.links[0].displayString)
} }
func testHTMLStringInit_markdownEscaping() throws { func testHTMLStringInit_markdownEscaping() throws {
let decoder = JSONDecoder() let decoder = JSONDecoder()
let stdMarkdownContent = "\"<p>This [*is*] `a`\\n**test**</p>\"" let stdMarkdownContent = "\"<p>This [*is*] `a`\\n**test**</p>\""
var htmlString = try decoder.decode(HTMLString.self, from: Data(stdMarkdownContent.utf8)) var htmlString = try decoder.decode(HTMLString.self, from: Data(stdMarkdownContent.utf8))
XCTAssertEqual("This [*is*] `a`\n**test**", htmlString.asRawText) XCTAssertEqual("This [*is*] `a`\n**test**", htmlString.asRawText)
XCTAssertEqual("<p>This [*is*] `a`\n**test**</p>", htmlString.htmlValue) XCTAssertEqual("<p>This [*is*] `a`\n**test**</p>", htmlString.htmlValue)
XCTAssertEqual("This \\[\\*is\\*] \\`a\\` \\*\\*test\\*\\*", htmlString.asMarkdown) XCTAssertEqual("This \\[\\*is\\*] \\`a\\` \\*\\*test\\*\\*", htmlString.asMarkdown)
let underscoreContent = "\"<p>This _is_ an :emoji_maybe:</p>\"" let underscoreContent = "\"<p>This _is_ an :emoji_maybe:</p>\""
htmlString = try decoder.decode(HTMLString.self, from: Data(underscoreContent.utf8)) htmlString = try decoder.decode(HTMLString.self, from: Data(underscoreContent.utf8))
XCTAssertEqual("This _is_ an :emoji_maybe:", htmlString.asRawText) XCTAssertEqual("This _is_ an :emoji_maybe:", htmlString.asRawText)

View file

@ -25,7 +25,7 @@ let package = Package(
.product(name: "Models", package: "Models"), .product(name: "Models", package: "Models"),
], ],
swiftSettings: [ swiftSettings: [
.enableExperimentalFeature("StrictConcurrency") .enableExperimentalFeature("StrictConcurrency"),
] ]
), ),
.testTarget( .testTarget(

View file

@ -33,49 +33,49 @@ public enum Accounts: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case let .accounts(id): case let .accounts(id):
return "accounts/\(id)" "accounts/\(id)"
case .favorites: case .favorites:
return "favourites" "favourites"
case .bookmarks: case .bookmarks:
return "bookmarks" "bookmarks"
case .followedTags: case .followedTags:
return "followed_tags" "followed_tags"
case let .featuredTags(id): case let .featuredTags(id):
return "accounts/\(id)/featured_tags" "accounts/\(id)/featured_tags"
case .verifyCredentials: case .verifyCredentials:
return "accounts/verify_credentials" "accounts/verify_credentials"
case .updateCredentials: case .updateCredentials:
return "accounts/update_credentials" "accounts/update_credentials"
case let .statuses(id, _, _, _, _, _): case let .statuses(id, _, _, _, _, _):
return "accounts/\(id)/statuses" "accounts/\(id)/statuses"
case .relationships: case .relationships:
return "accounts/relationships" "accounts/relationships"
case let .follow(id, _, _): case let .follow(id, _, _):
return "accounts/\(id)/follow" "accounts/\(id)/follow"
case let .unfollow(id): case let .unfollow(id):
return "accounts/\(id)/unfollow" "accounts/\(id)/unfollow"
case .familiarFollowers: case .familiarFollowers:
return "accounts/familiar_followers" "accounts/familiar_followers"
case .suggestions: case .suggestions:
return "suggestions" "suggestions"
case let .following(id, _): case let .following(id, _):
return "accounts/\(id)/following" "accounts/\(id)/following"
case let .followers(id, _): case let .followers(id, _):
return "accounts/\(id)/followers" "accounts/\(id)/followers"
case let .lists(id): case let .lists(id):
return "accounts/\(id)/lists" "accounts/\(id)/lists"
case .preferences: case .preferences:
return "preferences" "preferences"
case let .block(id): case let .block(id):
return "accounts/\(id)/block" "accounts/\(id)/block"
case let .unblock(id): case let .unblock(id):
return "accounts/\(id)/unblock" "accounts/\(id)/unblock"
case let .mute(id, _): case let .mute(id, _):
return "accounts/\(id)/mute" "accounts/\(id)/mute"
case let .unmute(id): case let .unmute(id):
return "accounts/\(id)/unmute" "accounts/\(id)/unmute"
case let .relationshipNote(id, _): case let .relationshipNote(id, _):
return "accounts/\(id)/note" "accounts/\(id)/note"
} }
} }
@ -128,13 +128,13 @@ public enum Accounts: Endpoint {
public var jsonValue: Encodable? { public var jsonValue: Encodable? {
switch self { switch self {
case let .mute(_, json): case let .mute(_, json):
return json json
case let .relationshipNote(_, json): case let .relationshipNote(_, json):
return json json
case let .updateCredentials(json): case let .updateCredentials(json):
return json json
default: default:
return nil nil
} }
} }
} }

View file

@ -7,7 +7,7 @@ public enum Apps: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .registerApp: case .registerApp:
return "apps" "apps"
} }
} }

View file

@ -8,20 +8,20 @@ public enum Conversations: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .conversations: case .conversations:
return "conversations" "conversations"
case let .delete(id): case let .delete(id):
return "conversations/\(id)" "conversations/\(id)"
case let .read(id): case let .read(id):
return "conversations/\(id)/read" "conversations/\(id)/read"
} }
} }
public func queryItems() -> [URLQueryItem]? { public func queryItems() -> [URLQueryItem]? {
switch self { switch self {
case let .conversations(maxId): case let .conversations(maxId):
return makePaginationParam(sinceId: nil, maxId: maxId, mindId: nil) makePaginationParam(sinceId: nil, maxId: maxId, mindId: nil)
default: default:
return nil nil
} }
} }
} }

View file

@ -6,7 +6,7 @@ public enum CustomEmojis: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .customEmojis: case .customEmojis:
return "custom_emojis" "custom_emojis"
} }
} }

View file

@ -8,11 +8,11 @@ public enum FollowRequests: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .list: case .list:
return "follow_requests" "follow_requests"
case let .accept(id): case let .accept(id):
return "follow_requests/\(id)/authorize" "follow_requests/\(id)/authorize"
case let .reject(id): case let .reject(id):
return "follow_requests/\(id)/reject" "follow_requests/\(id)/reject"
} }
} }

View file

@ -7,9 +7,9 @@ public enum Instances: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .instance: case .instance:
return "instance" "instance"
case .peers: case .peers:
return "instance/peers" "instance/peers"
} }
} }

View file

@ -10,13 +10,13 @@ public enum Lists: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .lists, .createList: case .lists, .createList:
return "lists" "lists"
case let .list(id): case let .list(id):
return "lists/\(id)" "lists/\(id)"
case let .accounts(listId): case let .accounts(listId):
return "lists/\(listId)/accounts" "lists/\(listId)/accounts"
case let .updateAccounts(listId, _): case let .updateAccounts(listId, _):
return "lists/\(listId)/accounts" "lists/\(listId)/accounts"
} }
} }

View file

@ -7,22 +7,22 @@ public enum Media: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .medias: case .medias:
return "media" "media"
case let .media(id, _): case let .media(id, _):
return "media/\(id)" "media/\(id)"
} }
} }
public func queryItems() -> [URLQueryItem]? { public func queryItems() -> [URLQueryItem]? {
return nil nil
} }
public var jsonValue: Encodable? { public var jsonValue: Encodable? {
switch self { switch self {
case let .media(_, json): case let .media(_, json):
return json json
default: default:
return nil nil
} }
} }
} }

View file

@ -11,11 +11,11 @@ public enum Notifications: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .notifications: case .notifications:
return "notifications" "notifications"
case let .notification(id): case let .notification(id):
return "notifications/\(id)" "notifications/\(id)"
case .clear: case .clear:
return "notifications/clear" "notifications/clear"
} }
} }

View file

@ -8,18 +8,18 @@ public enum Oauth: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .authorize: case .authorize:
return "oauth/authorize" "oauth/authorize"
case .token: case .token:
return "oauth/token" "oauth/token"
} }
} }
public var jsonValue: Encodable? { public var jsonValue: Encodable? {
switch self { switch self {
case let .token(code, clientId, clientSecret): case let .token(code, clientId, clientSecret):
return TokenData(clientId: clientId, clientSecret: clientSecret, code: code) TokenData(clientId: clientId, clientSecret: clientSecret, code: code)
default: default:
return nil nil
} }
} }

View file

@ -7,9 +7,9 @@ public enum Polls: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case let .poll(id): case let .poll(id):
return "polls/\(id)" "polls/\(id)"
case let .vote(id, _): case let .vote(id, _):
return "polls/\(id)/votes" "polls/\(id)/votes"
} }
} }

View file

@ -15,7 +15,7 @@ public enum Push: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .subscription, .createSub: case .subscription, .createSub:
return "push/subscription" "push/subscription"
} }
} }

View file

@ -6,7 +6,7 @@ public enum Search: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .search: case .search:
return "search" "search"
} }
} }

View file

@ -12,17 +12,17 @@ public enum ServerFilters: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .filters: case .filters:
return "filters" "filters"
case .createFilter: case .createFilter:
return "filters" "filters"
case let .filter(id): case let .filter(id):
return "filters/\(id)" "filters/\(id)"
case let .editFilter(id, _): case let .editFilter(id, _):
return "filters/\(id)" "filters/\(id)"
case let .addKeyword(id, _, _): case let .addKeyword(id, _, _):
return "filters/\(id)/keywords" "filters/\(id)/keywords"
case let .removeKeyword(id): case let .removeKeyword(id):
return "filters/keywords/\(id)" "filters/keywords/\(id)"
} }
} }
@ -39,11 +39,11 @@ public enum ServerFilters: Endpoint {
public var jsonValue: Encodable? { public var jsonValue: Encodable? {
switch self { switch self {
case let .createFilter(json): case let .createFilter(json):
return json json
case let .editFilter(_, json): case let .editFilter(_, json):
return json json
default: default:
return nil nil
} }
} }
} }

View file

@ -23,39 +23,39 @@ public enum Statuses: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .postStatus: case .postStatus:
return "statuses" "statuses"
case let .status(id): case let .status(id):
return "statuses/\(id)" "statuses/\(id)"
case let .editStatus(id, _): case let .editStatus(id, _):
return "statuses/\(id)" "statuses/\(id)"
case let .context(id): case let .context(id):
return "statuses/\(id)/context" "statuses/\(id)/context"
case let .favorite(id): case let .favorite(id):
return "statuses/\(id)/favourite" "statuses/\(id)/favourite"
case let .unfavorite(id): case let .unfavorite(id):
return "statuses/\(id)/unfavourite" "statuses/\(id)/unfavourite"
case let .reblog(id): case let .reblog(id):
return "statuses/\(id)/reblog" "statuses/\(id)/reblog"
case let .unreblog(id): case let .unreblog(id):
return "statuses/\(id)/unreblog" "statuses/\(id)/unreblog"
case let .rebloggedBy(id, _): case let .rebloggedBy(id, _):
return "statuses/\(id)/reblogged_by" "statuses/\(id)/reblogged_by"
case let .favoritedBy(id, _): case let .favoritedBy(id, _):
return "statuses/\(id)/favourited_by" "statuses/\(id)/favourited_by"
case let .pin(id): case let .pin(id):
return "statuses/\(id)/pin" "statuses/\(id)/pin"
case let .unpin(id): case let .unpin(id):
return "statuses/\(id)/unpin" "statuses/\(id)/unpin"
case let .bookmark(id): case let .bookmark(id):
return "statuses/\(id)/bookmark" "statuses/\(id)/bookmark"
case let .unbookmark(id): case let .unbookmark(id):
return "statuses/\(id)/unbookmark" "statuses/\(id)/unbookmark"
case let .history(id): case let .history(id):
return "statuses/\(id)/history" "statuses/\(id)/history"
case let .translate(id, _): case let .translate(id, _):
return "statuses/\(id)/translate" "statuses/\(id)/translate"
case .report: case .report:
return "reports" "reports"
} }
} }
@ -82,11 +82,11 @@ public enum Statuses: Endpoint {
public var jsonValue: Encodable? { public var jsonValue: Encodable? {
switch self { switch self {
case let .postStatus(json): case let .postStatus(json):
return json json
case let .editStatus(_, json): case let .editStatus(_, json):
return json json
default: default:
return nil nil
} }
} }
} }

View file

@ -6,14 +6,14 @@ public enum Streaming: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .streaming: case .streaming:
return "streaming" "streaming"
} }
} }
public func queryItems() -> [URLQueryItem]? { public func queryItems() -> [URLQueryItem]? {
switch self { switch self {
default: default:
return nil nil
} }
} }
} }

View file

@ -8,18 +8,18 @@ public enum Tags: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case let .tag(id): case let .tag(id):
return "tags/\(id)/" "tags/\(id)/"
case let .follow(id): case let .follow(id):
return "tags/\(id)/follow" "tags/\(id)/follow"
case let .unfollow(id): case let .unfollow(id):
return "tags/\(id)/unfollow" "tags/\(id)/unfollow"
} }
} }
public func queryItems() -> [URLQueryItem]? { public func queryItems() -> [URLQueryItem]? {
switch self { switch self {
default: default:
return nil nil
} }
} }
} }

View file

@ -9,13 +9,13 @@ public enum Timelines: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .pub: case .pub:
return "timelines/public" "timelines/public"
case .home: case .home:
return "timelines/home" "timelines/home"
case let .list(listId, _, _, _): case let .list(listId, _, _, _):
return "timelines/list/\(listId)" "timelines/list/\(listId)"
case let .hashtag(tag, _, _): case let .hashtag(tag, _, _):
return "timelines/tag/\(tag)" "timelines/tag/\(tag)"
} }
} }

View file

@ -8,11 +8,11 @@ public enum Trends: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .tags: case .tags:
return "trends/tags" "trends/tags"
case .statuses: case .statuses:
return "trends/statuses" "trends/statuses"
case .links: case .links:
return "trends/links" "trends/links"
} }
} }

View file

@ -62,15 +62,15 @@ public struct OpenAIClient {
var request: OpenAIRequest { var request: OpenAIRequest {
switch self { switch self {
case let .correct(input): case let .correct(input):
return ChatRequest(content: "Fix the spelling and grammar mistakes in the following text: \(input)", temperature: 0.2) ChatRequest(content: "Fix the spelling and grammar mistakes in the following text: \(input)", temperature: 0.2)
case let .addTags(input): case let .addTags(input):
return ChatRequest(content: "Replace relevant words with camel-cased hashtags in the following text. Don't try to search for context or add hashtags if there is not enough context: \(input)", temperature: 0.1) ChatRequest(content: "Replace relevant words with camel-cased hashtags in the following text. Don't try to search for context or add hashtags if there is not enough context: \(input)", temperature: 0.1)
case let .insertTags(input): case let .insertTags(input):
return ChatRequest(content: "Return the input with added camel-cased hashtags at the end of the input. Don't try to search for context or add hashtags if there is not enough context: \(input)", temperature: 0.2) ChatRequest(content: "Return the input with added camel-cased hashtags at the end of the input. Don't try to search for context or add hashtags if there is not enough context: \(input)", temperature: 0.2)
case let .shorten(input): case let .shorten(input):
return ChatRequest(content: "Make a shorter version of this text: \(input)", temperature: 0.5) ChatRequest(content: "Make a shorter version of this text: \(input)", temperature: 0.5)
case let .emphasize(input): case let .emphasize(input):
return ChatRequest(content: "Make this text catchy, more fun: \(input)", temperature: 1) ChatRequest(content: "Make this text catchy, more fun: \(input)", temperature: 1)
} }
} }
} }

View file

@ -2,7 +2,7 @@ import Foundation
public extension String { public extension String {
func escape() -> String { func escape() -> String {
return replacingOccurrences(of: "&amp;", with: "&") replacingOccurrences(of: "&amp;", with: "&")
.replacingOccurrences(of: "&lt;", with: "<") .replacingOccurrences(of: "&lt;", with: "<")
.replacingOccurrences(of: "&gt;", with: ">") .replacingOccurrences(of: "&gt;", with: ">")
.replacingOccurrences(of: "&quot;", with: "\"") .replacingOccurrences(of: "&quot;", with: "\"")

View file

@ -2,7 +2,7 @@ import Foundation
extension Data { extension Data {
func base64UrlEncodedString() -> String { func base64UrlEncodedString() -> String {
return base64EncodedString() base64EncodedString()
.replacingOccurrences(of: "+", with: "-") .replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_") .replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "") .replacingOccurrences(of: "=", with: "")

View file

@ -33,7 +33,7 @@ let package = Package(
.product(name: "DesignSystem", package: "DesignSystem"), .product(name: "DesignSystem", package: "DesignSystem"),
], ],
swiftSettings: [ swiftSettings: [
.enableExperimentalFeature("StrictConcurrency") .enableExperimentalFeature("StrictConcurrency"),
] ]
), ),
] ]

View file

@ -11,7 +11,7 @@ extension ConsolidatedNotification {
var notificationIds: [String] { notifications.map(\.id) } var notificationIds: [String] { notifications.map(\.id) }
} }
extension Array where Element == ConsolidatedNotification { extension [ConsolidatedNotification] {
var notificationCount: Int { var notificationCount: Int {
reduce(0) { $0 + ($1.accounts.isEmpty ? 1 : $1.accounts.count) } reduce(0) { $0 + ($1.accounts.isEmpty ? 1 : $1.accounts.count) }
} }

View file

@ -8,7 +8,7 @@
import Foundation import Foundation
import Models import Models
extension Array where Element == Models.Notification { extension [Models.Notification] {
func consolidated(selectedType: Models.Notification.NotificationType?) async -> [ConsolidatedNotification] { func consolidated(selectedType: Models.Notification.NotificationType?) async -> [ConsolidatedNotification] {
await withCheckedContinuation { result in await withCheckedContinuation { result in
DispatchQueue.global().async { DispatchQueue.global().async {

View file

@ -6,42 +6,42 @@ extension Models.Notification.NotificationType {
public func label(count: Int) -> LocalizedStringKey { public func label(count: Int) -> LocalizedStringKey {
switch self { switch self {
case .status: case .status:
return "notifications.label.status" "notifications.label.status"
case .mention: case .mention:
return "" ""
case .reblog: case .reblog:
return "notifications.label.reblog \(count)" "notifications.label.reblog \(count)"
case .follow: case .follow:
return "notifications.label.follow \(count)" "notifications.label.follow \(count)"
case .follow_request: case .follow_request:
return "notifications.label.follow-request" "notifications.label.follow-request"
case .favourite: case .favourite:
return "notifications.label.favorite \(count)" "notifications.label.favorite \(count)"
case .poll: case .poll:
return "notifications.label.poll" "notifications.label.poll"
case .update: case .update:
return "notifications.label.update" "notifications.label.update"
} }
} }
public func notificationKey() -> String { public func notificationKey() -> String {
switch self { switch self {
case .status: case .status:
return "notifications.label.status.push" "notifications.label.status.push"
case .mention: case .mention:
return "" ""
case .reblog: case .reblog:
return "notifications.label.reblog.push" "notifications.label.reblog.push"
case .follow: case .follow:
return "notifications.label.follow.push" "notifications.label.follow.push"
case .follow_request: case .follow_request:
return "notifications.label.follow-request.push" "notifications.label.follow-request.push"
case .favourite: case .favourite:
return "notifications.label.favorite.push" "notifications.label.favorite.push"
case .poll: case .poll:
return "notifications.label.poll.push" "notifications.label.poll.push"
case .update: case .update:
return "notifications.label.update.push" "notifications.label.update.push"
} }
} }
@ -86,21 +86,21 @@ extension Models.Notification.NotificationType {
func menuTitle() -> LocalizedStringKey { func menuTitle() -> LocalizedStringKey {
switch self { switch self {
case .status: case .status:
return "notifications.menu-title.status" "notifications.menu-title.status"
case .mention: case .mention:
return "notifications.menu-title.mention" "notifications.menu-title.mention"
case .reblog: case .reblog:
return "notifications.menu-title.reblog" "notifications.menu-title.reblog"
case .follow: case .follow:
return "notifications.menu-title.follow" "notifications.menu-title.follow"
case .follow_request: case .follow_request:
return "notifications.menu-title.follow-request" "notifications.menu-title.follow-request"
case .favourite: case .favourite:
return "notifications.menu-title.favorite" "notifications.menu-title.favorite"
case .poll: case .poll:
return "notifications.menu-title.poll" "notifications.menu-title.poll"
case .update: case .update:
return "notifications.menu-title.update" "notifications.menu-title.update"
} }
} }
} }

View file

@ -51,7 +51,7 @@ class NotificationsViewModel: ObservableObject {
if let selectedType { if let selectedType {
var excludedTypes = Models.Notification.NotificationType.allCases var excludedTypes = Models.Notification.NotificationType.allCases
excludedTypes.removeAll(where: { $0 == selectedType }) excludedTypes.removeAll(where: { $0 == selectedType })
return excludedTypes.map { $0.rawValue } return excludedTypes.map(\.rawValue)
} }
return nil return nil
} }

View file

@ -33,7 +33,7 @@ let package = Package(
.product(name: "DesignSystem", package: "DesignSystem"), .product(name: "DesignSystem", package: "DesignSystem"),
], ],
swiftSettings: [ swiftSettings: [
.enableExperimentalFeature("StrictConcurrency") .enableExperimentalFeature("StrictConcurrency"),
] ]
), ),
] ]

View file

@ -161,7 +161,7 @@ public struct StatusDetailView: View {
.id(status.id) .id(status.id)
// VoiceOver / Switch Control focus workaround // VoiceOver / Switch Control focus workaround
.onAppear { .onAppear {
self.initialFocusBugWorkaround = true initialFocusBugWorkaround = true
} }
} }

View file

@ -24,15 +24,15 @@ enum StatusEditorAIPrompt: CaseIterable {
func toRequestPrompt(text: String) -> OpenAIClient.Prompt { func toRequestPrompt(text: String) -> OpenAIClient.Prompt {
switch self { switch self {
case .correct: case .correct:
return .correct(input: text) .correct(input: text)
case .addTags: case .addTags:
return .addTags(input: text) .addTags(input: text)
case .insertTags: case .insertTags:
return .insertTags(input: text) .insertTags(input: text)
case .fit: case .fit:
return .shorten(input: text) .shorten(input: text)
case .emphasize: case .emphasize:
return .emphasize(input: text) .emphasize(input: text)
} }
} }
} }

View file

@ -162,7 +162,7 @@ struct StatusEditorAccessoryView: View {
@ViewBuilder @ViewBuilder
private func languageTextView(isoCode: String, nativeName: String?, name: String?) -> some View { private func languageTextView(isoCode: String, nativeName: String?, name: String?) -> some View {
if let nativeName = nativeName, let name = name { if let nativeName, let name {
Text("\(nativeName) (\(name))") Text("\(nativeName) (\(name))")
} else { } else {
Text(isoCode.uppercased()) Text(isoCode.uppercased())

View file

@ -8,7 +8,7 @@ actor StatusEditorCompressor {
} }
func compressImageFrom(url: URL) async -> Data? { func compressImageFrom(url: URL) async -> Data? {
return await withCheckedContinuation { continuation in await withCheckedContinuation { continuation in
let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else { guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else {
continuation.resume(returning: nil) continuation.resume(returning: nil)

View file

@ -66,7 +66,7 @@ struct StatusEditorMediaEditView: View {
Button { Button {
if !imageDescription.isEmpty { if !imageDescription.isEmpty {
isUpdating = true isUpdating = true
if currentInstance.isEditAltTextSupported && viewModel.mode.isEditing { if currentInstance.isEditAltTextSupported, viewModel.mode.isEditing {
Task { Task {
await viewModel.editDescription(container: container, description: imageDescription) await viewModel.editDescription(container: container, description: imageDescription)
dismiss() dismiss()

Some files were not shown because too many files have changed in this diff Show more