Run swiftformat

This commit is contained in:
Thomas Ricouard 2023-07-19 07:46:25 +02:00
parent 5951bcec38
commit a2fe0511e0
38 changed files with 471 additions and 486 deletions

View File

@ -83,8 +83,8 @@ extension View {
AddRemoteTimelineView()
.withEnvironments()
case .addTagGroup:
AddTagGroupView()
.withEnvironments()
AddTagGroupView()
.withEnvironments()
case let .statusEditHistory(status):
StatusEditHistoryView(statusId: status)
.withEnvironments()

View File

@ -97,9 +97,9 @@ struct SideBarView<Content: View>: View {
private var tabsView: some View {
ForEach(tabs) { tab in
Button {
//ensure keyboard is always dismissed when selecting a tab
// ensure keyboard is always dismissed when selecting a tab
hideKeyboard()
if tab == selectedTab {
popToRootTab = .other
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
@ -176,8 +176,8 @@ private struct SideBarIcon: View {
}
extension View {
func hideKeyboard() {
let resign = #selector(UIResponder.resignFirstResponder)
UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
}
func hideKeyboard() {
let resign = #selector(UIResponder.resignFirstResponder)
UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
}
}

View File

@ -27,7 +27,7 @@ struct ContentSettingsView: View {
Text("settings.content.media.show.alt")
}
}.listRowBackground(theme.primaryBackgroundColor)
Section("settings.content.sharing") {
Picker("settings.content.sharing.share-button-behavior", selection: $userPreferences.shareButtonBehavior) {
ForEach(PreferredShareButtonBehavior.allCases, id: \.rawValue) { option in

View File

@ -13,7 +13,7 @@ class DisplaySettingsLocalValues: ObservableObject {
@Published var labelColor = Theme.shared.labelColor
@Published var lineSpacing = Theme.shared.lineSpacing
@Published var fontSizeScale = Theme.shared.fontSizeScale
private let debouncesDelay: DispatchQueue.SchedulerTimeType.Stride = .seconds(0.5)
private var subscriptions = Set<AnyCancellable>()

View File

@ -139,9 +139,9 @@ struct SettingsTabs: View {
NavigationLink(destination: remoteLocalTimelinesView) {
Label("settings.general.remote-timelines", systemImage: "dot.radiowaves.right")
}
NavigationLink(destination: tagGroupsView) {
Label("timeline.filter.tag-groups", systemImage: "number")
}
NavigationLink(destination: tagGroupsView) {
Label("timeline.filter.tag-groups", systemImage: "number")
}
NavigationLink(destination: ContentSettingsView()) {
Label("settings.general.content", systemImage: "rectangle.stack")
}
@ -264,36 +264,36 @@ struct SettingsTabs: View {
}
}
}
private var tagGroupsView: some View {
Form {
ForEach(preferences.tagGroups, id: \.self) { group in
Text(group.title)
}
.onDelete { indexes in
if let index = indexes.first {
_ = preferences.tagGroups.remove(at: index)
}
}
.onMove { source, destination in
preferences.tagGroups.move(fromOffsets: source, toOffset: destination)
}
.listRowBackground(theme.primaryBackgroundColor)
Button {
routerPath.presentedSheet = .addTagGroup
} label: {
Label("timeline.filter.add-tag-groups", systemImage: "plus")
}
.listRowBackground(theme.primaryBackgroundColor)
private var tagGroupsView: some View {
Form {
ForEach(preferences.tagGroups, id: \.self) { group in
Text(group.title)
}
.navigationTitle("timeline.filter.tag-groups")
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.toolbar {
EditButton()
.onDelete { indexes in
if let index = indexes.first {
_ = preferences.tagGroups.remove(at: index)
}
}
.onMove { source, destination in
preferences.tagGroups.move(fromOffsets: source, toOffset: destination)
}
.listRowBackground(theme.primaryBackgroundColor)
Button {
routerPath.presentedSheet = .addTagGroup
} label: {
Label("timeline.filter.add-tag-groups", systemImage: "plus")
}
.listRowBackground(theme.primaryBackgroundColor)
}
.navigationTitle("timeline.filter.tag-groups")
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.toolbar {
EditButton()
}
}
private var remoteLocalTimelinesView: some View {
Form {
@ -317,7 +317,7 @@ struct SettingsTabs: View {
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.toolbar {
EditButton()
EditButton()
}
}

View File

@ -38,7 +38,7 @@ struct TranslationSettingsView: View {
.listRowBackground(theme.primaryBackgroundColor)
}
}
Section {
Toggle(isOn: preferences.$autoDetectPostLanguage) {
Text("settings.translation.auto-detect-post-language")
@ -46,7 +46,6 @@ struct TranslationSettingsView: View {
} footer: {
Text("settings.translation.auto-detect-post-language-footer")
}
}
.navigationTitle("settings.translation.navigation-title")
.scrollContentBackground(.hidden)

View File

@ -8,190 +8,188 @@ import Shimmer
import SwiftUI
struct AddTagGroupView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var theme: Theme
@State private var title: String = ""
@State private var sfSymbolName: String = ""
@State private var tags: [String] = []
@State private var newTag: String = ""
private var canSave: Bool {
!title.isEmpty &&
// At least have 2 tags, one main and one additional.
tags.count >= 2
}
@FocusState private var focusedField: Focus?
enum Focus {
case title
case symbol
case new
}
var body: some View {
NavigationStack {
Form {
metadataSection
keywordsSection
}
.formStyle(.grouped)
.navigationTitle("timeline.filter.add-tag-groups")
.navigationBarTitleDisplayMode(.inline)
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.scrollDismissesKeyboard(.immediately)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("action.cancel", action: { dismiss() })
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("action.save", action: { save() })
.disabled(!canSave)
}
}
.onAppear {
focusedField = .title
}
.overlay(alignment: .bottom) {
symbolsSuggestionView
}
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var theme: Theme
@State private var title: String = ""
@State private var sfSymbolName: String = ""
@State private var tags: [String] = []
@State private var newTag: String = ""
private var canSave: Bool {
!title.isEmpty &&
// At least have 2 tags, one main and one additional.
tags.count >= 2
}
@FocusState private var focusedField: Focus?
enum Focus {
case title
case symbol
case new
}
var body: some View {
NavigationStack {
Form {
metadataSection
keywordsSection
}
.formStyle(.grouped)
.navigationTitle("timeline.filter.add-tag-groups")
.navigationBarTitleDisplayMode(.inline)
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.scrollDismissesKeyboard(.immediately)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("action.cancel", action: { dismiss() })
}
}
@ViewBuilder
private var metadataSection: some View {
Section {
TextField("add-tag-groups.edit.title.field", text: $title, axis: .horizontal)
.focused($focusedField, equals: Focus.title)
.onSubmit {
focusedField = Focus.symbol
}
HStack {
TextField("add-tag-groups.edit.icon.field", text: $sfSymbolName, axis: .horizontal)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.focused($focusedField, equals: Focus.symbol)
.onSubmit {
focusedField = Focus.new
}
.onChange(of: sfSymbolName) { name in
popupTagsPresented = true
}
Image(systemName: sfSymbolName)
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("action.save", action: { save() })
.disabled(!canSave)
}
}
.onAppear {
focusedField = .title
}
.overlay(alignment: .bottom) {
symbolsSuggestionView
}
}
@State private var popupTagsPresented = false
private var keywordsSection: some View {
Section("add-tag-groups.edit.tags") {
ForEach(tags, id: \.self) { tag in
HStack {
Text(tag)
Spacer()
Button {
deleteTag(tag)
} label: {
Image(systemName: "trash")
.tint(.red)
}
}
}
.onDelete { indexes in
if let index = indexes.first {
let tag = tags[index]
deleteTag(tag)
}
}
HStack {
TextField("add-tag-groups.edit.tags.add", text: $newTag, axis: .horizontal)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.onSubmit {
addNewTag()
}
.focused($focusedField, equals: Focus.new)
Spacer()
if !newTag.isEmpty {
Button {
addNewTag()
} label: {
Image(systemName: "checkmark.circle.fill")
.tint(.green)
}
}
}
}
@ViewBuilder
private var metadataSection: some View {
Section {
TextField("add-tag-groups.edit.title.field", text: $title, axis: .horizontal)
.focused($focusedField, equals: Focus.title)
.onSubmit {
focusedField = Focus.symbol
}
.listRowBackground(theme.primaryBackgroundColor)
HStack {
TextField("add-tag-groups.edit.icon.field", text: $sfSymbolName, axis: .horizontal)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.focused($focusedField, equals: Focus.symbol)
.onSubmit {
focusedField = Focus.new
}
.onChange(of: sfSymbolName) { _ in
popupTagsPresented = true
}
Image(systemName: sfSymbolName)
}
}
private func addNewTag() {
addTag(newTag.trimmingCharacters(in: .whitespaces))
newTag = ""
focusedField = Focus.new
}
private func addTag(_ tag: String) {
guard !tag.isEmpty else { return }
tags.append(tag)
}
private func deleteTag(_ tag: String) {
tags.removeAll(where: { $0 == tag })
}
private func save() {
var toSave = tags
let main = toSave.removeFirst()
preferences.tagGroups.append(.init(
title: title.trimmingCharacters(in: .whitespaces),
sfSymbolName: sfSymbolName,
main: main,
additional: toSave
))
dismiss()
}
@ViewBuilder
private var symbolsSuggestionView: some View {
if focusedField == .symbol && !sfSymbolName.isEmpty {
let filteredMatches = allSymbols
.filter { $0.contains(sfSymbolName) }
if !filteredMatches.isEmpty {
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(filteredMatches, id: \.self) { symbolName in
Button {
sfSymbolName = symbolName
} label: {
Image(systemName: symbolName)
}
}
}
.padding(.horizontal, .layoutPadding)
}
.frame(height: 40)
.background(.ultraThinMaterial)
}
} else {
EmptyView()
}
@State private var popupTagsPresented = false
private var keywordsSection: some View {
Section("add-tag-groups.edit.tags") {
ForEach(tags, id: \.self) { tag in
HStack {
Text(tag)
Spacer()
Button {
deleteTag(tag)
} label: {
Image(systemName: "trash")
.tint(.red)
}
}
}
.onDelete { indexes in
if let index = indexes.first {
let tag = tags[index]
deleteTag(tag)
}
}
HStack {
TextField("add-tag-groups.edit.tags.add", text: $newTag, axis: .horizontal)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.onSubmit {
addNewTag()
}
.focused($focusedField, equals: Focus.new)
Spacer()
if !newTag.isEmpty {
Button {
addNewTag()
} label: {
Image(systemName: "checkmark.circle.fill")
.tint(.green)
}
}
}
}
.listRowBackground(theme.primaryBackgroundColor)
}
private func addNewTag() {
addTag(newTag.trimmingCharacters(in: .whitespaces))
newTag = ""
focusedField = Focus.new
}
private func addTag(_ tag: String) {
guard !tag.isEmpty else { return }
tags.append(tag)
}
private func deleteTag(_ tag: String) {
tags.removeAll(where: { $0 == tag })
}
private func save() {
var toSave = tags
let main = toSave.removeFirst()
preferences.tagGroups.append(.init(
title: title.trimmingCharacters(in: .whitespaces),
sfSymbolName: sfSymbolName,
main: main,
additional: toSave
))
dismiss()
}
@ViewBuilder
private var symbolsSuggestionView: some View {
if focusedField == .symbol && !sfSymbolName.isEmpty {
let filteredMatches = allSymbols
.filter { $0.contains(sfSymbolName) }
if !filteredMatches.isEmpty {
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(filteredMatches, id: \.self) { symbolName in
Button {
sfSymbolName = symbolName
} label: {
Image(systemName: symbolName)
}
}
}
.padding(.horizontal, .layoutPadding)
}
.frame(height: 40)
.background(.ultraThinMaterial)
}
} else {
EmptyView()
}
}
}
struct AddTagGroupView_Previews: PreviewProvider {
static var previews: some View {
AddTagGroupView()
.withEnvironments()
}
static var previews: some View {
AddTagGroupView()
.withEnvironments()
}
}

View File

@ -7,13 +7,12 @@
import Foundation
let allSymbols: [String] = {
if let bundle = Bundle(identifier: "com.apple.CoreGlyphs"),
let resourcePath = bundle.path(forResource: "symbol_search", ofType: "plist"),
let plist = NSDictionary(contentsOfFile: resourcePath) {
return plist.allKeys as? [String] ?? []
}
return []
if let bundle = Bundle(identifier: "com.apple.CoreGlyphs"),
let resourcePath = bundle.path(forResource: "symbol_search", ofType: "plist"),
let plist = NSDictionary(contentsOfFile: resourcePath)
{
return plist.allKeys as? [String] ?? []
}
return []
}()

View File

@ -151,25 +151,25 @@ struct TimelineTab: View {
Label("timeline.filter.add-local", systemImage: "badge.plus.radiowaves.right")
}
}
Menu("timeline.filter.tag-groups") {
ForEach(preferences.tagGroups, id: \.self) { group in
Button {
timeline = .tagGroup(group)
} label: {
VStack {
let icon = group.sfSymbolName.isEmpty ? "number" : group.sfSymbolName
Label(group.title, systemImage: icon)
}
}
}
Button {
routerPath.presentedSheet = .addTagGroup
} label: {
Label("timeline.filter.add-tag-groups", systemImage: "plus")
Menu("timeline.filter.tag-groups") {
ForEach(preferences.tagGroups, id: \.self) { group in
Button {
timeline = .tagGroup(group)
} label: {
VStack {
let icon = group.sfSymbolName.isEmpty ? "number" : group.sfSymbolName
Label(group.title, systemImage: icon)
}
}
}
Button {
routerPath.presentedSheet = .addTagGroup
} label: {
Label("timeline.filter.add-tag-groups", systemImage: "plus")
}
}
}
private var addAccountButton: some View {

View File

@ -316,7 +316,7 @@ public struct AccountDetailView: View {
Image(systemName: "arrowshape.turn.up.left")
}
}
Menu {
AccountDetailContextMenu(viewModel: viewModel)

View File

@ -99,7 +99,7 @@ public struct AppAccountsSelectorView: View {
return theme.secondaryBackgroundColor
}
}
private var accountsView: some View {
NavigationStack {
List {

View File

@ -201,26 +201,25 @@ struct ConversationsListRow: View {
// Add in each detected link in the content
ForEach(lastStatus.content.links) { link in
switch link.type {
case .url:
if UIApplication.shared.canOpenURL(link.url) {
Button("accessibility.tabs.timeline.content-link-\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = routerPath.handle(url: link.url)
}
}
case .hashtag:
Button("accessibility.tabs.timeline.content-hashtag-\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = routerPath.handle(url: link.url)
}
case .mention:
Button("\(link.title)") {
case .url:
if UIApplication.shared.canOpenURL(link.url) {
Button("accessibility.tabs.timeline.content-link-\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = routerPath.handle(url: link.url)
}
}
case .hashtag:
Button("accessibility.tabs.timeline.content-hashtag-\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = routerPath.handle(url: link.url)
}
case .mention:
Button("\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = routerPath.handle(url: link.url)
}
}
}
}
}
}

View File

@ -15,7 +15,7 @@ public extension View {
.accessibilityInputLabels([
LocalizedStringKey("accessibility.tabs.timeline.new-post.label"),
LocalizedStringKey("accessibility.tabs.timeline.new-post.inputLabel1"),
LocalizedStringKey("accessibility.tabs.timeline.new-post.inputLabel2")
LocalizedStringKey("accessibility.tabs.timeline.new-post.inputLabel2"),
])
}
}
@ -42,7 +42,7 @@ public struct StatusEditorToolbarItem: ToolbarContent {
.accessibilityInputLabels([
LocalizedStringKey("accessibility.tabs.timeline.new-post.label"),
LocalizedStringKey("accessibility.tabs.timeline.new-post.inputLabel1"),
LocalizedStringKey("accessibility.tabs.timeline.new-post.inputLabel2")
LocalizedStringKey("accessibility.tabs.timeline.new-post.inputLabel2"),
])
}
}

View File

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

View File

@ -33,7 +33,7 @@ public enum SheetDestination: Identifiable {
case listAddAccount(account: Account)
case addAccount
case addRemoteLocalTimeline
case addTagGroup
case addTagGroup
case statusEditHistory(status: String)
case settings
case accountPushNotficationsSettings
@ -52,7 +52,7 @@ case addTagGroup
case .addAccount:
return "addAccount"
case .addTagGroup:
return "addTagGroup"
return "addTagGroup"
case .addRemoteLocalTimeline:
return "addRemoteLocalTimeline"
case .statusEditHistory:

View File

@ -12,7 +12,7 @@ public class StreamWatcher: ObservableObject {
private let decoder = JSONDecoder()
private let encoder = JSONEncoder()
private var retryDelay: Int = 10
public enum Stream: String {
@ -24,7 +24,6 @@ public class StreamWatcher: ObservableObject {
@Published public var events: [any StreamEvent] = []
@Published public var unreadNotificationsCount: Int = 0
@Published public var latestEvent: (any StreamEvent)?
public init() {
decoder.keyDecodingStrategy = .convertFromSnakeCase
@ -66,7 +65,8 @@ public class StreamWatcher: ObservableObject {
private func sendMessage(message: StreamMessage) {
if let encodedMessage = try? encoder.encode(message),
let stringMessage = String(data: encodedMessage, encoding: .utf8) {
let stringMessage = String(data: encodedMessage, encoding: .utf8)
{
task?.send(.string(stringMessage), completionHandler: { _ in })
}
}

View File

@ -12,7 +12,7 @@ public class UserPreferences: ObservableObject {
private var client: Client?
@AppStorage("remote_local_timeline") public var remoteLocalTimelines: [String] = []
@AppStorage("tag_groups") public var tagGroups: [TagGroup] = []
@AppStorage("tag_groups") public var tagGroups: [TagGroup] = []
@AppStorage("preferred_browser") public var preferredBrowser: PreferredBrowser = .inAppSafari
@AppStorage("draft_posts") public var draftsPosts: [String] = []
@AppStorage("show_translate_button_inline") public var showTranslateButton: Bool = true
@ -55,7 +55,7 @@ public class UserPreferences: ObservableObject {
@AppStorage("requested_review") public var requestedReview = false
@AppStorage("collapse-long-posts") public var collapseLongPosts = true
@AppStorage("share-button-behavior") public var shareButtonBehavior: PreferredShareButtonBehavior = .linkAndText
public enum SwipeActionsIconStyle: String, CaseIterable {

View File

@ -90,7 +90,7 @@ public struct ExploreView: View {
}
}
}
private var quickAccessView: some View {
ScrollView(.horizontal) {
HStack {

View File

@ -7,7 +7,7 @@ import SwiftUI
class ExploreViewModel: ObservableObject {
enum SearchScope: String, CaseIterable {
case all, people, hashtags, posts
var localizedString: LocalizedStringKey {
switch self {
case .all:
@ -21,7 +21,7 @@ class ExploreViewModel: ObservableObject {
}
}
}
var client: Client? {
didSet {
if oldValue != client {

View File

@ -11,7 +11,7 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
public var asMarkdown: String = ""
public var asRawText: String = ""
public var statusesURLs = [URL]()
private(set) public var links = [Link]()
public private(set) var links = [Link]()
public var asSafeMarkdownAttributedString: AttributedString = .init()
private var main_regex: NSRegularExpression?
@ -156,7 +156,7 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
asMarkdown += ")"
if let url = URL(string: href) {
let displayString = asMarkdown[start..<finish]
let displayString = asMarkdown[start ..< finish]
links.append(Link(url, displayString: String(displayString)))
}
return
@ -190,19 +190,19 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
self.displayString = displayString
switch displayString.first {
case "@":
self.type = .mention
self.title = displayString
case "#":
self.type = .hashtag
self.title = String(displayString.dropFirst())
default:
self.type = .url
var hostNameUrl = url.host ?? url.absoluteString
if hostNameUrl.hasPrefix("www.") {
hostNameUrl = String(hostNameUrl.dropFirst(4))
}
self.title = hostNameUrl
case "@":
type = .mention
title = displayString
case "#":
type = .hashtag
title = String(displayString.dropFirst())
default:
type = .url
var hostNameUrl = url.host ?? url.absoluteString
if hostNameUrl.hasPrefix("www.") {
hostNameUrl = String(hostNameUrl.dropFirst(4))
}
title = hostNameUrl
}
}

View File

@ -27,14 +27,14 @@ public struct MediaAttachment: Codable, Identifiable, Hashable, Equatable {
public var localizedTypeDescription: String? {
if let supportedType {
switch supportedType {
case .image:
return NSLocalizedString("accessibility.media.supported-type.image.label", bundle: .main, comment: "A localized description of SupportedType.image")
case .gifv:
return NSLocalizedString("accessibility.media.supported-type.gifv.label", bundle: .main, comment: "A localized description of SupportedType.gifv")
case .video:
return NSLocalizedString("accessibility.media.supported-type.video.label", bundle: .main, comment: "A localized description of SupportedType.video")
case .audio:
return NSLocalizedString("accessibility.media.supported-type.audio.label", bundle: .main, comment: "A localized description of SupportedType.audio")
case .image:
return NSLocalizedString("accessibility.media.supported-type.image.label", bundle: .main, comment: "A localized description of SupportedType.image")
case .gifv:
return NSLocalizedString("accessibility.media.supported-type.gifv.label", bundle: .main, comment: "A localized description of SupportedType.gifv")
case .video:
return NSLocalizedString("accessibility.media.supported-type.video.label", bundle: .main, comment: "A localized description of SupportedType.video")
case .audio:
return NSLocalizedString("accessibility.media.supported-type.audio.label", bundle: .main, comment: "A localized description of SupportedType.audio")
}
}
return nil

View File

@ -63,28 +63,26 @@ extension Tag: Sendable {}
extension Tag.History: Sendable {}
extension FeaturedTag: Sendable {}
public struct TagGroup: Codable, Equatable, Hashable {
public init(title: String, sfSymbolName: String, main: String, additional: [String]) {
self.title = title
self.sfSymbolName = sfSymbolName
self.main = main
self.additional = additional
}
public let title: String
public let sfSymbolName: String
public let main: String
public let additional: [String]
public var tags: [String] {
[main] + additional
}
public var description: String {
tags
.map { "#\($0)" }
.joined(separator: " ")
}
}
public init(title: String, sfSymbolName: String, main: String, additional: [String]) {
self.title = title
self.sfSymbolName = sfSymbolName
self.main = main
self.additional = additional
}
public let title: String
public let sfSymbolName: String
public let main: String
public let additional: [String]
public var tags: [String] {
[main] + additional
}
public var description: String {
tags
.map { "#\($0)" }
.joined(separator: " ")
}
}

View File

@ -16,7 +16,7 @@ public enum Media: Endpoint {
public func queryItems() -> [URLQueryItem]? {
return nil
}
public var jsonValue: Encodable? {
switch self {
case let .media(_, json):
@ -25,7 +25,6 @@ public enum Media: Endpoint {
return nil
}
}
}
public struct MediaDescriptionData: Encodable, Sendable {
@ -35,4 +34,3 @@ public struct MediaDescriptionData: Encodable, Sendable {
self.description = description
}
}

View File

@ -4,7 +4,7 @@ public enum Timelines: Endpoint {
case pub(sinceId: String?, maxId: String?, minId: String?, local: Bool)
case home(sinceId: String?, maxId: String?, minId: String?)
case list(listId: String, sinceId: String?, maxId: String?, minId: String?)
case hashtag(tag: String, additional: [String]?, maxId: String?)
case hashtag(tag: String, additional: [String]?, maxId: String?)
public func path() -> String {
switch self {
@ -18,7 +18,7 @@ public enum Timelines: Endpoint {
return "timelines/tag/\(tag)"
}
}
public func queryItems() -> [URLQueryItem]? {
switch self {
case let .pub(sinceId, maxId, minId, local):
@ -30,10 +30,10 @@ public enum Timelines: Endpoint {
case let .list(_, sinceId, maxId, mindId):
return makePaginationParam(sinceId: sinceId, maxId: maxId, mindId: mindId)
case let .hashtag(_, additional, maxId):
var params = makePaginationParam(sinceId: nil, maxId: maxId, mindId: nil) ?? []
params.append(contentsOf: (additional ?? [])
.map { URLQueryItem(name: "any[]", value: $0) })
return params
var params = makePaginationParam(sinceId: nil, maxId: maxId, mindId: nil) ?? []
params.append(contentsOf: (additional ?? [])
.map { URLQueryItem(name: "any[]", value: $0) })
return params
}
}
}

View File

@ -11,9 +11,9 @@ public struct StatusDetailView: View {
@EnvironmentObject private var watcher: StreamWatcher
@EnvironmentObject private var client: Client
@EnvironmentObject private var routerPath: RouterPath
@StateObject private var viewModel: StatusDetailViewModel
@State private var isLoaded: Bool = false
@State private var statusHeight: CGFloat = 0

View File

@ -1,35 +1,33 @@
import UIKit
import SwiftUI
import UIKit
struct StatusEditorCameraPickerView: UIViewControllerRepresentable {
@Binding var selectedImage: UIImage?
@Environment(\.presentationMode) var isPresented
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let picker: StatusEditorCameraPickerView
init(picker: StatusEditorCameraPickerView) {
self.picker = picker
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
func imagePickerController(_: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
guard let selectedImage = info[.originalImage] as? UIImage else { return }
self.picker.selectedImage = selectedImage
self.picker.isPresented.wrappedValue.dismiss()
picker.selectedImage = selectedImage
picker.isPresented.wrappedValue.dismiss()
}
}
func makeUIViewController(context: Context) -> UIImagePickerController {
let imagePicker = UIImagePickerController()
imagePicker.sourceType = .camera
imagePicker.delegate = context.coordinator
return imagePicker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
}
func updateUIViewController(_: UIImagePickerController, context _: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(picker: self)
}

View File

@ -15,7 +15,7 @@ struct StatusEditorMediaEditView: View {
@FocusState private var isFieldFocused: Bool
@State private var isUpdating: Bool = false
@State private var didAppear: Bool = false
var body: some View {

View File

@ -71,7 +71,8 @@ enum StatusEditorUTTypeSupported: String, CaseIterable {
if self == .jpeg || self == .png || self == .tiff || self == .image || self == .uiimage || self == .adobeRawImage {
if let image = result as? UIImage,
let compressedData = try? await compressor.compressImageForUpload(image),
let compressedImage = UIImage(data: compressedData) {
let compressedImage = UIImage(data: compressedData)
{
return compressedImage
} else if let imageURL = result as? URL,
let compressedData = await compressor.compressImageFrom(url: imageURL),

View File

@ -368,7 +368,7 @@ public class StatusEditorViewModel: NSObject, ObservableObject {
.compactMap { NSItemProvider(contentsOf: $0) }
processItemsProvider(items: items)
}
func processCameraPhoto(image: UIImage) {
mediasImages.append(.init(image: image,
movieTransferable: nil,

View File

@ -75,7 +75,7 @@ public class ReblogCache {
if let reblog = status.reblog {
if let cached = statusCache.value(forKey: reblog.id) {
// this is already cached
if cached.postId != status.id && cached.seen {
if cached.postId != status.id, cached.seen {
// This was posted by someone other than the person we have in the cache
// and we have seen the items at some point, so we might want to suppress it

View File

@ -117,7 +117,6 @@ public struct StatusPollView: View {
}
.accessibilityElement(children: .contain)
.accessibilityLabel(viewModel.poll.expired ? "accessibility.status.poll.finished.label" : "accessibility.status.poll.active.label")
}
func combinedAccessibilityLabel(for option: Poll.Option, index: Int) -> Text {
@ -126,7 +125,6 @@ public struct StatusPollView: View {
Text(", ") +
Text(option.title) +
Text(showPercentage ? ", \(percentForOption(option: option))%" : "")
}
private var footerView: some View {

View File

@ -124,7 +124,7 @@ public struct StatusRowView: View {
trailing: .layoutPadding))
.accessibilityElement(children: viewModel.isFocused ? .contain : .combine)
.accessibilityLabel(viewModel.isFocused == false && accessibilityEnabled
? CombinedAccessibilityLabel(viewModel: viewModel).finalLabel() : Text(""))
? CombinedAccessibilityLabel(viewModel: viewModel).finalLabel() : Text(""))
.accessibilityHidden(viewModel.filter?.filter.filterAction == .hide)
.accessibilityAction {
viewModel.navigateToDetail()
@ -214,23 +214,23 @@ public struct StatusRowView: View {
// Add in each detected link in the content
ForEach(viewModel.finalStatus.content.links) { link in
switch link.type {
case .url:
if UIApplication.shared.canOpenURL(link.url) {
Button("accessibility.tabs.timeline.content-link-\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = viewModel.routerPath.handle(url: link.url)
}
}
case .hashtag:
Button("accessibility.tabs.timeline.content-hashtag-\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = viewModel.routerPath.handle(url: link.url)
}
case .mention:
Button("\(link.title)") {
case .url:
if UIApplication.shared.canOpenURL(link.url) {
Button("accessibility.tabs.timeline.content-link-\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = viewModel.routerPath.handle(url: link.url)
}
}
case .hashtag:
Button("accessibility.tabs.timeline.content-hashtag-\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = viewModel.routerPath.handle(url: link.url)
}
case .mention:
Button("\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = viewModel.routerPath.handle(url: link.url)
}
}
}
}
@ -298,28 +298,27 @@ private struct CombinedAccessibilityLabel {
func finalLabel() -> Text {
if let filter {
switch filter.filterAction {
case .warn:
return Text("status.filter.filtered-by-\(filter.title)")
case .hide:
return Text("")
case .warn:
return Text("status.filter.filtered-by-\(filter.title)")
case .hide:
return Text("")
}
} else {
return userNamePreamble() +
Text(hasSpoiler
? viewModel.finalStatus.spoilerText.asRawText
: viewModel.finalStatus.content.asRawText
) +
Text(hasSpoiler
? "status.editor.spoiler"
: ""
) + Text(", ") +
pollText() +
imageAltText() +
Text(viewModel.finalStatus.createdAt.relativeFormatted) + Text(", ") +
Text("status.summary.n-replies \(viewModel.finalStatus.repliesCount)") + Text(", ") +
Text("status.summary.n-boosts \(viewModel.finalStatus.reblogsCount)") + Text(", ") +
Text("status.summary.n-favorites \(viewModel.finalStatus.favouritesCount)")
Text(hasSpoiler
? viewModel.finalStatus.spoilerText.asRawText
: viewModel.finalStatus.content.asRawText
) +
Text(hasSpoiler
? "status.editor.spoiler"
: ""
) + Text(", ") +
pollText() +
imageAltText() +
Text(viewModel.finalStatus.createdAt.relativeFormatted) + Text(", ") +
Text("status.summary.n-replies \(viewModel.finalStatus.repliesCount)") + Text(", ") +
Text("status.summary.n-boosts \(viewModel.finalStatus.reblogsCount)") + Text(", ") +
Text("status.summary.n-favorites \(viewModel.finalStatus.favouritesCount)")
}
}
@ -377,12 +376,12 @@ private struct CombinedAccessibilityLabel {
: 0
text = text +
Text(selected ? "accessibility.status.poll.selected.label" : "") +
Text(", ") +
Text("accessibility.status.poll.option-prefix-\(index + 1)-of-\(poll.options.count)") +
Text(", ") +
Text(option.title) +
Text(showPercentage ? ", \(percentage)%. " : ". ")
Text(selected ? "accessibility.status.poll.selected.label" : "") +
Text(", ") +
Text("accessibility.status.poll.option-prefix-\(index + 1)-of-\(poll.options.count)") +
Text(", ") +
Text(option.title) +
Text(showPercentage ? ", \(percentage)%. " : ". ")
}
}
return Text("")

View File

@ -33,13 +33,14 @@ public class StatusRowViewModel: ObservableObject {
@Published var isLoadingRemoteContent: Bool = false
@Published var localStatusId: String?
@Published var localStatus: Status?
// The relationship our user has to the author of this post, if available
@Published var authorRelationship: Relationship? {
didSet {
// if we are newly blocking or muting the author, force collapse post so it goes away
if let relationship = authorRelationship,
relationship.blocking || relationship.muting {
relationship.blocking || relationship.muting
{
lineLimit = 0
}
}

View File

@ -124,8 +124,7 @@ struct StatusRowActionsView: View {
{
switch userPreferences.shareButtonBehavior {
case .linkOnly:
ShareLink(item: url)
{
ShareLink(item: url) {
action.image(dataController: statusDataController)
}
.buttonStyle(.statusAction())

View File

@ -187,8 +187,7 @@ struct StatusRowContextMenu: View {
} label: {
Label("status.action.message", systemImage: "tray.full")
}
if viewModel.authorRelationship?.blocking == true {
Button {
Task {
@ -216,7 +215,7 @@ struct StatusRowContextMenu: View {
Label("account.action.block", systemImage: "person.crop.circle.badge.xmark")
}
}
if viewModel.authorRelationship?.muting == true {
Button {
Task {
@ -248,7 +247,6 @@ struct StatusRowContextMenu: View {
Label("account.action.mute", systemImage: "speaker.slash")
}
}
}
}
Section {

View File

@ -31,8 +31,8 @@ public enum RemoteTimelineFilter: String, CaseIterable, Hashable, Equatable {
public enum TimelineFilter: Hashable, Equatable {
case home, local, federated, trending
case hashtag(tag: String, accountId: String?)
case tagGroup(TagGroup)
case hashtag(tag: String, accountId: String?)
case tagGroup(TagGroup)
case list(list: Models.List)
case remoteLocal(server: String, filter: RemoteTimelineFilter)
case latest
@ -74,7 +74,7 @@ public enum TimelineFilter: Hashable, Equatable {
case let .hashtag(tag, _):
return "#\(tag)"
case let .tagGroup(group):
return group.title
return group.title
case let .list(list):
return list.title
case let .remoteLocal(server, _):
@ -97,7 +97,7 @@ public enum TimelineFilter: Hashable, Equatable {
case let .hashtag(tag, _):
return "#\(tag)"
case let .tagGroup(group):
return LocalizedStringKey(group.title) // ?? not sure since this can't be localized.
return LocalizedStringKey(group.title) // ?? not sure since this can't be localized.
case let .list(list):
return LocalizedStringKey(list.title)
case let .remoteLocal(server, _):
@ -147,10 +147,10 @@ public enum TimelineFilter: Hashable, Equatable {
if let accountId {
return Accounts.statuses(id: accountId, sinceId: nil, tag: tag, onlyMedia: nil, excludeReplies: nil, pinned: nil)
} else {
return Timelines.hashtag(tag: tag, additional: nil, maxId: maxId)
return Timelines.hashtag(tag: tag, additional: nil, maxId: maxId)
}
case let .tagGroup(group):
return Timelines.hashtag(tag: group.main, additional: group.additional, maxId: maxId)
return Timelines.hashtag(tag: group.main, additional: group.additional, maxId: maxId)
}
}
}
@ -162,7 +162,7 @@ extension TimelineFilter: Codable {
case federated
case trending
case hashtag
case tagGroup
case tagGroup
case list
case remoteLocal
case latest
@ -189,8 +189,8 @@ extension TimelineFilter: Codable {
accountId: accountId
)
case .tagGroup:
let group = try container.decode(TagGroup.self, forKey: .tagGroup)
self = .tagGroup(group)
let group = try container.decode(TagGroup.self, forKey: .tagGroup)
self = .tagGroup(group)
case .list:
let list = try container.decode(
Models.List.self,
@ -233,7 +233,7 @@ extension TimelineFilter: Codable {
try nestedContainer.encode(tag)
try nestedContainer.encode(accountId)
case let .tagGroup(group):
try container.encode(group, forKey: .tagGroup)
try container.encode(group, forKey: .tagGroup)
case let .list(list):
try container.encode(list, forKey: .list)
case let .remoteLocal(server, filter):

View File

@ -1,11 +1,11 @@
import DesignSystem
import Env
import SwiftUIIntrospect
import Models
import Network
import Shimmer
import Status
import SwiftUI
import SwiftUIIntrospect
public struct TimelineView: View {
private enum Constants {
@ -39,8 +39,8 @@ public struct TimelineView: View {
ScrollViewReader { proxy in
ZStack(alignment: .top) {
List {
if viewModel.tagGroup != nil {
tagGroupHeaderView
if viewModel.tagGroup != nil {
tagGroupHeaderView
} else if viewModel.tag == nil {
scrollToTopView
} else {
@ -106,19 +106,18 @@ public struct TimelineView: View {
}
.accessibilityRepresentation {
switch timeline {
case let .remoteLocal(_, filter):
if canFilterTimeline {
Menu(filter.localizedTitle()) {}
} else {
Text(filter.localizedTitle())
}
default:
if canFilterTimeline {
Menu(timeline.localizedTitle()) {}
} else {
Text(timeline.localizedTitle())
}
case let .remoteLocal(_, filter):
if canFilterTimeline {
Menu(filter.localizedTitle()) {}
} else {
Text(filter.localizedTitle())
}
default:
if canFilterTimeline {
Menu(timeline.localizedTitle()) {}
} else {
Text(timeline.localizedTitle())
}
}
}
.accessibilityAddTraits(.isHeader)
@ -182,64 +181,64 @@ public struct TimelineView: View {
@ViewBuilder
private var tagHeaderView: some View {
if let tag = viewModel.tag {
headerView {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("#\(tag.name)")
.font(.scaledHeadline)
Text("timeline.n-recent-from-n-participants \(tag.totalUses) \(tag.totalAccounts)")
.font(.scaledFootnote)
.foregroundColor(.gray)
}
.accessibilityElement(children: .combine)
Spacer()
Button {
Task {
if tag.following {
viewModel.tag = await account.unfollowTag(id: tag.name)
} else {
viewModel.tag = await account.followTag(id: tag.name)
}
}
} label: {
Text(tag.following ? "account.follow.following" : "account.follow.follow")
}.buttonStyle(.bordered)
}
}
}
}
@ViewBuilder
private var tagGroupHeaderView: some View {
if let group = viewModel.tagGroup {
headerView {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(group.description)
.font(.scaledHeadline)
}
.accessibilityElement(children: .combine)
}
headerView {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("#\(tag.name)")
.font(.scaledHeadline)
Text("timeline.n-recent-from-n-participants \(tag.totalUses) \(tag.totalAccounts)")
.font(.scaledFootnote)
.foregroundColor(.gray)
}
.accessibilityElement(children: .combine)
Spacer()
Button {
Task {
if tag.following {
viewModel.tag = await account.unfollowTag(id: tag.name)
} else {
viewModel.tag = await account.followTag(id: tag.name)
}
}
} label: {
Text(tag.following ? "account.follow.following" : "account.follow.follow")
}.buttonStyle(.bordered)
}
}
}
@ViewBuilder
private func headerView(
@ViewBuilder content: () -> some View
) -> some View {
VStack(alignment: .leading) {
Spacer()
content()
Spacer()
}
@ViewBuilder
private var tagGroupHeaderView: some View {
if let group = viewModel.tagGroup {
headerView {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(group.description)
.font(.scaledHeadline)
}
.accessibilityElement(children: .combine)
}
.listRowBackground(theme.secondaryBackgroundColor)
.listRowSeparator(.hidden)
.listRowInsets(.init(top: 8,
leading: .layoutPadding,
bottom: 8,
trailing: .layoutPadding))
}
}
}
@ViewBuilder
private func headerView(
@ViewBuilder content: () -> some View
) -> some View {
VStack(alignment: .leading) {
Spacer()
content()
Spacer()
}
.listRowBackground(theme.secondaryBackgroundColor)
.listRowSeparator(.hidden)
.listRowInsets(.init(top: 8,
leading: .layoutPadding,
bottom: 8,
trailing: .layoutPadding))
}
private var scrollToTopView: some View {
HStack { EmptyView() }

View File

@ -40,13 +40,13 @@ class TimelineViewModel: ObservableObject {
private var timelineTask: Task<Void, Never>?
@Published var tag: Tag?
var tagGroup: TagGroup? {
if case let .tagGroup(group) = timeline {
return group
}
return nil
var tagGroup: TagGroup? {
if case let .tagGroup(group) = timeline {
return group
}
return nil
}
// Internal source of truth for a timeline.
private var datasource = TimelineDatasource()
@ -170,7 +170,7 @@ extension TimelineViewModel: StatusesFetcher {
}
await fetchNewestStatuses()
}
func refreshTimeline() {
timelineTask?.cancel()
timelineTask = Task {
@ -321,8 +321,9 @@ extension TimelineViewModel: StatusesFetcher {
// If none, it'll stop there.
// Only do that in the context of the home timeline as other don't worth catching up that much.
if timeline == .home,
!Task.isCancelled,
let latest = await datasource.get().first {
!Task.isCancelled,
let latest = await datasource.get().first
{
try await fetchNewPagesFrom(latestStatus: latest, client: client)
}
}