Swiftformat

This commit is contained in:
Thomas Ricouard 2023-01-17 11:36:01 +01:00
parent 96344e2815
commit 7f6419ebae
161 changed files with 1777 additions and 1746 deletions

1
.swiftformat Normal file
View file

@ -0,0 +1 @@
--indent 2

View file

@ -644,7 +644,7 @@
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 730;
DEVELOPMENT_TEAM = Z6P74P6T99; DEVELOPMENT_TEAM = Z6P74P6T99;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = IceCubesShareExtension/Info.plist; INFOPLIST_FILE = IceCubesShareExtension/Info.plist;
@ -674,7 +674,7 @@
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 730;
DEVELOPMENT_TEAM = Z6P74P6T99; DEVELOPMENT_TEAM = Z6P74P6T99;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = IceCubesShareExtension/Info.plist; INFOPLIST_FILE = IceCubesShareExtension/Info.plist;

View file

@ -1,16 +1,16 @@
import Account
import AppAccount
import DesignSystem
import Env
import Lists
import Status
import SwiftUI import SwiftUI
import Timeline import Timeline
import Account
import Env
import Status
import DesignSystem
import Lists
import AppAccount
@MainActor @MainActor
extension View { extension View {
func withAppRouteur() -> some View { func withAppRouteur() -> some View {
self.navigationDestination(for: RouteurDestinations.self) { destination in navigationDestination(for: RouteurDestinations.self) { destination in
switch destination { switch destination {
case let .accountDetail(id): case let .accountDetail(id):
AccountDetailView(accountId: id) AccountDetailView(accountId: id)
@ -37,7 +37,7 @@ extension View {
} }
func withSheetDestinations(sheetDestinations: Binding<SheetDestinations?>) -> some View { func withSheetDestinations(sheetDestinations: Binding<SheetDestinations?>) -> some View {
self.sheet(item: sheetDestinations) { destination in sheet(item: sheetDestinations) { destination in
switch destination { switch destination {
case let .replyToStatusEditor(status): case let .replyToStatusEditor(status):
StatusEditorView(mode: .replyTo(status: status)) StatusEditorView(mode: .replyTo(status: status))
@ -71,8 +71,7 @@ extension View {
} }
func withEnvironments() -> some View { func withEnvironments() -> some View {
self environmentObject(CurrentAccount.shared)
.environmentObject(CurrentAccount.shared)
.environmentObject(UserPreferences.shared) .environmentObject(UserPreferences.shared)
.environmentObject(CurrentInstance.shared) .environmentObject(CurrentInstance.shared)
.environmentObject(Theme.shared) .environmentObject(Theme.shared)

View file

@ -1,13 +1,13 @@
import SwiftUI
import AVFoundation
import Timeline
import Network
import KeychainSwift
import Env
import DesignSystem
import RevenueCat
import AppAccount
import Account import Account
import AppAccount
import AVFoundation
import DesignSystem
import Env
import KeychainSwift
import Network
import RevenueCat
import SwiftUI
import Timeline
@main @main
struct IceCubesApp: App { struct IceCubesApp: App {
@ -168,14 +168,16 @@ struct IceCubesApp: App {
} }
class AppDelegate: NSObject, UIApplicationDelegate { class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, func application(_: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
{
try? AVAudioSession.sharedInstance().setCategory(.ambient, options: .mixWithOthers) try? AVAudioSession.sharedInstance().setCategory(.ambient, options: .mixWithOthers)
return true return true
} }
func application(_ application: UIApplication, func application(_: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
{
PushNotificationsService.shared.pushToken = deviceToken PushNotificationsService.shared.pushToken = deviceToken
Task { Task {
await PushNotificationsService.shared.fetchSubscriptions(accounts: AppAccountsManager.shared.pushAccounts) await PushNotificationsService.shared.fetchSubscriptions(accounts: AppAccountsManager.shared.pushAccounts)
@ -183,6 +185,5 @@ class AppDelegate: NSObject, UIApplicationDelegate {
} }
} }
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError _: Error) {}
}
} }

View file

@ -1,6 +1,6 @@
import UIKit
import SwiftUI
import QuickLook import QuickLook
import SwiftUI
import UIKit
extension URL: Identifiable { extension URL: Identifiable {
public var id: String { public var id: String {
@ -21,7 +21,8 @@ struct QuickLookPreview: UIViewControllerRepresentable {
} }
func updateUIViewController( func updateUIViewController(
_ uiViewController: UINavigationController, context: Context) {} _: UINavigationController, context _: Context
) {}
func makeCoordinator() -> Coordinator { func makeCoordinator() -> Coordinator {
return Coordinator(parent: self) return Coordinator(parent: self)
@ -34,15 +35,15 @@ struct QuickLookPreview: UIViewControllerRepresentable {
self.parent = parent self.parent = parent
} }
func numberOfPreviewItems(in controller: QLPreviewController) -> Int { func numberOfPreviewItems(in _: QLPreviewController) -> Int {
return parent.urls.count return parent.urls.count
} }
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { func previewController(_: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return parent.urls[index] as QLPreviewItem return parent.urls[index] as QLPreviewItem
} }
func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode { func previewController(_: QLPreviewController, editingModeFor _: QLPreviewItem) -> QLPreviewItemEditingMode {
.createCopy .createCopy
} }
} }

View file

@ -1,7 +1,7 @@
import SwiftUI
import SafariServices
import Env
import DesignSystem import DesignSystem
import Env
import SafariServices
import SwiftUI
extension View { extension View {
func withSafariRouteur() -> some View { func withSafariRouteur() -> some View {
@ -45,14 +45,14 @@ private struct SafariRouteur: ViewModifier {
struct SafariPresenter: UIViewRepresentable { struct SafariPresenter: UIViewRepresentable {
var safari: SFSafariViewController? var safari: SFSafariViewController?
func makeUIView(context: Context) -> UIView { func makeUIView(context _: Context) -> UIView {
let view = UIView(frame: .zero) let view = UIView(frame: .zero)
view.isHidden = true view.isHidden = true
view.isUserInteractionEnabled = false view.isUserInteractionEnabled = false
return view return view
} }
func updateUIView(_ uiView: UIView, context: Context) { func updateUIView(_ uiView: UIView, context _: Context) {
guard let safari = safari, let viewController = uiView.findTopViewController() else { return } guard let safari = safari, let viewController = uiView.findTopViewController() else { return }
viewController.present(safari, animated: true) viewController.present(safari, animated: true)
} }
@ -61,9 +61,9 @@ private struct SafariRouteur: ViewModifier {
private extension UIView { private extension UIView {
func findTopViewController() -> UIViewController? { func findTopViewController() -> UIViewController? {
if let nextResponder = self.next as? UIViewController { if let nextResponder = next as? UIViewController {
return nextResponder.topViewController() return nextResponder.topViewController()
} else if let nextResponder = self.next as? UIView { } else if let nextResponder = next as? UIView {
return nextResponder.findTopViewController() return nextResponder.findTopViewController()
} else { } else {
return nil return nil
@ -77,7 +77,7 @@ private extension UIViewController {
return nvc.visibleViewController?.topViewController() return nvc.visibleViewController?.topViewController()
} else if let tbc = self as? UITabBarController, let selected = tbc.selectedViewController { } else if let tbc = self as? UITabBarController, let selected = tbc.selectedViewController {
return selected.topViewController() return selected.topViewController()
} else if let presented = self.presentedViewController { } else if let presented = presentedViewController {
return presented.topViewController() return presented.topViewController()
} }
return self return self

View file

@ -1,8 +1,8 @@
import SwiftUI
import Env
import Account import Account
import DesignSystem
import AppAccount import AppAccount
import DesignSystem
import Env
import SwiftUI
struct SideBarView<Content: View>: View { struct SideBarView<Content: View>: View {
@EnvironmentObject private var currentAccount: CurrentAccount @EnvironmentObject private var currentAccount: CurrentAccount

View file

@ -1,11 +1,10 @@
import SwiftUI
import Env
import Models
import Shimmer
import Explore
import Env
import Network
import AppAccount import AppAccount
import Env
import Explore
import Models
import Network
import Shimmer
import SwiftUI
struct ExploreTab: View { struct ExploreTab: View {
@EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var preferences: UserPreferences

View file

@ -1,12 +1,11 @@
import SwiftUI
import Env
import Network
import Account import Account
import Models import AppAccount
import Shimmer
import Conversations import Conversations
import Env import Env
import AppAccount import Models
import Network
import Shimmer
import SwiftUI
struct MessagesTab: View { struct MessagesTab: View {
@EnvironmentObject private var watcher: StreamWatcher @EnvironmentObject private var watcher: StreamWatcher

View file

@ -1,9 +1,9 @@
import SwiftUI import AppAccount
import Timeline
import Env import Env
import Network import Network
import Notifications import Notifications
import AppAccount import SwiftUI
import Timeline
struct NotificationsTab: View { struct NotificationsTab: View {
@EnvironmentObject private var client: Client @EnvironmentObject private var client: Client

View file

@ -1,12 +1,12 @@
import SwiftUI
import Network
import Models
import Env
import DesignSystem
import NukeUI
import Shimmer
import AppAccount import AppAccount
import Combine import Combine
import DesignSystem
import Env
import Models
import Network
import NukeUI
import Shimmer
import SwiftUI
struct AddAccountView: View { struct AddAccountView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@ -133,7 +133,7 @@ struct AddAccountView: View {
if instances.isEmpty { if instances.isEmpty {
placeholderRow placeholderRow
} 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 self.instanceName = instance.name
} label: { } label: {

View file

@ -1,7 +1,7 @@
import SwiftUI
import Models
import DesignSystem import DesignSystem
import Models
import Status import Status
import SwiftUI
struct DisplaySettingsView: View { struct DisplaySettingsView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme

View file

@ -1,5 +1,5 @@
import SwiftUI
import DesignSystem import DesignSystem
import SwiftUI
struct IconSelectorView: View { struct IconSelectorView: View {
enum Icon: Int, CaseIterable, Identifiable { enum Icon: Int, CaseIterable, Identifiable {

View file

@ -1,7 +1,7 @@
import SwiftUI
import Models
import DesignSystem import DesignSystem
import Models
import NukeUI import NukeUI
import SwiftUI
struct InstanceInfoView: View { struct InstanceInfoView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme

View file

@ -1,11 +1,11 @@
import SwiftUI
import Models
import DesignSystem
import NukeUI
import Network
import UserNotifications
import Env
import AppAccount import AppAccount
import DesignSystem
import Env
import Models
import Network
import NukeUI
import SwiftUI
import UserNotifications
struct PushNotificationsView: View { struct PushNotificationsView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme

View file

@ -1,11 +1,11 @@
import Account
import AppAccount
import DesignSystem
import Env
import Models
import Network
import SwiftUI import SwiftUI
import Timeline import Timeline
import Env
import Network
import Account
import Models
import DesignSystem
import AppAccount
struct SettingsTabs: View { struct SettingsTabs: View {
@EnvironmentObject private var pushNotifications: PushNotificationsService @EnvironmentObject private var pushNotifications: PushNotificationsService

View file

@ -1,8 +1,8 @@
import SwiftUI
import Env
import DesignSystem import DesignSystem
import Env
import RevenueCat import RevenueCat
import Shimmer import Shimmer
import SwiftUI
struct SupportAppView: View { struct SupportAppView: View {
enum Tips: String, CaseIterable { enum Tips: String, CaseIterable {
@ -133,7 +133,7 @@ struct SupportAppView: View {
}) })
.onAppear { .onAppear {
loadingProducts = true loadingProducts = true
Purchases.shared.getProducts(Tips.allCases.map{ $0.productId }) { products in Purchases.shared.getProducts(Tips.allCases.map { $0.productId }) { products in
self.products = products.sorted(by: { $0.price < $1.price }) self.products = products.sorted(by: { $0.price < $1.price })
withAnimation { withAnimation {
loadingProducts = false loadingProducts = false

View file

@ -1,7 +1,7 @@
import Foundation
import Status
import Account import Account
import Explore import Explore
import Foundation
import Status
import SwiftUI import SwiftUI
enum Tab: Int, Identifiable, Hashable { enum Tab: Int, Identifiable, Hashable {
@ -96,4 +96,3 @@ enum Tab: Int, Identifiable, Hashable {
} }
} }
} }

View file

@ -1,11 +1,11 @@
import SwiftUI import Combine
import Network
import Models
import Env
import DesignSystem import DesignSystem
import Env
import Models
import Network
import NukeUI import NukeUI
import Shimmer import Shimmer
import Combine import SwiftUI
struct AddRemoteTimelineView: View { struct AddRemoteTimelineView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@ -83,7 +83,7 @@ struct AddRemoteTimelineView: View {
ProgressView() ProgressView()
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} 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 self.instanceName = instance.name
} label: { } label: {

View file

@ -1,11 +1,11 @@
import SwiftUI import AppAccount
import Timeline
import Env
import Network
import Combine import Combine
import DesignSystem import DesignSystem
import Env
import Models import Models
import AppAccount import Network
import SwiftUI
import Timeline
struct TimelineTab: View { struct TimelineTab: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@ -72,7 +72,6 @@ struct TimelineTab: View {
.environmentObject(routeurPath) .environmentObject(routeurPath)
} }
@ViewBuilder @ViewBuilder
private var timelineFilterButton: some View { private var timelineFilterButton: some View {
ForEach(TimelineFilter.availableTimeline(client: client), id: \.self) { timeline in ForEach(TimelineFilter.availableTimeline(client: client), id: \.self) { timeline in

View file

@ -1,13 +1,12 @@
import UserNotifications
import KeychainSwift
import Env
import CryptoKit import CryptoKit
import Env
import KeychainSwift
import Models import Models
import UIKit import UIKit
import UserNotifications
@MainActor @MainActor
class NotificationService: UNNotificationServiceExtension { class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)? var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent? var bestAttemptContent: UNMutableNotificationContent?
@ -20,20 +19,23 @@ class NotificationService: UNNotificationServiceExtension {
let auth = PushNotificationsService.shared.notificationsAuthKeyAsKey let auth = PushNotificationsService.shared.notificationsAuthKeyAsKey
guard let encodedPayload = bestAttemptContent.userInfo["m"] as? String, guard let encodedPayload = bestAttemptContent.userInfo["m"] as? String,
let payload = Data(base64Encoded: encodedPayload.URLSafeBase64ToBase64()) else { let payload = Data(base64Encoded: encodedPayload.URLSafeBase64ToBase64())
else {
contentHandler(bestAttemptContent) contentHandler(bestAttemptContent)
return return
} }
guard let encodedPublicKey = bestAttemptContent.userInfo["k"] as? String, guard let encodedPublicKey = bestAttemptContent.userInfo["k"] as? String,
let publicKeyData = Data(base64Encoded: encodedPublicKey.URLSafeBase64ToBase64()), let publicKeyData = Data(base64Encoded: encodedPublicKey.URLSafeBase64ToBase64()),
let publicKey = try? P256.KeyAgreement.PublicKey(x963Representation: publicKeyData) else { let publicKey = try? P256.KeyAgreement.PublicKey(x963Representation: publicKeyData)
else {
contentHandler(bestAttemptContent) contentHandler(bestAttemptContent)
return return
} }
guard let encodedSalt = bestAttemptContent.userInfo["s"] as? String, guard let encodedSalt = bestAttemptContent.userInfo["s"] as? String,
let salt = Data(base64Encoded: encodedSalt.URLSafeBase64ToBase64()) else { let salt = Data(base64Encoded: encodedSalt.URLSafeBase64ToBase64())
else {
contentHandler(bestAttemptContent) contentHandler(bestAttemptContent)
return return
} }
@ -43,7 +45,8 @@ class NotificationService: UNNotificationServiceExtension {
auth: auth, auth: auth,
privateKey: privateKey, privateKey: privateKey,
publicKey: publicKey), publicKey: publicKey),
let notification = try? JSONDecoder().decode(MastodonPushNotification.self, from: plaintextData) else { let notification = try? JSONDecoder().decode(MastodonPushNotification.self, from: plaintextData)
else {
contentHandler(bestAttemptContent) contentHandler(bestAttemptContent)
return return
} }
@ -52,7 +55,7 @@ class NotificationService: UNNotificationServiceExtension {
bestAttemptContent.subtitle = "" bestAttemptContent.subtitle = ""
bestAttemptContent.body = notification.body.escape() bestAttemptContent.body = notification.body.escape()
bestAttemptContent.userInfo["plaintext"] = plaintextData bestAttemptContent.userInfo["plaintext"] = plaintextData
bestAttemptContent.sound = UNNotificationSound.init(named: UNNotificationSoundName(rawValue: "glass.wav")) bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "glass.wav"))
let preferences = UserPreferences.shared let preferences = UserPreferences.shared
preferences.pushNotificationsCount += 1 preferences.pushNotificationsCount += 1
@ -60,7 +63,8 @@ class NotificationService: UNNotificationServiceExtension {
bestAttemptContent.badge = .init(integerLiteral: preferences.pushNotificationsCount) bestAttemptContent.badge = .init(integerLiteral: preferences.pushNotificationsCount)
if let urlString = notification.icon, if let urlString = notification.icon,
let url = URL(string: urlString) { let url = URL(string: urlString)
{
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("notification-attachments") let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("notification-attachments")
try? FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil) try? FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil)
let filename = url.lastPathComponent let filename = url.lastPathComponent

View file

@ -1,5 +1,5 @@
import Foundation
import CryptoKit import CryptoKit
import Foundation
extension NotificationService { extension NotificationService {
static func decrypt(payload: Data, salt: Data, auth: Data, privateKey: P256.KeyAgreement.PrivateKey, publicKey: P256.KeyAgreement.PublicKey) -> Data? { static func decrypt(payload: Data, salt: Data, auth: Data, privateKey: P256.KeyAgreement.PrivateKey, publicKey: P256.KeyAgreement.PublicKey) -> Data? {
@ -41,7 +41,7 @@ extension NotificationService {
return Data(unpadded) return Data(unpadded)
} }
static private func info(type: String, clientPublicKey: Data, serverPublicKey: Data) -> Data { private static func info(type: String, clientPublicKey: Data, serverPublicKey: Data) -> Data {
var info = Data() var info = Data()
info.append("Content-Encoding: ".data(using: .utf8)!) info.append("Content-Encoding: ".data(using: .utf8)!)
@ -62,14 +62,12 @@ extension NotificationService {
extension String { extension String {
func escape() -> String { func escape() -> String {
return self 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: "\"")
.replacingOccurrences(of: "&apos;", with: "'") .replacingOccurrences(of: "&apos;", with: "'")
.replacingOccurrences(of: "&#39;", with: "") .replacingOccurrences(of: "&#39;", with: "")
} }
func URLSafeBase64ToBase64() -> String { func URLSafeBase64ToBase64() -> String {
@ -83,4 +81,3 @@ extension String {
return base64 return base64
} }
} }

View file

@ -8,14 +8,14 @@
<dict> <dict>
<key>NSExtensionActivationRule</key> <key>NSExtensionActivationRule</key>
<dict> <dict>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key> <key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>4</integer> <integer>4</integer>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict> </dict>
</dict> </dict>
<key>NSExtensionMainStoryboard</key> <key>NSExtensionMainStoryboard</key>

View file

@ -1,11 +1,11 @@
import Account
import AppAccount
import DesignSystem
import Env
import Network
import Status
import SwiftUI import SwiftUI
import UIKit import UIKit
import Status
import DesignSystem
import Account
import Network
import Env
import AppAccount
class ShareViewController: UIViewController { class ShareViewController: UIViewController {
@IBOutlet var container: UIView! @IBOutlet var container: UIView!
@ -35,9 +35,9 @@ class ShareViewController: UIViewController {
.tint(theme.tintColor) .tint(theme.tintColor)
.preferredColorScheme(colorScheme == .light ? .light : .dark) .preferredColorScheme(colorScheme == .light ? .light : .dark)
let childView = UIHostingController(rootView: view) let childView = UIHostingController(rootView: view)
self.addChild(childView) addChild(childView)
childView.view.frame = self.container.bounds childView.view.frame = container.bounds
self.container.addSubview(childView.view) container.addSubview(childView.view)
childView.didMove(toParent: self) childView.didMove(toParent: self)
} }
} }

View file

@ -11,7 +11,8 @@ let package = Package(
products: [ products: [
.library( .library(
name: "Account", name: "Account",
targets: ["Account"]), targets: ["Account"]
),
], ],
dependencies: [ dependencies: [
.package(name: "Network", path: "../Network"), .package(name: "Network", path: "../Network"),
@ -25,9 +26,11 @@ let package = Package(
.product(name: "Network", package: "Network"), .product(name: "Network", package: "Network"),
.product(name: "Models", package: "Models"), .product(name: "Models", package: "Models"),
.product(name: "Status", package: "Status"), .product(name: "Status", package: "Status"),
]), ]
),
.testTarget( .testTarget(
name: "AccountTests", name: "AccountTests",
dependencies: ["Account"]), dependencies: ["Account"]
),
] ]
) )

View file

@ -1,10 +1,10 @@
import SwiftUI
import Models
import DesignSystem import DesignSystem
import Env
import Shimmer
import NukeUI
import EmojiText import EmojiText
import Env
import Models
import NukeUI
import Shimmer
import SwiftUI
struct AccountDetailHeaderView: View { struct AccountDetailHeaderView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@ -30,7 +30,7 @@ struct AccountDetailHeaderView: View {
} }
private var headerImageView: some View { private var headerImageView: some View {
GeometryReader { proxy in GeometryReader { _ in
ZStack(alignment: .bottomTrailing) { ZStack(alignment: .bottomTrailing) {
if reasons.contains(.placeholder) { if reasons.contains(.placeholder) {
Rectangle() Rectangle()

View file

@ -1,11 +1,11 @@
import SwiftUI import DesignSystem
import EmojiText
import Env
import Models import Models
import Network import Network
import Status
import Shimmer import Shimmer
import DesignSystem import Status
import Env import SwiftUI
import EmojiText
public struct AccountDetailView: View { public struct AccountDetailView: View {
@Environment(\.redactionReasons) private var reasons @Environment(\.redactionReasons) private var reasons
@ -94,9 +94,10 @@ public struct AccountDetailView: View {
await viewModel.fetchStatuses() await viewModel.fetchStatuses()
} }
} }
.onChange(of: watcher.latestEvent?.id) { id in .onChange(of: watcher.latestEvent?.id) { _ in
if let latestEvent = watcher.latestEvent, if let latestEvent = watcher.latestEvent,
viewModel.accountId == currentAccount.account?.id { viewModel.accountId == currentAccount.account?.id
{
viewModel.handleEvent(event: latestEvent, currentAccount: currentAccount) viewModel.handleEvent(event: latestEvent, currentAccount: currentAccount)
} }
} }
@ -396,4 +397,3 @@ struct AccountDetailView_Previews: PreviewProvider {
AccountDetailView(account: .placeholder()) AccountDetailView(account: .placeholder())
} }
} }

View file

@ -1,8 +1,8 @@
import SwiftUI
import Network
import Models
import Status
import Env import Env
import Models
import Network
import Status
import SwiftUI
@MainActor @MainActor
class AccountDetailViewModel: ObservableObject, StatusesFetcher { class AccountDetailViewModel: ObservableObject, StatusesFetcher {
@ -57,6 +57,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
} }
} }
} }
@Published var statusesState: StatusesState = .loading @Published var statusesState: StatusesState = .loading
@Published var relationship: Relationshionship? @Published var relationship: Relationshionship?
@ -90,14 +91,14 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
/// When coming from a URL like a mention tap in a status. /// When coming from a URL like a mention tap in a status.
init(accountId: String) { init(accountId: String) {
self.accountId = accountId self.accountId = accountId
self.isCurrentUser = false isCurrentUser = false
} }
/// When the account is already fetched by the parent caller. /// When the account is already fetched by the parent caller.
init(account: Account) { init(account: Account) {
self.accountId = account.id accountId = account.id
self.account = account self.account = account
self.accountState = .data(account: account) accountState = .data(account: account)
} }
struct AccountData { struct AccountData {

View file

@ -1,9 +1,9 @@
import SwiftUI import DesignSystem
import EmojiText
import Env
import Models import Models
import Network import Network
import DesignSystem import SwiftUI
import Env
import EmojiText
@MainActor @MainActor
public class AccountsListRowViewModel: ObservableObject { public class AccountsListRowViewModel: ObservableObject {

View file

@ -1,9 +1,9 @@
import SwiftUI
import Network
import Models
import Env
import Shimmer
import DesignSystem import DesignSystem
import Env
import Models
import Network
import Shimmer
import SwiftUI
public struct AccountsListView: View { public struct AccountsListView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@ -19,7 +19,7 @@ public struct AccountsListView: View {
List { List {
switch viewModel.state { switch viewModel.state {
case .loading: case .loading:
ForEach(Account.placeholders()) { account in ForEach(Account.placeholders()) { _ in
AccountsListRow(viewModel: .init(account: .placeholder(), relationShip: .placeholder())) AccountsListRow(viewModel: .init(account: .placeholder(), relationShip: .placeholder()))
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.shimmering() .shimmering()
@ -63,7 +63,7 @@ public struct AccountsListView: View {
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.task { .task {
viewModel.client = client viewModel.client = client
guard !didAppear else { return} guard !didAppear else { return }
didAppear = true didAppear = true
await viewModel.fetch() await viewModel.fetch()
} }

View file

@ -1,6 +1,6 @@
import SwiftUI
import Models import Models
import Network import Network
import SwiftUI
public enum AccountsListMode { public enum AccountsListMode {
case following(accountId: String), followers(accountId: String) case following(accountId: String), followers(accountId: String)
@ -30,6 +30,7 @@ class AccountsListViewModel: ObservableObject {
public enum PagingState { public enum PagingState {
case hasNextPage, loadingNextPage, none case hasNextPage, loadingNextPage, none
} }
case loading case loading
case display(accounts: [Account], case display(accounts: [Account],
relationships: [Relationshionship], relationships: [Relationshionship],
@ -69,11 +70,11 @@ 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 { $0.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)
} catch { } } catch {}
} }
func fetchNextPage() async { func fetchNextPage() async {
@ -98,7 +99,7 @@ class AccountsListViewModel: ObservableObject {
} }
accounts.append(contentsOf: newAccounts) accounts.append(contentsOf: newAccounts)
let newRelationships: [Relationshionship] = let newRelationships: [Relationshionship] =
try await client.get(endpoint: Accounts.relationships(ids: newAccounts.map{ $0.id })) try await client.get(endpoint: Accounts.relationships(ids: newAccounts.map { $0.id }))
relationships.append(contentsOf: newRelationships) relationships.append(contentsOf: newRelationships)
self.nextPageId = link?.maxId self.nextPageId = link?.maxId

View file

@ -1,7 +1,7 @@
import SwiftUI import DesignSystem
import Models import Models
import Network import Network
import DesignSystem import SwiftUI
struct EditAccountView: View { struct EditAccountView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@ -31,7 +31,7 @@ struct EditAccountView: View {
.alert("Error while saving your profile", .alert("Error while saving your profile",
isPresented: $viewModel.saveError, isPresented: $viewModel.saveError,
actions: { actions: {
Button("Ok", action: { }) Button("Ok", action: {})
}, message: { Text("Error while saving your profile, please try again.") }) }, message: { Text("Error while saving your profile, please try again.") })
.task { .task {
viewModel.client = client viewModel.client = client

View file

@ -1,6 +1,6 @@
import SwiftUI
import Models import Models
import Network import Network
import SwiftUI
@MainActor @MainActor
class EditAccountViewModel: ObservableObject { class EditAccountViewModel: ObservableObject {
@ -18,7 +18,7 @@ class EditAccountViewModel: ObservableObject {
@Published var isSaving: Bool = false @Published var isSaving: Bool = false
@Published var saveError: Bool = false @Published var saveError: Bool = false
init() { } init() {}
func fetchAccount() async { func fetchAccount() async {
guard let client else { return } guard let client else { return }
@ -34,7 +34,7 @@ class EditAccountViewModel: ObservableObject {
withAnimation { withAnimation {
isLoading = false isLoading = false
} }
} catch { } } catch {}
} }
func save() async { func save() async {
@ -57,5 +57,4 @@ class EditAccountViewModel: ObservableObject {
saveError = true saveError = true
} }
} }
} }

View file

@ -1,7 +1,7 @@
import Foundation import Foundation
import SwiftUI
import Models import Models
import Network import Network
import SwiftUI
@MainActor @MainActor
public class FollowButtonViewModel: ObservableObject { public class FollowButtonViewModel: ObservableObject {
@ -9,8 +9,8 @@ public class FollowButtonViewModel: ObservableObject {
public let accountId: String public let accountId: String
public let shouldDisplayNotify: Bool public let shouldDisplayNotify: Bool
@Published private(set) public var relationship: Relationshionship @Published public private(set) var relationship: Relationshionship
@Published private(set) public var isUpdating: Bool = false @Published public private(set) var isUpdating: Bool = false
public init(accountId: String, relationship: Relationshionship, shouldDisplayNotify: Bool) { public init(accountId: String, relationship: Relationshionship, shouldDisplayNotify: Bool) {
self.accountId = accountId self.accountId = accountId

View file

@ -1,5 +1,5 @@
import XCTest
@testable import Account @testable import Account
import XCTest
final class AccountTests: XCTestCase { final class AccountTests: XCTestCase {
func testExample() throws { func testExample() throws {

View file

@ -11,7 +11,8 @@ let package = Package(
products: [ products: [
.library( .library(
name: "AppAccount", name: "AppAccount",
targets: ["AppAccount"]), targets: ["AppAccount"]
),
], ],
dependencies: [ dependencies: [
.package(name: "Network", path: "../Network"), .package(name: "Network", path: "../Network"),
@ -27,6 +28,7 @@ let package = Package(
.product(name: "Models", package: "Models"), .product(name: "Models", package: "Models"),
.product(name: "Env", package: "Env"), .product(name: "Env", package: "Env"),
.product(name: "DesignSystem", package: "DesignSystem"), .product(name: "DesignSystem", package: "DesignSystem"),
]) ]
),
] ]
) )

View file

@ -1,8 +1,8 @@
import SwiftUI import CryptoKit
import Network
import KeychainSwift import KeychainSwift
import Models import Models
import CryptoKit import Network
import SwiftUI
public struct AppAccount: Codable, Identifiable { public struct AppAccount: Codable, Identifiable {
public let server: String public let server: String

View file

@ -1,7 +1,7 @@
import SwiftUI
import DesignSystem import DesignSystem
import Env
import EmojiText import EmojiText
import Env
import SwiftUI
public struct AppAccountView: View { public struct AppAccountView: View {
@EnvironmentObject private var routeurPath: RouterPath @EnvironmentObject private var routeurPath: RouterPath
@ -43,7 +43,8 @@ public struct AppAccountView: View {
} }
.onTapGesture { .onTapGesture {
if appAccounts.currentAccount.id == viewModel.appAccount.id, if appAccounts.currentAccount.id == viewModel.appAccount.id,
let account = viewModel.account { let account = viewModel.account
{
routeurPath.navigate(to: .accountDetailWithAccount(account: account)) routeurPath.navigate(to: .accountDetailWithAccount(account: account))
} else { } else {
appAccounts.currentAccount = viewModel.appAccount appAccounts.currentAccount = viewModel.appAccount

View file

@ -1,6 +1,6 @@
import SwiftUI
import Models import Models
import Network import Network
import SwiftUI
@MainActor @MainActor
public class AppAccountViewModel: ObservableObject { public class AppAccountViewModel: ObservableObject {
@ -15,7 +15,7 @@ public class AppAccountViewModel: ObservableObject {
public init(appAccount: AppAccount) { public init(appAccount: AppAccount) {
self.appAccount = appAccount self.appAccount = appAccount
self.client = .init(server: appAccount.server, oauthToken: appAccount.oauthToken) client = .init(server: appAccount.server, oauthToken: appAccount.oauthToken)
} }
func fetchAccount() async { func fetchAccount() async {

View file

@ -1,12 +1,12 @@
import SwiftUI
import Network
import Env import Env
import Models import Models
import Network
import SwiftUI
@MainActor @MainActor
public class AppAccountsManager: ObservableObject { public class AppAccountsManager: ObservableObject {
@AppStorage("latestCurrentAccountKey", store: UserPreferences.sharedDefault) @AppStorage("latestCurrentAccountKey", store: UserPreferences.sharedDefault)
static public var latestCurrentAccountKey: String = "" public static var latestCurrentAccountKey: String = ""
@Published public var currentAccount: AppAccount { @Published public var currentAccount: AppAccount {
didSet { didSet {
@ -15,12 +15,13 @@ public class AppAccountsManager: ObservableObject {
oauthToken: currentAccount.oauthToken) oauthToken: currentAccount.oauthToken)
} }
} }
@Published public var availableAccounts: [AppAccount] @Published public var availableAccounts: [AppAccount]
@Published public var currentClient: Client @Published public var currentClient: Client
public var pushAccounts: [PushNotificationsService.PushAccounts] { public var pushAccounts: [PushNotificationsService.PushAccounts] {
availableAccounts.filter{ $0.oauthToken != nil} availableAccounts.filter { $0.oauthToken != nil }
.map{ .init(server: $0.server, token: $0.oauthToken!) } .map { .init(server: $0.server, token: $0.oauthToken!) }
} }
public static var shared = AppAccountsManager() public static var shared = AppAccountsManager()
@ -43,7 +44,7 @@ public class AppAccountsManager: ObservableObject {
try account.save() try account.save()
availableAccounts.append(account) availableAccounts.append(account)
currentAccount = account currentAccount = account
} catch { } } catch {}
} }
public func delete(account: AppAccount) { public func delete(account: AppAccount) {

View file

@ -1,6 +1,6 @@
import SwiftUI
import Env
import DesignSystem import DesignSystem
import Env
import SwiftUI
public struct AppAccountsSelectorView: View { public struct AppAccountsSelectorView: View {
@EnvironmentObject private var currentAccount: CurrentAccount @EnvironmentObject private var currentAccount: CurrentAccount
@ -15,7 +15,8 @@ public struct AppAccountsSelectorView: View {
public init(routeurPath: RouterPath, public init(routeurPath: RouterPath,
accountCreationEnabled: Bool = true, accountCreationEnabled: Bool = true,
avatarSize: AvatarView.Size = .badge) { avatarSize: AvatarView.Size = .badge)
{
self.routeurPath = routeurPath self.routeurPath = routeurPath
self.accountCreationEnabled = accountCreationEnabled self.accountCreationEnabled = accountCreationEnabled
self.avatarSize = avatarSize self.avatarSize = avatarSize
@ -59,7 +60,8 @@ public struct AppAccountsSelectorView: View {
Section(viewModel.acct) { Section(viewModel.acct) {
Button { Button {
if let account = currentAccount.account, if let account = currentAccount.account,
viewModel.account?.id == account.id { viewModel.account?.id == account.id
{
routeurPath.navigate(to: .accountDetailWithAccount(account: account)) routeurPath.navigate(to: .accountDetailWithAccount(account: account))
} else { } else {
appAccounts.currentAccount = viewModel.appAccount appAccounts.currentAccount = viewModel.appAccount
@ -98,5 +100,4 @@ public struct AppAccountsSelectorView: View {
} }
} }
} }
} }

View file

@ -11,7 +11,8 @@ let package = Package(
products: [ products: [
.library( .library(
name: "Conversations", name: "Conversations",
targets: ["Conversations"]), targets: ["Conversations"]
),
], ],
dependencies: [ dependencies: [
.package(name: "Models", path: "../Models"), .package(name: "Models", path: "../Models"),
@ -27,7 +28,7 @@ let package = Package(
.product(name: "Network", package: "Network"), .product(name: "Network", package: "Network"),
.product(name: "Env", package: "Env"), .product(name: "Env", package: "Env"),
.product(name: "DesignSystem", package: "DesignSystem"), .product(name: "DesignSystem", package: "DesignSystem"),
]), ]
),
] ]
) )

View file

@ -1,9 +1,9 @@
import SwiftUI
import Models
import Accounts import Accounts
import DesignSystem import DesignSystem
import Env import Env
import Models
import Network import Network
import SwiftUI
struct ConversationsListRow: View { struct ConversationsListRow: View {
@EnvironmentObject private var client: Client @EnvironmentObject private var client: Client
@ -19,7 +19,7 @@ struct ConversationsListRow: View {
AvatarView(url: conversation.accounts.first!.avatar) AvatarView(url: conversation.accounts.first!.avatar)
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
HStack { HStack {
Text(conversation.accounts.map{ $0.safeDisplayName }.joined(separator: ", ")) Text(conversation.accounts.map { $0.safeDisplayName }.joined(separator: ", "))
.font(.headline) .font(.headline)
.foregroundColor(theme.labelColor) .foregroundColor(theme.labelColor)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)

View file

@ -1,9 +1,9 @@
import SwiftUI
import Network
import Models
import DesignSystem import DesignSystem
import Shimmer
import Env import Env
import Models
import Network
import Shimmer
import SwiftUI
public struct ConversationsListView: View { public struct ConversationsListView: View {
@EnvironmentObject private var routeurPath: RouterPath @EnvironmentObject private var routeurPath: RouterPath
@ -13,7 +13,7 @@ public struct ConversationsListView: View {
@StateObject private var viewModel = ConversationsListViewModel() @StateObject private var viewModel = ConversationsListViewModel()
public init() { } public init() {}
private var conversations: [Conversation] { private var conversations: [Conversation] {
if viewModel.isLoadingFirstPage { if viewModel.isLoadingFirstPage {
@ -61,7 +61,7 @@ public struct ConversationsListView: View {
.toolbar { .toolbar {
StatusEditorToolbarItem(visibility: .direct) StatusEditorToolbarItem(visibility: .direct)
} }
.onChange(of: watcher.latestEvent?.id) { id in .onChange(of: watcher.latestEvent?.id) { _ in
if let latestEvent = watcher.latestEvent { if let latestEvent = watcher.latestEvent {
viewModel.handleEvent(event: latestEvent) viewModel.handleEvent(event: latestEvent)
} }

View file

@ -10,7 +10,7 @@ class ConversationsListViewModel: ObservableObject {
@Published var conversations: [Conversation] = [] @Published var conversations: [Conversation] = []
@Published var isError: Bool = false @Published var isError: Bool = false
public init() { } public init() {}
func fetchConversations() async { func fetchConversations() async {
guard let client else { return } guard let client else { return }

View file

@ -11,14 +11,15 @@ let package = Package(
products: [ products: [
.library( .library(
name: "DesignSystem", name: "DesignSystem",
targets: ["DesignSystem"]), targets: ["DesignSystem"]
),
], ],
dependencies: [ dependencies: [
.package(name: "Models", path: "../Models"), .package(name: "Models", path: "../Models"),
.package(name: "Env", path: "../Env"), .package(name: "Env", path: "../Env"),
.package(url: "https://github.com/markiv/SwiftUI-Shimmer", exact: "1.1.0"), .package(url: "https://github.com/markiv/SwiftUI-Shimmer", exact: "1.1.0"),
.package(url: "https://github.com/kean/Nuke", from: "11.5.0"), .package(url: "https://github.com/kean/Nuke", from: "11.5.0"),
.package(url: "https://github.com/divadretlaw/EmojiText", from: "1.1.0") .package(url: "https://github.com/divadretlaw/EmojiText", from: "1.1.0"),
], ],
targets: [ targets: [
.target( .target(
@ -29,8 +30,8 @@ let package = Package(
.product(name: "Shimmer", package: "SwiftUI-Shimmer"), .product(name: "Shimmer", package: "SwiftUI-Shimmer"),
.product(name: "NukeUI", package: "Nuke"), .product(name: "NukeUI", package: "Nuke"),
.product(name: "Nuke", package: "Nuke"), .product(name: "Nuke", package: "Nuke"),
.product(name: "EmojiText", package: "EmojiText") .product(name: "EmojiText", package: "EmojiText"),
]), ]
),
] ]
) )

View file

@ -1,22 +1,22 @@
import Foundation import Foundation
import SwiftUI
import NukeUI
import Models import Models
import NukeUI
import SwiftUI
extension Account { public extension Account {
private struct Part: Identifiable { private struct Part: Identifiable {
let id = UUID().uuidString let id = UUID().uuidString
let value: Substring let value: Substring
} }
public var safeDisplayName: String { var safeDisplayName: String {
if displayName.isEmpty { if displayName.isEmpty {
return username return username
} }
return displayName return displayName
} }
public var displayNameWithoutEmojis: String { var displayNameWithoutEmojis: String {
var name = safeDisplayName var name = safeDisplayName
for emoji in emojis { for emoji in emojis {
name = name.replacingOccurrences(of: ":\(emoji.shortcode):", with: "") name = name.replacingOccurrences(of: ":\(emoji.shortcode):", with: "")

View file

@ -24,9 +24,9 @@ public enum ColorSetName: String {
public struct IceCubeDark: ColorSet { public struct IceCubeDark: ColorSet {
public var name: ColorSetName = .iceCubeDark public var name: ColorSetName = .iceCubeDark
public var scheme: ColorScheme = .dark public var scheme: ColorScheme = .dark
public var tintColor: Color = Color(red: 187/255, green: 59/255, blue: 226/255) public var tintColor: Color = .init(red: 187 / 255, green: 59 / 255, blue: 226 / 255)
public var primaryBackgroundColor: Color = Color(red: 16/255, green: 21/255, blue: 35/255) public var primaryBackgroundColor: Color = .init(red: 16 / 255, green: 21 / 255, blue: 35 / 255)
public var secondaryBackgroundColor: Color = Color(red: 30/255, green: 35/255, blue: 62/255) public var secondaryBackgroundColor: Color = .init(red: 30 / 255, green: 35 / 255, blue: 62 / 255)
public var labelColor: Color = .white public var labelColor: Color = .white
public init() {} public init() {}
@ -35,9 +35,9 @@ public struct IceCubeDark: ColorSet {
public struct IceCubeLight: ColorSet { public struct IceCubeLight: ColorSet {
public var name: ColorSetName = .iceCubeLight public var name: ColorSetName = .iceCubeLight
public var scheme: ColorScheme = .light public var scheme: ColorScheme = .light
public var tintColor: Color = Color(red: 187/255, green: 59/255, blue: 226/255) public var tintColor: Color = .init(red: 187 / 255, green: 59 / 255, blue: 226 / 255)
public var primaryBackgroundColor: Color = .white public var primaryBackgroundColor: Color = .white
public var secondaryBackgroundColor: Color = Color(hex:0xF0F1F2) public var secondaryBackgroundColor: Color = .init(hex: 0xF0F1F2)
public var labelColor: Color = .black public var labelColor: Color = .black
public init() {} public init() {}
@ -46,9 +46,9 @@ public struct IceCubeLight: ColorSet {
public struct DesertDark: ColorSet { public struct DesertDark: ColorSet {
public var name: ColorSetName = .desertDark public var name: ColorSetName = .desertDark
public var scheme: ColorScheme = .dark public var scheme: ColorScheme = .dark
public var tintColor: Color = Color(hex: 0xdf915e) public var tintColor: Color = .init(hex: 0xDF915E)
public var primaryBackgroundColor: Color = Color(hex: 0x433744) public var primaryBackgroundColor: Color = .init(hex: 0x433744)
public var secondaryBackgroundColor: Color = Color(hex:0x654868) public var secondaryBackgroundColor: Color = .init(hex: 0x654868)
public var labelColor: Color = .white public var labelColor: Color = .white
public init() {} public init() {}
@ -57,9 +57,9 @@ public struct DesertDark: ColorSet {
public struct DesertLight: ColorSet { public struct DesertLight: ColorSet {
public var name: ColorSetName = .desertLight public var name: ColorSetName = .desertLight
public var scheme: ColorScheme = .light public var scheme: ColorScheme = .light
public var tintColor: Color = Color(hex: 0xdf915e) public var tintColor: Color = .init(hex: 0xDF915E)
public var primaryBackgroundColor: Color = Color(hex: 0xfcf2eb) public var primaryBackgroundColor: Color = .init(hex: 0xFCF2EB)
public var secondaryBackgroundColor: Color = Color(hex:0xeeede7) public var secondaryBackgroundColor: Color = .init(hex: 0xEEEDE7)
public var labelColor: Color = .black public var labelColor: Color = .black
public init() {} public init() {}
@ -68,9 +68,9 @@ public struct DesertLight: ColorSet {
public struct NemesisDark: ColorSet { public struct NemesisDark: ColorSet {
public var name: ColorSetName = .nemesisDark public var name: ColorSetName = .nemesisDark
public var scheme: ColorScheme = .dark public var scheme: ColorScheme = .dark
public var tintColor: Color = Color(hex: 0x17a2f2) public var tintColor: Color = .init(hex: 0x17A2F2)
public var primaryBackgroundColor: Color = Color(hex: 0x000000) public var primaryBackgroundColor: Color = .init(hex: 0x000000)
public var secondaryBackgroundColor: Color = Color(hex:0x151e2b) public var secondaryBackgroundColor: Color = .init(hex: 0x151E2B)
public var labelColor: Color = .white public var labelColor: Color = .white
public init() {} public init() {}
@ -79,13 +79,10 @@ public struct NemesisDark: ColorSet {
public struct NemesisLight: ColorSet { public struct NemesisLight: ColorSet {
public var name: ColorSetName = .nemesisLight public var name: ColorSetName = .nemesisLight
public var scheme: ColorScheme = .light public var scheme: ColorScheme = .light
public var tintColor: Color = Color(hex: 0x17a2f2) public var tintColor: Color = .init(hex: 0x17A2F2)
public var primaryBackgroundColor: Color = Color(hex: 0xffffff) public var primaryBackgroundColor: Color = .init(hex: 0xFFFFFF)
public var secondaryBackgroundColor: Color = Color(hex:0xe8ecef) public var secondaryBackgroundColor: Color = .init(hex: 0xE8ECEF)
public var labelColor: Color = .black public var labelColor: Color = .black
public init() {} public init() {}
} }

View file

@ -1,8 +1,8 @@
import Foundation import Foundation
extension CGFloat { public extension CGFloat {
public static let layoutPadding: CGFloat = 20 static let layoutPadding: CGFloat = 20
public static let dividerPadding: CGFloat = 2 static let dividerPadding: CGFloat = 2
public static let statusColumnsSpacing: CGFloat = 8 static let statusColumnsSpacing: CGFloat = 8
public static let maxColumnWidth: CGFloat = 650 static let maxColumnWidth: CGFloat = 650
} }

View file

@ -1,19 +1,19 @@
import SwiftUI import SwiftUI
extension Color { public extension Color {
public static var brand: Color { static var brand: Color {
Color(red: 187/255, green: 59/255, blue: 226/255) Color(red: 187 / 255, green: 59 / 255, blue: 226 / 255)
} }
public static var primaryBackground: Color { static var primaryBackground: Color {
Color(red: 16/255, green: 21/255, blue: 35/255) Color(red: 16 / 255, green: 21 / 255, blue: 35 / 255)
} }
public static var secondaryBackground: Color { static var secondaryBackground: Color {
Color(red: 30/255, green: 35/255, blue: 62/255) Color(red: 30 / 255, green: 35 / 255, blue: 62 / 255)
} }
public static var label: Color { static var label: Color {
Color("label", bundle: .module) Color("label", bundle: .module)
} }
} }
@ -43,10 +43,9 @@ extension Color: RawRepresentable {
extension Color { extension Color {
init(hex: Int, opacity: Double = 1.0) { init(hex: Int, opacity: Double = 1.0) {
let red = Double((hex & 0xff0000) >> 16) / 255.0 let red = Double((hex & 0xFF0000) >> 16) / 255.0
let green = Double((hex & 0xff00) >> 8) / 255.0 let green = Double((hex & 0xFF00) >> 8) / 255.0
let blue = Double((hex & 0xff) >> 0) / 255.0 let blue = Double((hex & 0xFF) >> 0) / 255.0
self.init(.sRGB, red: red, green: green, blue: blue, opacity: opacity) self.init(.sRGB, red: red, green: green, blue: blue, opacity: opacity)
} }
} }

View file

@ -127,17 +127,17 @@ public class Theme: ObservableObject {
DesertDark(), DesertDark(),
DesertLight(), DesertLight(),
NemesisDark(), NemesisDark(),
NemesisLight() NemesisLight(),
] ]
} }
public func setColor(withName name: ColorSetName) { public func setColor(withName name: ColorSetName) {
let colorSet = Theme.allColorSet.filter { $0.name == name }.first ?? IceCubeDark() let colorSet = Theme.allColorSet.filter { $0.name == name }.first ?? IceCubeDark()
self.selectedScheme = colorSet.scheme selectedScheme = colorSet.scheme
self.tintColor = colorSet.tintColor tintColor = colorSet.tintColor
self.primaryBackgroundColor = colorSet.primaryBackgroundColor primaryBackgroundColor = colorSet.primaryBackgroundColor
self.secondaryBackgroundColor = colorSet.secondaryBackgroundColor secondaryBackgroundColor = colorSet.secondaryBackgroundColor
self.labelColor = colorSet.labelColor labelColor = colorSet.labelColor
self.storedSet = name storedSet = name
} }
} }

View file

@ -1,6 +1,6 @@
import SwiftUI import SwiftUI
#if canImport(UIKit) #if canImport(UIKit)
import UIKit import UIKit
#endif #endif
public extension View { public extension View {

View file

@ -1,6 +1,6 @@
import SwiftUI
import Shimmer
import NukeUI import NukeUI
import Shimmer
import SwiftUI
public struct AvatarView: View { public struct AvatarView: View {
@Environment(\.redactionReasons) private var reasons @Environment(\.redactionReasons) private var reasons

View file

@ -1,7 +1,7 @@
import Foundation
import EmojiText import EmojiText
import Models import Foundation
import HTML2Markdown import HTML2Markdown
import Models
import SwiftUI import SwiftUI
public struct EmojiTextApp: View { public struct EmojiTextApp: View {

View file

@ -4,7 +4,7 @@ public struct ErrorView: View {
public let title: String public let title: String
public let message: String public let message: String
public let buttonTitle: String public let buttonTitle: String
public let onButtonPress: (() -> Void) public let onButtonPress: () -> Void
public init(title: String, message: String, buttonTitle: String, onButtonPress: @escaping (() -> Void)) { public init(title: String, message: String, buttonTitle: String, onButtonPress: @escaping (() -> Void)) {
self.title = title self.title = title

View file

@ -40,5 +40,5 @@ public struct ScrollViewOffsetReader<Content: View>: View {
private struct OffsetPreferenceKey: PreferenceKey { private struct OffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = .zero static var defaultValue: CGFloat = .zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {} static func reduce(value _: inout CGFloat, nextValue _: () -> CGFloat) {}
} }

View file

@ -1,10 +1,10 @@
import SwiftUI
import Env import Env
import Models import Models
import SwiftUI
@MainActor @MainActor
extension View { public extension View {
public func statusEditorToolbarItem(routeurPath: RouterPath, visibility: Models.Visibility) -> some ToolbarContent { func statusEditorToolbarItem(routeurPath: RouterPath, visibility: Models.Visibility) -> some ToolbarContent {
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
Button { Button {
routeurPath.presentedSheet = .newStatusEditor(visibility: visibility) routeurPath.presentedSheet = .newStatusEditor(visibility: visibility)

View file

@ -1,6 +1,6 @@
import Env
import Models import Models
import SwiftUI import SwiftUI
import Env
public struct TagRowView: View { public struct TagRowView: View {
@EnvironmentObject private var routeurPath: RouterPath @EnvironmentObject private var routeurPath: RouterPath

View file

@ -1,5 +1,5 @@
import SwiftUI
import Combine import Combine
import SwiftUI
public struct ThemePreviewView: View { public struct ThemePreviewView: View {
private let gutterSpace: Double = 8 private let gutterSpace: Double = 8
@ -10,15 +10,15 @@ public struct ThemePreviewView: View {
public var body: some View { public var body: some View {
ScrollView { ScrollView {
HStack (spacing: gutterSpace) { HStack(spacing: gutterSpace) {
ThemeBoxView(color: IceCubeDark()) ThemeBoxView(color: IceCubeDark())
ThemeBoxView(color: IceCubeLight()) ThemeBoxView(color: IceCubeLight())
} }
HStack (spacing: gutterSpace) { HStack(spacing: gutterSpace) {
ThemeBoxView(color: DesertDark()) ThemeBoxView(color: DesertDark())
ThemeBoxView(color: DesertLight()) ThemeBoxView(color: DesertLight())
} }
HStack (spacing: gutterSpace) { HStack(spacing: gutterSpace) {
ThemeBoxView(color: NemesisDark()) ThemeBoxView(color: NemesisDark())
ThemeBoxView(color: NemesisLight()) ThemeBoxView(color: NemesisLight())
} }
@ -31,7 +31,6 @@ public struct ThemePreviewView: View {
} }
struct ThemeBoxView: View { struct ThemeBoxView: View {
@EnvironmentObject var theme: Theme @EnvironmentObject var theme: Theme
private let gutterSpace = 8.0 private let gutterSpace = 8.0
@State private var isSelected = false @State private var isSelected = false
@ -46,7 +45,7 @@ struct ThemeBoxView: View {
.cornerRadius(4) .cornerRadius(4)
.shadow(radius: 2, x: 2, y: 4) .shadow(radius: 2, x: 2, y: 4)
VStack (spacing: gutterSpace) { VStack(spacing: gutterSpace) {
Text(color.name.rawValue) Text(color.name.rawValue)
.foregroundColor(color.tintColor) .foregroundColor(color.tintColor)
.font(.system(size: 20)) .font(.system(size: 20))
@ -95,4 +94,3 @@ struct ThemeBoxView: View {
} }
} }
} }

View file

@ -11,11 +11,12 @@ let package = Package(
products: [ products: [
.library( .library(
name: "Env", name: "Env",
targets: ["Env"]), targets: ["Env"]
),
], ],
dependencies: [ dependencies: [
.package(name: "Models", path: "../Models"), .package(name: "Models", path: "../Models"),
.package(name: "Network", path: "../Network") .package(name: "Network", path: "../Network"),
], ],
targets: [ targets: [
.target( .target(
@ -23,6 +24,7 @@ let package = Package(
dependencies: [ dependencies: [
.product(name: "Models", package: "Models"), .product(name: "Models", package: "Models"),
.product(name: "Network", package: "Network"), .product(name: "Network", package: "Network"),
]), ]
),
] ]
) )

View file

@ -10,9 +10,9 @@ public class CurrentAccount: ObservableObject {
private var client: Client? private var client: Client?
static public let shared = CurrentAccount() public static let shared = CurrentAccount()
private init() { } private init() {}
public func setClient(client: Client) { public func setClient(client: Client) {
self.client = client self.client = client
@ -36,7 +36,7 @@ public class CurrentAccount: ObservableObject {
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)
} catch { } } catch {}
} }
public func fetchCurrentAccount() async { public func fetchCurrentAccount() async {
@ -70,7 +70,7 @@ public class CurrentAccount: ObservableObject {
do { do {
let list: Models.List = try await client.post(endpoint: Lists.createList(title: title)) let list: Models.List = try await client.post(endpoint: Lists.createList(title: title))
lists.append(list) lists.append(list)
} catch { } } catch {}
} }
public func deleteList(list: Models.List) async { public func deleteList(list: Models.List) async {
@ -97,7 +97,7 @@ public class CurrentAccount: ObservableObject {
guard let client else { return nil } guard let client else { return nil }
do { do {
let tag: Tag = try await client.post(endpoint: Tags.unfollow(id: id)) let tag: Tag = try await client.post(endpoint: Tags.unfollow(id: id))
tags.removeAll{ $0.id == tag.id } tags.removeAll { $0.id == tag.id }
return tag return tag
} catch { } catch {
return nil return nil

View file

@ -8,9 +8,9 @@ public class CurrentInstance: ObservableObject {
private var client: Client? private var client: Client?
static public let shared = CurrentInstance() public static let shared = CurrentInstance()
private init() { } private init() {}
public func setClient(client: Client) { public func setClient(client: Client) {
self.client = client self.client = client

View file

@ -1,10 +1,10 @@
import Foundation
import UserNotifications
import SwiftUI
import KeychainSwift
import CryptoKit import CryptoKit
import Foundation
import KeychainSwift
import Models import Models
import Network import Network
import SwiftUI
import UserNotifications
@MainActor @MainActor
public class PushNotificationsService: ObservableObject { public class PushNotificationsService: ObservableObject {
@ -36,6 +36,7 @@ public class PushNotificationsService: ObservableObject {
} }
} }
} }
@Published public var isFollowNotificationEnabled: Bool = true @Published public var isFollowNotificationEnabled: Bool = true
@Published public var isFavoriteNotificationEnabled: Bool = true @Published public var isFavoriteNotificationEnabled: Bool = true
@Published public var isReblogNotificationEnabled: Bool = true @Published public var isReblogNotificationEnabled: Bool = true
@ -54,7 +55,7 @@ public class PushNotificationsService: ObservableObject {
} }
public func requestPushNotifications() { public func requestPushNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (_, _) in UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { _, _ in
DispatchQueue.main.async { DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications() UIApplication.shared.registerForRemoteNotifications()
} }
@ -68,7 +69,7 @@ public class PushNotificationsService: ObservableObject {
do { do {
let sub: PushSubscription = try await client.get(endpoint: Push.subscription) let sub: PushSubscription = try await client.get(endpoint: Push.subscription)
subscriptions.append(sub) subscriptions.append(sub)
} catch { } } catch {}
} }
refreshSubscriptionsUI() refreshSubscriptionsUI()
} }
@ -99,7 +100,7 @@ public class PushNotificationsService: ObservableObject {
favourite: isFavoriteNotificationEnabled, favourite: isFavoriteNotificationEnabled,
poll: isPollNotificationEnabled)) poll: isPollNotificationEnabled))
subscriptions.append(sub) subscriptions.append(sub)
} catch { } } catch {}
} }
refreshSubscriptionsUI() refreshSubscriptionsUI()
} }
@ -109,7 +110,7 @@ public class PushNotificationsService: ObservableObject {
let client = Client(server: account.server, oauthToken: account.token) let client = Client(server: account.server, oauthToken: account.token)
do { do {
_ = try await client.delete(endpoint: Push.subscription) _ = try await client.delete(endpoint: Push.subscription)
} catch { } } catch {}
} }
await fetchSubscriptions(accounts: accounts) await fetchSubscriptions(accounts: accounts)
refreshSubscriptionsUI() refreshSubscriptionsUI()
@ -133,7 +134,8 @@ public class PushNotificationsService: ObservableObject {
public var notificationsPrivateKeyAsKey: P256.KeyAgreement.PrivateKey { public var notificationsPrivateKeyAsKey: P256.KeyAgreement.PrivateKey {
if let key = keychain.get(Constants.keychainPrivateKey), if let key = keychain.get(Constants.keychainPrivateKey),
let data = Data(base64Encoded: key) { let data = Data(base64Encoded: key)
{
do { do {
return try P256.KeyAgreement.PrivateKey(rawRepresentation: data) return try P256.KeyAgreement.PrivateKey(rawRepresentation: data)
} catch { } catch {
@ -154,7 +156,8 @@ public class PushNotificationsService: ObservableObject {
public var notificationsAuthKeyAsKey: Data { public var notificationsAuthKeyAsKey: Data {
if let key = keychain.get(Constants.keychainAuthKey), if let key = keychain.get(Constants.keychainAuthKey),
let data = Data(base64Encoded: key) { let data = Data(base64Encoded: key)
{
return data return data
} else { } else {
let key = Self.makeRandomeNotificationsAuthKey() let key = Self.makeRandomeNotificationsAuthKey()
@ -165,7 +168,7 @@ public class PushNotificationsService: ObservableObject {
} }
} }
static private func makeRandomeNotificationsAuthKey() -> Data { private static func makeRandomeNotificationsAuthKey() -> Data {
let byteCount = 16 let byteCount = 16
var bytes = Data(count: byteCount) var bytes = Data(count: byteCount)
_ = bytes.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, byteCount, $0.baseAddress!) } _ = bytes.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, byteCount, $0.baseAddress!) }
@ -178,4 +181,3 @@ extension Data {
return map { String(format: "%02.2hhx", arguments: [$0]) }.joined() return map { String(format: "%02.2hhx", arguments: [$0]) }.joined()
} }
} }

View file

@ -8,9 +8,7 @@ public class QuickLook: ObservableObject {
@Published public private(set) var isPreparing: Bool = false @Published public private(set) var isPreparing: Bool = false
@Published public private(set) var latestError: Error? @Published public private(set) var latestError: Error?
public init() { public init() {}
}
public func prepareFor(urls: [URL], selectedURL: URL) async { public func prepareFor(urls: [URL], selectedURL: URL) async {
withAnimation { withAnimation {

View file

@ -1,7 +1,7 @@
import Foundation import Foundation
import SwiftUI
import Models import Models
import Network import Network
import SwiftUI
public enum RouteurDestinations: Hashable { public enum RouteurDestinations: Hashable {
case accountDetail(id: String) case accountDetail(id: String)
@ -59,7 +59,8 @@ 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.contains(where: { $0 == "tags" }), if url.pathComponents.contains(where: { $0 == "tags" }),
let tag = url.pathComponents.last { let tag = url.pathComponents.last
{
navigate(to: .hashTag(tag: tag, account: nil)) navigate(to: .hashTag(tag: tag, account: nil))
return .handled return .handled
} else if let mention = status.mentions.first(where: { $0.url == url }) { } else if let mention = status.mentions.first(where: { $0.url == url }) {
@ -68,7 +69,8 @@ public class RouterPath: ObservableObject {
} else if let client = client, } else if let client = client,
client.isAuth, client.isAuth,
client.hasConnection(with: url), client.hasConnection(with: url),
let id = Int(url.lastPathComponent) { let id = Int(url.lastPathComponent)
{
if url.absoluteString.contains(client.server) { if url.absoluteString.contains(client.server) {
navigate(to: .statusDetail(id: String(id))) navigate(to: .statusDetail(id: String(id)))
} else { } else {
@ -81,7 +83,8 @@ public class RouterPath: ObservableObject {
public func handle(url: URL) -> OpenURLAction.Result { public func handle(url: URL) -> OpenURLAction.Result {
if url.pathComponents.contains(where: { $0 == "tags" }), if url.pathComponents.contains(where: { $0 == "tags" }),
let tag = url.pathComponents.last { let tag = url.pathComponents.last
{
navigate(to: .hashTag(tag: tag, account: nil)) navigate(to: .hashTag(tag: tag, account: nil))
return .handled return .handled
} else if url.lastPathComponent.first == "@", let host = url.host { } else if url.lastPathComponent.first == "@", let host = url.host {

View file

@ -1,11 +1,11 @@
import SwiftUI
import Foundation import Foundation
import Models import Models
import Network import Network
import SwiftUI
@MainActor @MainActor
public class UserPreferences: ObservableObject { public class UserPreferences: ObservableObject {
public static let sharedDefault = UserDefaults.init(suiteName: "group.icecubesapps") public static let sharedDefault = UserDefaults(suiteName: "group.icecubesapps")
public static let shared = UserPreferences() public static let shared = UserPreferences()
private var client: Client? private var client: Client?
@ -25,7 +25,7 @@ public class UserPreferences: ObservableObject {
@Published public var serverPreferences: ServerPreferences? @Published public var serverPreferences: ServerPreferences?
private init() { } private init() {}
public func setClient(client: Client) { public func setClient(client: Client) {
self.client = client self.client = client

View file

@ -11,7 +11,8 @@ let package = Package(
products: [ products: [
.library( .library(
name: "Explore", name: "Explore",
targets: ["Explore"]), targets: ["Explore"]
),
], ],
dependencies: [ dependencies: [
.package(name: "Account", path: "../Account"), .package(name: "Account", path: "../Account"),
@ -30,8 +31,8 @@ let package = Package(
.product(name: "Models", package: "Models"), .product(name: "Models", package: "Models"),
.product(name: "Env", package: "Env"), .product(name: "Env", package: "Env"),
.product(name: "Status", package: "Status"), .product(name: "Status", package: "Status"),
.product(name: "DesignSystem", package: "DesignSystem") .product(name: "DesignSystem", package: "DesignSystem"),
]) ]
),
] ]
) )

View file

@ -1,11 +1,11 @@
import SwiftUI
import Env
import Network
import DesignSystem
import Models
import Status
import Shimmer
import Account import Account
import DesignSystem
import Env
import Models
import Network
import Shimmer
import Status
import SwiftUI
public struct ExploreView: View { public struct ExploreView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@ -14,7 +14,7 @@ public struct ExploreView: View {
@StateObject private var viewModel = ExploreViewModel() @StateObject private var viewModel = ExploreViewModel()
public init() { } public init() {}
public var body: some View { public var body: some View {
List { List {
@ -228,5 +228,4 @@ public struct ExploreView: View {
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }
} }
} }

View file

@ -1,7 +1,7 @@
import SwiftUI import Combine
import Models import Models
import Network import Network
import Combine import SwiftUI
@MainActor @MainActor
class ExploreViewModel: ObservableObject { class ExploreViewModel: ObservableObject {
@ -61,7 +61,7 @@ class ExploreViewModel: ObservableObject {
$searchQuery $searchQuery
.removeDuplicates() .removeDuplicates()
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main) .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
.sink(receiveValue: { [weak self] newValue in .sink(receiveValue: { [weak self] _ in
guard let self else { return } guard let self else { return }
if self.searchQuery.starts(with: "@") { if self.searchQuery.starts(with: "@") {
@ -81,12 +81,12 @@ class ExploreViewModel: ObservableObject {
guard let client else { return } guard let client else { return }
do { do {
let data = try await fetchTrendingsData(client: client) let data = try await fetchTrendingsData(client: client)
self.suggestedAccounts = data.suggestedAccounts suggestedAccounts = data.suggestedAccounts
self.trendingTags = data.trendingTags trendingTags = data.trendingTags
self.trendingStatuses = data.trendingStatuses trendingStatuses = data.trendingStatuses
self.trendingLinks = data.trendingLinks trendingLinks = data.trendingLinks
self.suggestedAccountsRelationShips = try await client.get(endpoint: Accounts.relationships(ids: self.suggestedAccounts.map{ $0.id })) suggestedAccountsRelationShips = try await client.get(endpoint: Accounts.relationships(ids: suggestedAccounts.map { $0.id }))
withAnimation { withAnimation {
isLoaded = true isLoaded = true
} }
@ -127,10 +127,10 @@ class ExploreViewModel: ObservableObject {
following: nil), following: nil),
forceVersion: .v2) forceVersion: .v2)
let relationships: [Relationshionship] = let relationships: [Relationshionship] =
try await client.get(endpoint: Accounts.relationships(ids: results.accounts.map{ $0.id })) try await client.get(endpoint: Accounts.relationships(ids: results.accounts.map { $0.id }))
results.relationships = relationships results.relationships = relationships
self.results[searchQuery] = results self.results[searchQuery] = results
} catch { } } catch {}
} }
} }
} }

View file

@ -11,7 +11,8 @@ let package = Package(
products: [ products: [
.library( .library(
name: "Lists", name: "Lists",
targets: ["Lists"]), targets: ["Lists"]
),
], ],
dependencies: [ dependencies: [
.package(name: "Network", path: "../Network"), .package(name: "Network", path: "../Network"),
@ -26,8 +27,8 @@ let package = Package(
.product(name: "Network", package: "Network"), .product(name: "Network", package: "Network"),
.product(name: "Models", package: "Models"), .product(name: "Models", package: "Models"),
.product(name: "Env", package: "Env"), .product(name: "Env", package: "Env"),
.product(name: "DesignSystem", package: "DesignSystem") .product(name: "DesignSystem", package: "DesignSystem"),
]), ]
),
] ]
) )

View file

@ -1,8 +1,8 @@
import SwiftUI
import Network
import DesignSystem import DesignSystem
import Env import Env
import Models import Models
import Network
import SwiftUI
public struct ListAddAccountView: View { public struct ListAddAccountView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@ -14,7 +14,6 @@ public struct ListAddAccountView: View {
@State private var isCreateListAlertPresented: Bool = false @State private var isCreateListAlertPresented: Bool = false
@State private var createListTitle: String = "" @State private var createListTitle: String = ""
public init(account: Account) { public init(account: Account) {
_viewModel = StateObject(wrappedValue: .init(account: account)) _viewModel = StateObject(wrappedValue: .init(account: account))
} }

View file

@ -1,6 +1,6 @@
import SwiftUI
import Models import Models
import Network import Network
import SwiftUI
@MainActor @MainActor
class ListAddAccountViewModel: ObservableObject { class ListAddAccountViewModel: ObservableObject {

View file

@ -1,8 +1,8 @@
import SwiftUI
import Models
import DesignSystem import DesignSystem
import Network
import EmojiText import EmojiText
import Models
import Network
import SwiftUI
public struct ListEditView: View { public struct ListEditView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss

View file

@ -1,6 +1,6 @@
import SwiftUI
import Models import Models
import Network import Network
import SwiftUI
@MainActor @MainActor
public class ListEditViewModel: ObservableObject { public class ListEditViewModel: ObservableObject {
@ -33,6 +33,6 @@ public class ListEditViewModel: ObservableObject {
if response?.statusCode == 200 { if response?.statusCode == 200 {
accounts.removeAll(where: { $0.id == account.id }) accounts.removeAll(where: { $0.id == account.id })
} }
} catch { } } catch {}
} }
} }

View file

@ -11,7 +11,8 @@ let package = Package(
products: [ products: [
.library( .library(
name: "Models", name: "Models",
targets: ["Models"]), targets: ["Models"]
),
], ],
dependencies: [ dependencies: [
.package(url: "https://gitlab.com/mflint/HTML2Markdown", exact: "1.0.0"), .package(url: "https://gitlab.com/mflint/HTML2Markdown", exact: "1.0.0"),
@ -21,9 +22,11 @@ let package = Package(
.target( .target(
name: "Models", name: "Models",
dependencies: ["HTML2Markdown", dependencies: ["HTML2Markdown",
"SwiftSoup"]), "SwiftSoup"]
),
.testTarget( .testTarget(
name: "ModelsTests", name: "ModelsTests",
dependencies: ["Models"]), dependencies: ["Models"]
),
] ]
) )

View file

@ -1,7 +1,6 @@
import Foundation import Foundation
public struct Account: Codable, Identifiable, Equatable, Hashable { public struct Account: Codable, Identifiable, Equatable, Hashable {
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
hasher.combine(id) hasher.combine(id)
} }

View file

@ -5,8 +5,8 @@ import SwiftUI
public typealias HTMLString = String public typealias HTMLString = String
extension HTMLString { public extension HTMLString {
public var asMarkdown: String { var asMarkdown: String {
do { do {
let dom = try HTMLParser().parse(html: self) let dom = try HTMLParser().parse(html: self)
return dom.toMarkdown() return dom.toMarkdown()
@ -17,7 +17,7 @@ extension HTMLString {
} }
} }
public var asRawText: String { var asRawText: String {
do { do {
let document: Document = try SwiftSoup.parse(self) let document: Document = try SwiftSoup.parse(self)
return try document.text() return try document.text()
@ -26,7 +26,7 @@ extension HTMLString {
} }
} }
public func findStatusesURLs() -> [URL]? { func findStatusesURLs() -> [URL]? {
do { do {
let document: Document = try SwiftSoup.parse(self) let document: Document = try SwiftSoup.parse(self)
let links: Elements = try document.select("a") let links: Elements = try document.select("a")
@ -34,7 +34,8 @@ extension HTMLString {
for link in links { for link in links {
let href = try link.attr("href") let href = try link.attr("href")
if let url = URL(string: href), if let url = URL(string: href),
let _ = Int(url.lastPathComponent) { let _ = Int(url.lastPathComponent)
{
URLs.append(url) URLs.append(url)
} }
} }
@ -44,7 +45,7 @@ extension HTMLString {
} }
} }
public var asSafeAttributedString: AttributedString { var asSafeAttributedString: AttributedString {
do { do {
let options = AttributedString.MarkdownParsingOptions(allowsExtendedAttributes: true, let options = AttributedString.MarkdownParsingOptions(allowsExtendedAttributes: true,
interpretedSyntax: .inlineOnlyPreservingWhitespace) interpretedSyntax: .inlineOnlyPreservingWhitespace)
@ -54,4 +55,3 @@ extension HTMLString {
} }
} }
} }

View file

@ -37,7 +37,6 @@ extension ServerDate {
} }
} }
extension Calendar { extension Calendar {
func numberOfDaysBetween(_ from: Date, and to: Date) -> Int { func numberOfDaysBetween(_ from: Date, and to: Date) -> Int {
let fromDate = startOfDay(for: from) let fromDate = startOfDay(for: from)

View file

@ -1,6 +1,6 @@
import Foundation import Foundation
public struct AppInfo { public enum AppInfo {
public static let clientName = "IceCubesApp" public static let clientName = "IceCubesApp"
public static let scheme = "icecubesapp://" public static let scheme = "icecubesapp://"
public static let scopes = "read write follow push" public static let scopes = "read write follow push"

View file

@ -1,7 +1,6 @@
import Foundation import Foundation
public struct Emoji: Codable, Hashable, Identifiable { public struct Emoji: Codable, Hashable, Identifiable {
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
hasher.combine(shortcode) hasher.combine(shortcode)
} }

View file

@ -4,6 +4,7 @@ public struct InstanceSocial: Decodable, Identifiable {
public struct Info: Decodable { public struct Info: Decodable {
public let shortDescription: String public let shortDescription: String
} }
public let id: String public let id: String
public let name: String public let name: String
public let dead: Bool public let dead: Bool

View file

@ -1,7 +1,6 @@
import Foundation import Foundation
public struct MastodonPushNotification: Codable { public struct MastodonPushNotification: Codable {
public let accessToken: String public let accessToken: String
public let notificationID: Int public let notificationID: Int

View file

@ -1,12 +1,12 @@
import Foundation import Foundation
public struct MediaAttachement: Codable, Identifiable, Hashable { public struct MediaAttachement: Codable, Identifiable, Hashable {
public struct MetaContainer: Codable, Equatable { public struct MetaContainer: Codable, Equatable {
public struct Meta: Codable, Equatable { public struct Meta: Codable, Equatable {
public let width: Int? public let width: Int?
public let height: Int? public let height: Int?
} }
public let original: Meta? public let original: Meta?
} }
@ -23,9 +23,9 @@ public struct MediaAttachement: Codable, Identifiable, Hashable {
public var supportedType: SupportedType? { public var supportedType: SupportedType? {
SupportedType(rawValue: type) SupportedType(rawValue: type)
} }
public let url: URL? public let url: URL?
public let previewUrl: URL? public let previewUrl: URL?
public let description: String? public let description: String?
public let meta: MetaContainer? public let meta: MetaContainer?
} }

View file

@ -27,4 +27,3 @@ public struct Notification: Codable, Identifiable {
[.placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder()] [.placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder()]
} }
} }

View file

@ -15,7 +15,7 @@ public struct Relationshionship: Codable {
public let note: String public let note: String
public let notifying: Bool public let notifying: Bool
static public func placeholder() -> Relationshionship { public static func placeholder() -> Relationshionship {
.init(id: UUID().uuidString, .init(id: UUID().uuidString,
following: false, following: false,
showingReblogs: false, showingReblogs: false,

View file

@ -4,12 +4,13 @@ public struct Application: Codable, Identifiable {
public var id: String { public var id: String {
name name
} }
public let name: String public let name: String
public let website: URL? public let website: URL?
} }
extension Application { public extension Application {
public init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self) let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decodeIfPresent(String.self, forKey: .name) ?? "" name = try values.decodeIfPresent(String.self, forKey: .name) ?? ""
@ -53,7 +54,6 @@ public protocol AnyStatus {
var language: String? { get } var language: String? { get }
} }
public struct Status: AnyStatus, Codable, Identifiable { public struct Status: AnyStatus, Codable, Identifiable {
public var viewId: String { public var viewId: String {
id + createdAt + (editedAt ?? "") id + createdAt + (editedAt ?? "")

View file

@ -6,7 +6,7 @@ public struct RawStreamEvent: Decodable {
public let payload: String public let payload: String
} }
public protocol StreamEvent: Identifiable{ public protocol StreamEvent: Identifiable {
var date: Date { get } var date: Date { get }
var id: String { get } var id: String { get }
} }

View file

@ -8,5 +8,4 @@ public struct StreamMessage: Encodable {
self.type = type self.type = type
self.stream = stream self.stream = stream
} }
} }

View file

@ -25,11 +25,11 @@ public struct Tag: Codable, Identifiable, Equatable, Hashable {
public let history: [History] public let history: [History]
public var totalUses: Int { public var totalUses: Int {
history.compactMap{ Int($0.uses) }.reduce(0, +) history.compactMap { Int($0.uses) }.reduce(0, +)
} }
public var totalAccounts: Int { public var totalAccounts: Int {
history.compactMap{ Int($0.accounts) }.reduce(0, +) history.compactMap { Int($0.accounts) }.reduce(0, +)
} }
} }

View file

@ -1,5 +1,5 @@
import XCTest
@testable import Models @testable import Models
import XCTest
final class ModelsTests: XCTestCase { final class ModelsTests: XCTestCase {
func testExample() throws { func testExample() throws {

View file

@ -11,7 +11,8 @@ let package = Package(
products: [ products: [
.library( .library(
name: "Network", name: "Network",
targets: ["Network"]), targets: ["Network"]
),
], ],
dependencies: [ dependencies: [
.package(name: "Models", path: "../Models"), .package(name: "Models", path: "../Models"),
@ -20,10 +21,12 @@ let package = Package(
.target( .target(
name: "Network", name: "Network",
dependencies: [ dependencies: [
.product(name: "Models", package: "Models") .product(name: "Models", package: "Models"),
]), ]
),
.testTarget( .testTarget(
name: "NetworkTests", name: "NetworkTests",
dependencies: ["Network"]), dependencies: ["Network"]
),
] ]
) )

View file

@ -1,6 +1,6 @@
import Foundation import Foundation
import SwiftUI
import Models import Models
import SwiftUI
public class Client: ObservableObject, Equatable { public class Client: ObservableObject, Equatable {
public static func == (lhs: Client, rhs: Client) -> Bool { public static func == (lhs: Client, rhs: Client) -> Bool {
@ -36,10 +36,10 @@ public class Client: ObservableObject, Equatable {
public init(server: String, version: Version = .v1, oauthToken: OauthToken? = nil) { public init(server: String, version: Version = .v1, oauthToken: OauthToken? = nil) {
self.server = server self.server = server
self.version = version self.version = version
self.urlSession = URLSession.shared urlSession = URLSession.shared
self.decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.keyDecodingStrategy = .convertFromSnakeCase
self.oauthToken = oauthToken self.oauthToken = oauthToken
self.connections = Set([server]) connections = Set([server])
} }
public func addConnections(_ connections: [String]) { public func addConnections(_ connections: [String]) {
@ -99,7 +99,8 @@ public class Client: ObservableObject, Equatable {
let (data, httpResponse) = try await urlSession.data(for: makeGet(endpoint: endpoint)) let (data, httpResponse) = try await urlSession.data(for: makeGet(endpoint: endpoint))
var linkHandler: LinkHandler? var linkHandler: LinkHandler?
if let response = httpResponse as? HTTPURLResponse, if let response = httpResponse as? HTTPURLResponse,
let link = response.allHeaderFields["Link"] as? String{ let link = response.allHeaderFields["Link"] as? String
{
linkHandler = .init(rawLink: link) linkHandler = .init(rawLink: link)
} }
logResponseOnError(httpResponse: httpResponse, data: data) logResponseOnError(httpResponse: httpResponse, data: data)
@ -137,7 +138,8 @@ public class Client: ObservableObject, Equatable {
private func makeEntityRequest<Entity: Decodable>(endpoint: Endpoint, private func makeEntityRequest<Entity: Decodable>(endpoint: Endpoint,
method: String, method: String,
forceVersion: Version? = nil) async throws -> Entity { forceVersion: Version? = nil) async throws -> Entity
{
let url = makeURL(endpoint: endpoint, forceVersion: forceVersion) let url = makeURL(endpoint: endpoint, forceVersion: forceVersion)
let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method) let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method)
let (data, httpResponse) = try await urlSession.data(for: request) let (data, httpResponse) = try await urlSession.data(for: request)
@ -147,7 +149,7 @@ public class Client: ObservableObject, Equatable {
public func oauthURL() async throws -> URL { public func oauthURL() async throws -> URL {
let app: InstanceApp = try await post(endpoint: Apps.registerApp) let app: InstanceApp = try await post(endpoint: Apps.registerApp)
self.oauthApp = app oauthApp = app
return makeURL(endpoint: Oauth.authorize(clientId: app.clientId)) return makeURL(endpoint: Oauth.authorize(clientId: app.clientId))
} }
@ -156,13 +158,14 @@ public class Client: ObservableObject, Equatable {
throw OauthError.missingApp throw OauthError.missingApp
} }
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false), guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let code = components.queryItems?.first(where: { $0.name == "code"})?.value else { let code = components.queryItems?.first(where: { $0.name == "code" })?.value
else {
throw OauthError.invalidRedirectURL throw OauthError.invalidRedirectURL
} }
let token: OauthToken = try await post(endpoint: Oauth.token(code: code, let token: OauthToken = try await post(endpoint: Oauth.token(code: code,
clientId: app.clientId, clientId: app.clientId,
clientSecret: app.clientSecret)) clientSecret: app.clientSecret))
self.oauthToken = token oauthToken = token
return token return token
} }
@ -177,7 +180,8 @@ public class Client: ObservableObject, Equatable {
method: String, method: String,
mimeType: String, mimeType: String,
filename: String, filename: String,
data: Data) async throws -> Entity { data: Data) async throws -> Entity
{
let url = makeURL(endpoint: endpoint, forceVersion: version) let url = makeURL(endpoint: endpoint, forceVersion: version)
var request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method) var request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method)
let boundary = UUID().uuidString let boundary = UUID().uuidString

View file

@ -33,7 +33,7 @@ public enum Accounts: Endpoint {
public func path() -> String { public func path() -> String {
switch self { switch self {
case .accounts(let id): case let .accounts(id):
return "accounts/\(id)" return "accounts/\(id)"
case .favourites: case .favourites:
return "favourites" return "favourites"
@ -41,29 +41,29 @@ public enum Accounts: Endpoint {
return "bookmarks" return "bookmarks"
case .followedTags: case .followedTags:
return "followed_tags" return "followed_tags"
case .featuredTags(let id): case let .featuredTags(id):
return "accounts/\(id)/featured_tags" return "accounts/\(id)/featured_tags"
case .verifyCredentials: case .verifyCredentials:
return "accounts/verify_credentials" return "accounts/verify_credentials"
case .updateCredentials: case .updateCredentials:
return "accounts/update_credentials" return "accounts/update_credentials"
case .statuses(let id, _, _, _, _, _): case let .statuses(id, _, _, _, _, _):
return "accounts/\(id)/statuses" return "accounts/\(id)/statuses"
case .relationships: case .relationships:
return "accounts/relationships" return "accounts/relationships"
case .follow(let id, _): case let .follow(id, _):
return "accounts/\(id)/follow" return "accounts/\(id)/follow"
case .unfollow(let id): case let .unfollow(id):
return "accounts/\(id)/unfollow" return "accounts/\(id)/unfollow"
case .familiarFollowers: case .familiarFollowers:
return "accounts/familiar_followers" return "accounts/familiar_followers"
case .suggestions: case .suggestions:
return "suggestions" return "suggestions"
case .following(let id, _): case let .following(id, _):
return "accounts/\(id)/following" return "accounts/\(id)/following"
case .followers(let id, _): case let .followers(id, _):
return "accounts/\(id)/followers" return "accounts/\(id)/followers"
case .lists(let id): case let .lists(id):
return "accounts/\(id)/lists" return "accounts/\(id)/lists"
case .preferences: case .preferences:
return "preferences" return "preferences"
@ -72,7 +72,7 @@ public enum Accounts: Endpoint {
public func queryItems() -> [URLQueryItem]? { public func queryItems() -> [URLQueryItem]? {
switch self { switch self {
case .statuses(_, let sinceId, let tag, let onlyMedia, let excludeReplies, let pinned): case let .statuses(_, sinceId, tag, onlyMedia, excludeReplies, pinned):
var params: [URLQueryItem] = [] var params: [URLQueryItem] = []
if let tag { if let tag {
params.append(.init(name: "tagged", value: tag)) params.append(.init(name: "tagged", value: tag))

View file

@ -18,7 +18,7 @@ public enum Apps: Endpoint {
.init(name: "client_name", value: AppInfo.clientName), .init(name: "client_name", value: AppInfo.clientName),
.init(name: "redirect_uris", value: AppInfo.scheme), .init(name: "redirect_uris", value: AppInfo.scheme),
.init(name: "scopes", value: AppInfo.scopes), .init(name: "scopes", value: AppInfo.scopes),
.init(name: "website", value: AppInfo.weblink) .init(name: "website", value: AppInfo.weblink),
] ]
} }
} }

View file

@ -6,8 +6,8 @@ public protocol Endpoint {
var jsonValue: Encodable? { get } var jsonValue: Encodable? { get }
} }
extension Endpoint { public extension Endpoint {
public var jsonValue: Encodable? { var jsonValue: Encodable? {
nil nil
} }
} }

View file

@ -22,7 +22,7 @@ public enum Lists: Endpoint {
public func queryItems() -> [URLQueryItem]? { public func queryItems() -> [URLQueryItem]? {
switch self { switch self {
case .accounts(_): case .accounts:
return [.init(name: "limit", value: String(0))] return [.init(name: "limit", value: String(0))]
case let .createList(title): case let .createList(title):
return [.init(name: "title", value: title)] return [.init(name: "title", value: title)]

View file

@ -17,7 +17,7 @@ public enum Notifications: Endpoint {
public func queryItems() -> [URLQueryItem]? { public func queryItems() -> [URLQueryItem]? {
switch self { switch self {
case .notifications(let sinceId, let maxId, let types): case let .notifications(sinceId, maxId, types):
var params = makePaginationParam(sinceId: sinceId, maxId: maxId, mindId: nil) ?? [] var params = makePaginationParam(sinceId: sinceId, maxId: maxId, mindId: nil) ?? []
if let types { if let types {
for type in types { for type in types {

View file

@ -21,7 +21,7 @@ public enum Oauth: Endpoint {
.init(name: "response_type", value: "code"), .init(name: "response_type", value: "code"),
.init(name: "client_id", value: clientId), .init(name: "client_id", value: clientId),
.init(name: "redirect_uri", value: AppInfo.scheme), .init(name: "redirect_uri", value: AppInfo.scheme),
.init(name: "scope", value: AppInfo.scopes) .init(name: "scope", value: AppInfo.scopes),
] ]
case let .token(code, clientId, clientSecret): case let .token(code, clientId, clientSecret):
return [ return [
@ -30,7 +30,7 @@ public enum Oauth: Endpoint {
.init(name: "client_secret", value: clientSecret), .init(name: "client_secret", value: clientSecret),
.init(name: "redirect_uri", value: AppInfo.scheme), .init(name: "redirect_uri", value: AppInfo.scheme),
.init(name: "code", value: code), .init(name: "code", value: code),
.init(name: "scope", value: AppInfo.scopes) .init(name: "scope", value: AppInfo.scopes),
] ]
} }
} }

View file

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

View file

@ -21,7 +21,7 @@ public enum Search: Endpoint {
params.append(.init(name: "offset", value: String(offset))) params.append(.init(name: "offset", value: String(offset)))
} }
if let following { if let following {
params.append(.init(name: "following", value: following ? "true": "false")) params.append(.init(name: "following", value: following ? "true" : "false"))
} }
params.append(.init(name: "resolve", value: "true")) params.append(.init(name: "resolve", value: "true"))
return params return params

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