Code cleanup / format / lint

This commit is contained in:
Thomas Ricouard 2023-01-27 20:36:40 +01:00
parent 8cac9df8c6
commit b89221a535
29 changed files with 239 additions and 262 deletions

View file

@ -5,100 +5,100 @@
// Created by Thomas Durand on 26/01/2023.
//
import UIKit
import MobileCoreServices
import UIKit
import UniformTypeIdentifiers
import Models
import Network
// Sample code was sending this from a thread to another, let asume @Sendable for this
extension NSExtensionContext: @unchecked Sendable { }
extension NSExtensionContext: @unchecked Sendable {}
class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
enum Error: Swift.Error {
case inputProviderNotFound
case loadedItemHasWrongType
case urlNotFound
case noHost
case notMastodonInstance
}
enum Error: Swift.Error {
case inputProviderNotFound
case loadedItemHasWrongType
case urlNotFound
case noHost
case notMastodonInstance
}
func beginRequest(with context: NSExtensionContext) {
// Do not call super in an Action extension with no user interface
Task {
do {
let url = try await url(from: context)
guard await url.isMastodonInstance else {
throw Error.notMastodonInstance
}
await MainActor.run {
let deeplink = url.iceCubesAppDeepLink
let output = output(wrapping: deeplink)
context.completeRequest(returningItems: output)
}
} catch {
await MainActor.run {
context.completeRequest(returningItems: [])
}
}
func beginRequest(with context: NSExtensionContext) {
// Do not call super in an Action extension with no user interface
Task {
do {
let url = try await url(from: context)
guard await url.isMastodonInstance else {
throw Error.notMastodonInstance
}
await MainActor.run {
let deeplink = url.iceCubesAppDeepLink
let output = output(wrapping: deeplink)
context.completeRequest(returningItems: output)
}
} catch {
await MainActor.run {
context.completeRequest(returningItems: [])
}
}
}
}
}
extension URL {
var isMastodonInstance: Bool {
get async {
do {
guard let host = host() else {
throw ActionRequestHandler.Error.noHost
}
let _: Instance = try await Client(server: host).get(endpoint: Instances.instance)
return true
} catch {
return false
}
var isMastodonInstance: Bool {
get async {
do {
guard let host = host() else {
throw ActionRequestHandler.Error.noHost
}
let _: Instance = try await Client(server: host).get(endpoint: Instances.instance)
return true
} catch {
return false
}
}
}
var iceCubesAppDeepLink: URL {
var components = URLComponents(url: self, resolvingAgainstBaseURL: false)!
components.scheme = AppInfo.scheme.trimmingCharacters(in: [":", "/"])
return components.url!
}
var iceCubesAppDeepLink: URL {
var components = URLComponents(url: self, resolvingAgainstBaseURL: false)!
components.scheme = AppInfo.scheme.trimmingCharacters(in: [":", "/"])
return components.url!
}
}
extension ActionRequestHandler {
/// Will look for an input item that might provide the property list that Javascript sent us
private func url(from context: NSExtensionContext) async throws -> URL {
for item in context.inputItems as! [NSExtensionItem] {
guard let attachments = item.attachments else {
continue
}
for itemProvider in attachments {
guard itemProvider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) else {
continue
}
guard let dictionary = try await itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier) as? [String: Any] else {
throw Error.loadedItemHasWrongType
}
let input = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! [String: Any]? ?? [:]
guard let absoluteStringUrl = input["url"] as? String, let url = URL(string: absoluteStringUrl) else {
throw Error.urlNotFound
}
return url
}
/// Will look for an input item that might provide the property list that Javascript sent us
private func url(from context: NSExtensionContext) async throws -> URL {
for item in context.inputItems as! [NSExtensionItem] {
guard let attachments = item.attachments else {
continue
}
for itemProvider in attachments {
guard itemProvider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) else {
continue
}
throw Error.inputProviderNotFound
guard let dictionary = try await itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier) as? [String: Any] else {
throw Error.loadedItemHasWrongType
}
let input = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! [String: Any]? ?? [:]
guard let absoluteStringUrl = input["url"] as? String, let url = URL(string: absoluteStringUrl) else {
throw Error.urlNotFound
}
return url
}
}
throw Error.inputProviderNotFound
}
/// Wrap the output to the expected object so we send back results to JS
private func output(wrapping deeplink: URL) -> [NSExtensionItem] {
let results = ["deeplink": deeplink.absoluteString]
let dictionary = [NSExtensionJavaScriptFinalizeArgumentKey: results]
let provider = NSItemProvider(item: dictionary as NSDictionary, typeIdentifier: UTType.propertyList.identifier)
let item = NSExtensionItem()
item.attachments = [provider]
return [item]
}
/// Wrap the output to the expected object so we send back results to JS
private func output(wrapping deeplink: URL) -> [NSExtensionItem] {
let results = ["deeplink": deeplink.absoluteString]
let dictionary = [NSExtensionJavaScriptFinalizeArgumentKey: results]
let provider = NSItemProvider(item: dictionary as NSDictionary, typeIdentifier: UTType.propertyList.identifier)
let item = NSExtensionItem()
item.attachments = [provider]
return [item]
}
}

View file

@ -2,8 +2,8 @@ import Account
import AppAccount
import DesignSystem
import Env
import SwiftUI
import Models
import SwiftUI
struct SideBarView<Content: View>: View {
@EnvironmentObject private var appAccounts: AppAccountsManager

View file

@ -1,9 +1,9 @@
import SwiftUI
import Account
import AppAccount
import DesignSystem
import Env
import Models
import AppAccount
import SwiftUI
struct AccountSettingsView: View {
@Environment(\.dismiss) private var dismiss
@ -54,7 +54,6 @@ struct AccountSettingsView: View {
} label: {
Text("account.action.logout")
}
}
.listRowBackground(theme.primaryBackgroundColor)
}

View file

@ -8,7 +8,6 @@ import SwiftUI
import UserNotifications
struct ContentSettingsView: View {
@EnvironmentObject private var userPreferences: UserPreferences
@EnvironmentObject private var theme: Theme
@ -19,7 +18,7 @@ struct ContentSettingsView: View {
Text("settings.content.use-instance-settings")
}
} footer: {
Text("settings.content.main-toggle.description")
Text("settings.content.main-toggle.description")
}
.listRowBackground(theme.primaryBackgroundColor)
.onChange(of: userPreferences.useInstanceContentSettings) { newVal in
@ -29,7 +28,6 @@ struct ContentSettingsView: View {
userPreferences.appDefaultPostsSensitive = userPreferences.postIsSensitive
userPreferences.appDefaultPostVisibility = userPreferences.postVisibility
}
}
Section("settings.content.reading") {
@ -54,22 +52,15 @@ struct ContentSettingsView: View {
}
.disabled(userPreferences.useInstanceContentSettings)
Toggle(isOn: $userPreferences.appDefaultPostsSensitive) {
Text("settings.content.default-sensitive")
}
.disabled(userPreferences.useInstanceContentSettings)
}
.listRowBackground(theme.primaryBackgroundColor)
}
.navigationTitle("settings.content.navigation-title")
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
}
}

View file

@ -101,7 +101,6 @@ struct PushNotificationsView: View {
Text("settings.push.duplicate.footer")
}
.listRowBackground(theme.primaryBackgroundColor)
}
.navigationTitle("settings.push.navigation-title")
.scrollContentBackground(.hidden)

View file

@ -75,7 +75,8 @@ struct SettingsTabs: View {
if let index = indexSet.first {
let account = appAccountsManager.availableAccounts[index]
if let token = account.oauthToken,
let sub = pushNotifications.subscriptions.first(where: { $0.account.token == token }) {
let sub = pushNotifications.subscriptions.first(where: { $0.account.token == token })
{
Task {
await sub.deleteSubscription()
appAccountsManager.delete(account: account)

View file

@ -10,7 +10,7 @@ public struct EditAccountView: View {
@StateObject private var viewModel = EditAccountViewModel()
public init() { }
public init() {}
public var body: some View {
NavigationStack {

View file

@ -1,8 +1,8 @@
import SwiftUI
import Models
import Env
import DesignSystem
import Env
import Models
import Network
import SwiftUI
struct EditFilterView: View {
@Environment(\.dismiss) private var dismiss
@ -55,7 +55,7 @@ struct EditFilterView: View {
.background(theme.secondaryBackgroundColor)
.onAppear {
if filter == nil {
isTitleFocused = true
isTitleFocused = true
}
}
.toolbar {
@ -76,7 +76,6 @@ struct EditFilterView: View {
}
}
.listRowBackground(theme.primaryBackgroundColor)
}
private var keywordsSection: some View {
@ -182,7 +181,7 @@ struct EditFilterView: View {
} else {
let newFilter: ServerFilter = try await client.post(endpoint: ServerFilters.createFilter(json: data),
forceVersion: .v2)
self.filter = newFilter
filter = newFilter
}
} catch {}
isSavingFilter = false
@ -193,12 +192,12 @@ struct EditFilterView: View {
isSavingFilter = true
do {
let keyword: ServerFilter.Keyword = try await
client.post(endpoint: ServerFilters.addKeyword(filter: filterId,
keyword: name,
wholeWord: true),
forceVersion: .v2)
self.keywords.append(keyword)
} catch { }
client.post(endpoint: ServerFilters.addKeyword(filter: filterId,
keyword: name,
wholeWord: true),
forceVersion: .v2)
keywords.append(keyword)
} catch {}
isSavingFilter = false
}
@ -210,7 +209,7 @@ struct EditFilterView: View {
if response?.statusCode == 200 {
keywords.removeAll(where: { $0.id == keyword.id })
}
} catch { }
} catch {}
isSavingFilter = false
}
}

View file

@ -1,8 +1,8 @@
import SwiftUI
import Env
import Network
import DesignSystem
import Env
import Models
import Network
import SwiftUI
public struct FiltersListView: View {
@Environment(\.dismiss) private var dismiss
@ -14,7 +14,7 @@ public struct FiltersListView: View {
@State private var isLoading: Bool = true
@State private var filters: [ServerFilter] = []
public init() { }
public init() {}
public var body: some View {
NavigationStack {
@ -31,7 +31,7 @@ public struct FiltersListView: View {
VStack(alignment: .leading) {
Text(filter.title)
.font(.scaledSubheadline)
Text("\(filter.context.map{ $0.name }.joined(separator: ", "))")
Text("\(filter.context.map { $0.name }.joined(separator: ", "))")
.font(.scaledBody)
.foregroundColor(.gray)
}

View file

@ -4,7 +4,7 @@ import Models
import Network
import SwiftUI
extension AppAccount {
public extension AppAccount {
private static var keychain: KeychainSwift {
let keychain = KeychainSwift()
#if !DEBUG && !targetEnvironment(simulator)
@ -13,17 +13,17 @@ extension AppAccount {
return keychain
}
public func save() throws {
func save() throws {
let encoder = JSONEncoder()
let data = try encoder.encode(self)
Self.keychain.set(data, forKey: key)
}
public func delete() {
func delete() {
Self.keychain.delete(key)
}
public static func retrieveAll() -> [AppAccount] {
static func retrieveAll() -> [AppAccount] {
migrateLegacyAccounts()
let keychain = Self.keychain
let decoder = JSONDecoder()
@ -39,7 +39,7 @@ extension AppAccount {
return accounts
}
public static func migrateLegacyAccounts() {
static func migrateLegacyAccounts() {
let keychain = KeychainSwift()
let decoder = JSONDecoder()
let keys = keychain.allKeys
@ -52,7 +52,7 @@ extension AppAccount {
}
}
public static func deleteAll() {
static func deleteAll() {
let keychain = Self.keychain
let keys = keychain.allKeys
for key in keys {

View file

@ -39,8 +39,8 @@ public struct ConversationDetailView: View {
ForEach(viewModel.messages) { message in
ConversationMessageView(message: message,
conversation: viewModel.conversation)
.padding(.vertical, 4)
.id(message.id)
.padding(.vertical, 4)
.id(message.id)
}
bottomAnchorView
}
@ -129,7 +129,7 @@ public struct ConversationDetailView: View {
.overlay(
RoundedRectangle(cornerRadius: 14)
.stroke(.gray, lineWidth: 1)
)
)
.font(.scaledBody)
if !viewModel.newMessageText.isEmpty {
Button {

View file

@ -68,7 +68,7 @@ struct ConversationMessageView: View {
}
Group {
Text(message.createdAt.shortDateFormatted) +
Text(" ")
Text(" ")
Text(message.createdAt.asDate, style: .time)
}
.font(.scaledFootnote)

View file

@ -64,7 +64,7 @@ public class PushNotificationsService: ObservableObject {
await withTaskGroup(of: Void.self, body: { group in
group.addTask {
await subscription.fetchSubscription()
if await subscription.subscription != nil && !forceCreate {
if await subscription.subscription != nil, !forceCreate {
await subscription.deleteSubscription()
await subscription.updateSubscription()
} else if forceCreate {
@ -176,15 +176,15 @@ public class PushNotificationSubscriptionSettings: ObservableObject {
listenerURL += "?sandbox=true"
#endif
subscription =
try await client.post(endpoint: Push.createSub(endpoint: listenerURL,
p256dh: key,
auth: authKey,
mentions: isMentionNotificationEnabled,
status: isNewPostsNotificationEnabled,
reblog: isReblogNotificationEnabled,
follow: isFollowNotificationEnabled,
favorite: isFavoriteNotificationEnabled,
poll: isPollNotificationEnabled))
try await client.post(endpoint: Push.createSub(endpoint: listenerURL,
p256dh: key,
auth: authKey,
mentions: isMentionNotificationEnabled,
status: isNewPostsNotificationEnabled,
reblog: isReblogNotificationEnabled,
follow: isFollowNotificationEnabled,
favorite: isFavoriteNotificationEnabled,
poll: isPollNotificationEnabled))
isEnabled = subscription != nil
} catch {

View file

@ -35,7 +35,7 @@ public enum SheetDestinations: Identifiable {
public var id: String {
switch self {
case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor,
.mentionStatusEditor, .settings, .accountPushNotficationsSettings:
.mentionStatusEditor, .settings, .accountPushNotficationsSettings:
return "statusEditor"
case .listEdit:
return "listEdit"
@ -67,8 +67,8 @@ public class RouterPath: ObservableObject {
public func handleStatus(status: AnyStatus, url: URL) -> OpenURLAction.Result {
if url.pathComponents.count == 3 && url.pathComponents[1] == "tags" &&
url.host() == status.account.url?.host(),
let tag = url.pathComponents.last
url.host() == status.account.url?.host(),
let tag = url.pathComponents.last
{
// OK this test looks weird but it's
// A 3 component path i.e. ["/", "tags", "tagname"]
@ -110,7 +110,8 @@ public class RouterPath: ObservableObject {
} else if let client = client,
client.isAuth,
client.hasConnection(with: url),
let id = Int(url.lastPathComponent) {
let id = Int(url.lastPathComponent)
{
if url.absoluteString.contains(client.server) {
navigate(to: .statusDetail(id: String(id)))
} else {

View file

@ -22,57 +22,42 @@ public class UserPreferences: ObservableObject {
@AppStorage("use_instance_content_settings") public var useInstanceContentSettings: Bool = true
@AppStorage("app_auto_expand_spoilers") public var appAutoExpandSpoilers = false
@AppStorage("app_auto_expand_media") public var appAutoExpandMedia:ServerPreferences.AutoExpandMedia = .hideSensitive
@AppStorage("app_default_post_visibility") public var appDefaultPostVisibility:Models.Visibility = .pub
@AppStorage("app_auto_expand_media") public var appAutoExpandMedia: ServerPreferences.AutoExpandMedia = .hideSensitive
@AppStorage("app_default_post_visibility") public var appDefaultPostVisibility: Models.Visibility = .pub
@AppStorage("app_default_posts_sensitive") public var appDefaultPostsSensitive = false
public var postVisibility:Models.Visibility {
get{
if useInstanceContentSettings {
return serverPreferences?.postVisibility ?? .pub
}
else {
return appDefaultPostVisibility
}
public var postVisibility: Models.Visibility {
if useInstanceContentSettings {
return serverPreferences?.postVisibility ?? .pub
} else {
return appDefaultPostVisibility
}
}
public var postIsSensitive:Bool {
get {
if useInstanceContentSettings {
return serverPreferences?.postIsSensitive ?? false
}
else {
return appDefaultPostsSensitive
}
public var postIsSensitive: Bool {
if useInstanceContentSettings {
return serverPreferences?.postIsSensitive ?? false
} else {
return appDefaultPostsSensitive
}
}
public var autoExpandSpoilers: Bool {
get {
if useInstanceContentSettings {
return serverPreferences?.autoExpandSpoilers ?? true
}
else {
return appAutoExpandSpoilers
}
if useInstanceContentSettings {
return serverPreferences?.autoExpandSpoilers ?? true
} else {
return appAutoExpandSpoilers
}
}
public var autoExpandMedia: ServerPreferences.AutoExpandMedia {
get {
if useInstanceContentSettings {
return serverPreferences?.autoExpandMedia ?? .hideSensitive
}
else {
return appAutoExpandMedia
}
if useInstanceContentSettings {
return serverPreferences?.autoExpandMedia ?? .hideSensitive
} else {
return appAutoExpandMedia
}
}
public var pushNotificationsCount: Int {
get {
Self.sharedDefault?.integer(forKey: "push_notifications_count") ?? 0

View file

@ -20,7 +20,8 @@ public struct AppAccount: Codable, Identifiable, Hashable {
public init(server: String,
accountName: String?,
oauthToken: OauthToken? = nil) {
oauthToken: OauthToken? = nil)
{
self.server = server
self.accountName = accountName
self.oauthToken = oauthToken

View file

@ -35,9 +35,9 @@ public struct NullableString: Codable, Equatable, Hashable {
public init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
self.value = try container.decode(String.self)
value = try container.decode(String.self)
} catch {
self.value = nil
value = nil
}
}
}

View file

@ -23,8 +23,8 @@ public struct ServerFilter: Codable, Identifiable, Hashable {
public let expireIn: Int?
}
extension ServerFilter.Context {
public var iconName: String {
public extension ServerFilter.Context {
var iconName: String {
switch self {
case .home:
return "rectangle.on.rectangle"
@ -39,7 +39,7 @@ extension ServerFilter.Context {
}
}
public var name: String {
var name: String {
switch self {
case .home:
return "Home and lists"
@ -55,8 +55,8 @@ extension ServerFilter.Context {
}
}
extension ServerFilter.Action {
public var label: String {
public extension ServerFilter.Action {
var label: String {
switch self {
case .warn:
return "Hide with a warning"

View file

@ -30,7 +30,7 @@ public enum ServerFilters: Endpoint {
switch self {
case let .addKeyword(_, keyword, wholeWord):
return [.init(name: "keyword", value: keyword),
.init(name: "whole_word", value: wholeWord ? "true": "false")]
.init(name: "whole_word", value: wholeWord ? "true" : "false")]
default:
return nil
}
@ -57,7 +57,8 @@ public struct ServerFilterData: Encodable {
public init(title: String,
context: [ServerFilter.Context],
filterAction: ServerFilter.Action,
expireIn: Int?) {
expireIn: Int?)
{
self.title = title
self.context = context
self.filterAction = filterAction

View file

@ -76,7 +76,7 @@ class NotificationsViewModel: ObservableObject {
newNotifications = newNotifications.filter { notification in
!consolidatedNotifications.contains(where: { $0.id == notification.id })
}
self.notifications.append(contentsOf: newNotifications)
notifications.append(contentsOf: newNotifications)
consolidatedNotifications.insert(contentsOf: newNotifications.consolidated(), at: 0)
}
withAnimation {
@ -98,7 +98,7 @@ class NotificationsViewModel: ObservableObject {
maxId: lastId,
types: queryTypes))
consolidatedNotifications.append(contentsOf: newNotifications.consolidated())
self.notifications.append(contentsOf: newNotifications)
notifications.append(contentsOf: newNotifications)
state = .display(notifications: consolidatedNotifications, nextPageState: newNotifications.count < 15 ? .none : .hasNextPage)
} catch {
state = .error(error: error)

View file

@ -159,11 +159,10 @@ struct StatusEditorMediaView: View {
.foregroundColor(.red)
}
.alert("alert.error", isPresented: $isErrorDisplayed) {
Button("Ok", action: { })
Button("Ok", action: {})
} message: {
Text(error.error ?? "")
}
}
private var altMarker: some View {

View file

@ -1,9 +1,9 @@
import AVFoundation
import Foundation
import PhotosUI
import SwiftUI
import UIKit
import UniformTypeIdentifiers
import AVFoundation
@MainActor
enum StatusEditorUTTypeSupported: String, CaseIterable {
@ -74,7 +74,7 @@ struct MovieFileTranseferable: Transferable {
private let url: URL
var compressedVideoURL: URL? {
get async {
return await withCheckedContinuation { continuation in
await withCheckedContinuation { continuation in
let urlAsset = AVURLAsset(url: url, options: nil)
guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) else {
continuation.resume(returning: nil)
@ -84,7 +84,7 @@ struct MovieFileTranseferable: Transferable {
exportSession.outputURL = outputURL
exportSession.outputFileType = .mp4
exportSession.shouldOptimizeForNetworkUse = true
exportSession.exportAsynchronously { () -> Void in
exportSession.exportAsynchronously { () in
continuation.resume(returning: outputURL)
}
}

View file

@ -56,6 +56,7 @@ public class StatusEditorViewModel: ObservableObject {
inflateSelectedMedias()
}
}
@Published var isMediasLoading: Bool = false
@Published var mediasImages: [StatusEditorMediaContainer] = []

View file

@ -225,13 +225,12 @@ public struct StatusRowView: View {
private func makeStatusContentView(status: AnyStatus) -> some View {
Group {
if !status.spoilerText.asRawText.isEmpty {
HStack(alignment: .top) {
Text("⚠︎")
.font(.system(.subheadline , weight:.bold))
.font(.system(.subheadline, weight: .bold))
.foregroundColor(.secondary)
EmojiTextApp(status.spoilerText, emojis: status.emojis, language: status.language)
.font(.system(.subheadline , weight:.bold))
.font(.system(.subheadline, weight: .bold))
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
Spacer()
@ -247,7 +246,7 @@ public struct StatusRowView: View {
.accessibility(label: viewModel.displaySpoiler ? Text("status.show-more") : Text("status.show-less"))
.accessibilityHidden(true)
}
.onTapGesture { // make whole row tapable to make up for smaller button size
.onTapGesture { // make whole row tapable to make up for smaller button size
withAnimation {
viewModel.displaySpoiler.toggle()
}
@ -376,7 +375,8 @@ public struct StatusRowView: View {
!viewModel.isCompact,
theme.statusDisplayStyle == .large,
status.content.statusesURLs.isEmpty,
status.mediaAttachments.isEmpty {
status.mediaAttachments.isEmpty
{
StatusCardView(card: card)
}
}