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[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 730;
DEVELOPMENT_TEAM = Z6P74P6T99;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = IceCubesShareExtension/Info.plist;
@ -674,7 +674,7 @@
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 730;
DEVELOPMENT_TEAM = Z6P74P6T99;
GENERATE_INFOPLIST_FILE = YES;
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 Timeline
import Account
import Env
import Status
import DesignSystem
import Lists
import AppAccount
@MainActor
extension View {
func withAppRouteur() -> some View {
self.navigationDestination(for: RouteurDestinations.self) { destination in
navigationDestination(for: RouteurDestinations.self) { destination in
switch destination {
case let .accountDetail(id):
AccountDetailView(accountId: id)
@ -37,7 +37,7 @@ extension View {
}
func withSheetDestinations(sheetDestinations: Binding<SheetDestinations?>) -> some View {
self.sheet(item: sheetDestinations) { destination in
sheet(item: sheetDestinations) { destination in
switch destination {
case let .replyToStatusEditor(status):
StatusEditorView(mode: .replyTo(status: status))
@ -71,8 +71,7 @@ extension View {
}
func withEnvironments() -> some View {
self
.environmentObject(CurrentAccount.shared)
environmentObject(CurrentAccount.shared)
.environmentObject(UserPreferences.shared)
.environmentObject(CurrentInstance.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 AppAccount
import AVFoundation
import DesignSystem
import Env
import KeychainSwift
import Network
import RevenueCat
import SwiftUI
import Timeline
@main
struct IceCubesApp: App {
@ -34,25 +34,25 @@ struct IceCubesApp: App {
var body: some Scene {
WindowGroup {
appView
.applyTheme(theme)
.onAppear {
setNewClientsInEnv(client: appAccountsManager.currentClient)
setupRevenueCat()
refreshPushSubs()
}
.environmentObject(appAccountsManager)
.environmentObject(appAccountsManager.currentClient)
.environmentObject(quickLook)
.environmentObject(currentAccount)
.environmentObject(currentInstance)
.environmentObject(userPreferences)
.environmentObject(theme)
.environmentObject(watcher)
.environmentObject(PushNotificationsService.shared)
.sheet(item: $quickLook.url, content: { url in
QuickLookPreview(selectedURL: url, urls: quickLook.urls)
.edgesIgnoringSafeArea(.bottom)
})
.applyTheme(theme)
.onAppear {
setNewClientsInEnv(client: appAccountsManager.currentClient)
setupRevenueCat()
refreshPushSubs()
}
.environmentObject(appAccountsManager)
.environmentObject(appAccountsManager.currentClient)
.environmentObject(quickLook)
.environmentObject(currentAccount)
.environmentObject(currentInstance)
.environmentObject(userPreferences)
.environmentObject(theme)
.environmentObject(watcher)
.environmentObject(PushNotificationsService.shared)
.sheet(item: $quickLook.url, content: { url in
QuickLookPreview(selectedURL: url, urls: quickLook.urls)
.edgesIgnoringSafeArea(.bottom)
})
}
.onChange(of: scenePhase) { scenePhase in
handleScenePhase(scenePhase: scenePhase)
@ -168,14 +168,16 @@ struct IceCubesApp: App {
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
{
try? AVAudioSession.sharedInstance().setCategory(.ambient, options: .mixWithOthers)
return true
}
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
func application(_: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
{
PushNotificationsService.shared.pushToken = deviceToken
Task {
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 SwiftUI
import UIKit
extension URL: Identifiable {
public var id: String {
@ -21,7 +21,8 @@ struct QuickLookPreview: UIViewControllerRepresentable {
}
func updateUIViewController(
_ uiViewController: UINavigationController, context: Context) {}
_: UINavigationController, context _: Context
) {}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
@ -34,15 +35,15 @@ struct QuickLookPreview: UIViewControllerRepresentable {
self.parent = parent
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
func numberOfPreviewItems(in _: QLPreviewController) -> Int {
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
}
func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode {
func previewController(_: QLPreviewController, editingModeFor _: QLPreviewItem) -> QLPreviewItemEditingMode {
.createCopy
}
}

View file

@ -1,85 +1,85 @@
import SwiftUI
import SafariServices
import Env
import DesignSystem
import Env
import SafariServices
import SwiftUI
extension View {
func withSafariRouteur() -> some View {
modifier(SafariRouteur())
}
func withSafariRouteur() -> some View {
modifier(SafariRouteur())
}
}
private struct SafariRouteur: ViewModifier {
@EnvironmentObject private var theme: Theme
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var routeurPath: RouterPath
@EnvironmentObject private var theme: Theme
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var routeurPath: RouterPath
@State private var safari: SFSafariViewController?
@State private var safari: SFSafariViewController?
func body(content: Content) -> some View {
content
.environment(\.openURL, OpenURLAction { url in
routeurPath.handle(url: url)
})
.onAppear {
routeurPath.urlHandler = { url in
guard preferences.preferredBrowser == .inAppSafari else { return .systemAction }
// SFSafariViewController only supports initial URLs with http:// or https:// schemes.
guard let scheme = url.scheme, ["https", "http"].contains(scheme.lowercased()) else {
return .systemAction
}
func body(content: Content) -> some View {
content
.environment(\.openURL, OpenURLAction { url in
routeurPath.handle(url: url)
})
.onAppear {
routeurPath.urlHandler = { url in
guard preferences.preferredBrowser == .inAppSafari else { return .systemAction }
// SFSafariViewController only supports initial URLs with http:// or https:// schemes.
guard let scheme = url.scheme, ["https", "http"].contains(scheme.lowercased()) else {
return .systemAction
}
let safari = SFSafariViewController(url: url)
safari.preferredBarTintColor = UIColor(theme.primaryBackgroundColor)
safari.preferredControlTintColor = UIColor(theme.tintColor)
let safari = SFSafariViewController(url: url)
safari.preferredBarTintColor = UIColor(theme.primaryBackgroundColor)
safari.preferredControlTintColor = UIColor(theme.tintColor)
self.safari = safari
return .handled
}
}
.background {
SafariPresenter(safari: safari)
}
self.safari = safari
return .handled
}
}
.background {
SafariPresenter(safari: safari)
}
}
struct SafariPresenter: UIViewRepresentable {
var safari: SFSafariViewController?
func makeUIView(context _: Context) -> UIView {
let view = UIView(frame: .zero)
view.isHidden = true
view.isUserInteractionEnabled = false
return view
}
struct SafariPresenter: UIViewRepresentable {
var safari: SFSafariViewController?
func makeUIView(context: Context) -> UIView {
let view = UIView(frame: .zero)
view.isHidden = true
view.isUserInteractionEnabled = false
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
guard let safari = safari, let viewController = uiView.findTopViewController() else { return }
viewController.present(safari, animated: true)
}
func updateUIView(_ uiView: UIView, context _: Context) {
guard let safari = safari, let viewController = uiView.findTopViewController() else { return }
viewController.present(safari, animated: true)
}
}
}
private extension UIView {
func findTopViewController() -> UIViewController? {
if let nextResponder = self.next as? UIViewController {
return nextResponder.topViewController()
} else if let nextResponder = self.next as? UIView {
return nextResponder.findTopViewController()
} else {
return nil
}
func findTopViewController() -> UIViewController? {
if let nextResponder = next as? UIViewController {
return nextResponder.topViewController()
} else if let nextResponder = next as? UIView {
return nextResponder.findTopViewController()
} else {
return nil
}
}
}
private extension UIViewController {
func topViewController() -> UIViewController? {
if let nvc = self as? UINavigationController {
return nvc.visibleViewController?.topViewController()
} else if let tbc = self as? UITabBarController, let selected = tbc.selectedViewController {
return selected.topViewController()
} else if let presented = self.presentedViewController {
return presented.topViewController()
}
return self
func topViewController() -> UIViewController? {
if let nvc = self as? UINavigationController {
return nvc.visibleViewController?.topViewController()
} else if let tbc = self as? UITabBarController, let selected = tbc.selectedViewController {
return selected.topViewController()
} else if let presented = presentedViewController {
return presented.topViewController()
}
return self
}
}

View file

@ -1,8 +1,8 @@
import SwiftUI
import Env
import Account
import DesignSystem
import AppAccount
import DesignSystem
import Env
import SwiftUI
struct SideBarView<Content: View>: View {
@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 Env
import Explore
import Models
import Network
import Shimmer
import SwiftUI
struct ExploreTab: View {
@EnvironmentObject private var preferences: UserPreferences

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
import SwiftUI
import Models
import DesignSystem
import Models
import NukeUI
import SwiftUI
struct InstanceInfoView: View {
@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 DesignSystem
import Env
import Models
import Network
import NukeUI
import SwiftUI
import UserNotifications
struct PushNotificationsView: View {
@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 Timeline
import Env
import Network
import Account
import Models
import DesignSystem
import AppAccount
struct SettingsTabs: View {
@EnvironmentObject private var pushNotifications: PushNotificationsService

View file

@ -1,8 +1,8 @@
import SwiftUI
import Env
import DesignSystem
import Env
import RevenueCat
import Shimmer
import SwiftUI
struct SupportAppView: View {
enum Tips: String, CaseIterable {
@ -133,7 +133,7 @@ struct SupportAppView: View {
})
.onAppear {
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 })
withAnimation {
loadingProducts = false

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import Foundation
import CryptoKit
import Foundation
extension NotificationService {
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)
}
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()
info.append("Content-Encoding: ".data(using: .utf8)!)
@ -62,14 +62,12 @@ extension NotificationService {
extension String {
func escape() -> String {
return self
.replacingOccurrences(of: "&amp;", with: "&")
return replacingOccurrences(of: "&amp;", with: "&")
.replacingOccurrences(of: "&lt;", with: "<")
.replacingOccurrences(of: "&gt;", with: ">")
.replacingOccurrences(of: "&quot;", with: "\"")
.replacingOccurrences(of: "&apos;", with: "'")
.replacingOccurrences(of: "&#39;", with: "")
}
func URLSafeBase64ToBase64() -> String {
@ -83,4 +81,3 @@ extension String {
return base64
}
}

View file

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

View file

@ -1,11 +1,11 @@
import Account
import AppAccount
import DesignSystem
import Env
import Network
import Status
import SwiftUI
import UIKit
import Status
import DesignSystem
import Account
import Network
import Env
import AppAccount
class ShareViewController: UIViewController {
@IBOutlet var container: UIView!
@ -35,9 +35,9 @@ class ShareViewController: UIViewController {
.tint(theme.tintColor)
.preferredColorScheme(colorScheme == .light ? .light : .dark)
let childView = UIHostingController(rootView: view)
self.addChild(childView)
childView.view.frame = self.container.bounds
self.container.addSubview(childView.view)
addChild(childView)
childView.view.frame = container.bounds
container.addSubview(childView.view)
childView.didMove(toParent: self)
}
}
@ -45,7 +45,7 @@ class ShareViewController: UIViewController {
NotificationCenter.default.addObserver(forName: NotificationsName.shareSheetClose,
object: nil,
queue: nil) { _ in
self.close()
self.close()
}
}

View file

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

View file

@ -1,10 +1,10 @@
import SwiftUI
import Models
import DesignSystem
import Env
import Shimmer
import NukeUI
import EmojiText
import Env
import Models
import NukeUI
import Shimmer
import SwiftUI
struct AccountDetailHeaderView: View {
@EnvironmentObject private var theme: Theme
@ -30,7 +30,7 @@ struct AccountDetailHeaderView: View {
}
private var headerImageView: some View {
GeometryReader { proxy in
GeometryReader { _ in
ZStack(alignment: .bottomTrailing) {
if reasons.contains(.placeholder) {
Rectangle()
@ -79,11 +79,11 @@ struct AccountDetailHeaderView: View {
private var accountAvatarView: some View {
HStack {
AvatarView(url: account.avatar, size: .account)
.onTapGesture {
Task {
await quickLook.prepareFor(urls: [account.avatar], selectedURL: account.avatar)
.onTapGesture {
Task {
await quickLook.prepareFor(urls: [account.avatar], selectedURL: account.avatar)
}
}
}
Spacer()
Group {
Button {
@ -109,10 +109,10 @@ struct AccountDetailHeaderView: View {
HStack {
VStack(alignment: .leading, spacing: 0) {
EmojiTextApp(account.safeDisplayName.asMarkdown, emojis: account.emojis)
.font(.headline)
.font(.headline)
Text("@\(account.acct)")
.font(.callout)
.foregroundColor(.gray)
.font(.callout)
.foregroundColor(.gray)
}
Spacer()
if let relationship = viewModel.relationship, !viewModel.isCurrentUser {

View file

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

View file

@ -1,8 +1,8 @@
import SwiftUI
import Network
import Models
import Status
import Env
import Models
import Network
import Status
import SwiftUI
@MainActor
class AccountDetailViewModel: ObservableObject, StatusesFetcher {
@ -57,6 +57,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
}
}
}
@Published var statusesState: StatusesState = .loading
@Published var relationship: Relationshionship?
@ -90,14 +91,14 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
/// When coming from a URL like a mention tap in a status.
init(accountId: String) {
self.accountId = accountId
self.isCurrentUser = false
isCurrentUser = false
}
/// When the account is already fetched by the parent caller.
init(account: Account) {
self.accountId = account.id
accountId = account.id
self.account = account
self.accountState = .data(account: account)
accountState = .data(account: account)
}
struct AccountData {
@ -151,20 +152,20 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
do {
tabState = .statuses(statusesState: .loading)
statuses =
try await client.get(endpoint: Accounts.statuses(id: accountId,
sinceId: nil,
tag: nil,
onlyMedia: selectedTab == .media ? true : nil,
excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil,
pinned: nil))
if selectedTab == .statuses {
pinned =
try await client.get(endpoint: Accounts.statuses(id: accountId,
sinceId: nil,
tag: nil,
onlyMedia: nil,
excludeReplies: nil,
pinned: true))
onlyMedia: selectedTab == .media ? true : nil,
excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil,
pinned: nil))
if selectedTab == .statuses {
pinned =
try await client.get(endpoint: Accounts.statuses(id: accountId,
sinceId: nil,
tag: nil,
onlyMedia: nil,
excludeReplies: nil,
pinned: true))
}
if isCurrentUser {
(favourites, favouritesNextPage) = try await client.getWithLink(endpoint: Accounts.favourites(sinceId: nil))
@ -184,12 +185,12 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
guard let lastId = statuses.last?.id else { return }
tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: .loadingNextPage))
let newStatuses: [Status] =
try await client.get(endpoint: Accounts.statuses(id: accountId,
sinceId: lastId,
tag: nil,
onlyMedia: selectedTab == .media ? true : nil,
excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil,
pinned: nil))
try await client.get(endpoint: Accounts.statuses(id: accountId,
sinceId: lastId,
tag: nil,
onlyMedia: selectedTab == .media ? true : nil,
excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil,
pinned: nil))
statuses.append(contentsOf: newStatuses)
tabState = .statuses(statusesState: .display(statuses: statuses,
nextPageState: newStatuses.count < 20 ? .none : .hasNextPage))

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import SwiftUI
import Models
import Network
import SwiftUI
@MainActor
class EditAccountViewModel: ObservableObject {
@ -18,7 +18,7 @@ class EditAccountViewModel: ObservableObject {
@Published var isSaving: Bool = false
@Published var saveError: Bool = false
init() { }
init() {}
func fetchAccount() async {
guard let client else { return }
@ -34,20 +34,20 @@ class EditAccountViewModel: ObservableObject {
withAnimation {
isLoading = false
}
} catch { }
} catch {}
}
func save() async {
isSaving = true
do {
let response =
try await client?.patch(endpoint: Accounts.updateCredentials(displayName: displayName,
note: note,
privacy: postPrivacy,
isSensitive: isSensitive,
isBot: isBot,
isLocked: isLocked,
isDiscoverable: isDiscoverable))
try await client?.patch(endpoint: Accounts.updateCredentials(displayName: displayName,
note: note,
privacy: postPrivacy,
isSensitive: isSensitive,
isBot: isBot,
isLocked: isLocked,
isDiscoverable: isDiscoverable))
if response?.statusCode != 200 {
saveError = true
}
@ -57,5 +57,4 @@ class EditAccountViewModel: ObservableObject {
saveError = true
}
}
}

View file

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

View file

@ -1,11 +1,11 @@
import XCTest
@testable import Account
import XCTest
final class AccountTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(Account().text, "Hello, World!")
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(Account().text, "Hello, World!")
}
}

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import SwiftUI
import Models
import Network
import SwiftUI
@MainActor
public class AppAccountViewModel: ObservableObject {
@ -15,7 +15,7 @@ public class AppAccountViewModel: ObservableObject {
public init(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 {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,14 +11,15 @@ let package = Package(
products: [
.library(
name: "DesignSystem",
targets: ["DesignSystem"]),
targets: ["DesignSystem"]
),
],
dependencies: [
.package(name: "Models", path: "../Models"),
.package(name: "Env", path: "../Env"),
.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/divadretlaw/EmojiText", from: "1.1.0")
.package(url: "https://github.com/divadretlaw/EmojiText", from: "1.1.0"),
],
targets: [
.target(
@ -29,8 +30,8 @@ let package = Package(
.product(name: "Shimmer", package: "SwiftUI-Shimmer"),
.product(name: "NukeUI", 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 SwiftUI
import NukeUI
import Models
import NukeUI
import SwiftUI
extension Account {
public extension Account {
private struct Part: Identifiable {
let id = UUID().uuidString
let value: Substring
}
public var safeDisplayName: String {
var safeDisplayName: String {
if displayName.isEmpty {
return username
}
return displayName
}
public var displayNameWithoutEmojis: String {
var displayNameWithoutEmojis: String {
var name = safeDisplayName
for emoji in emojis {
name = name.replacingOccurrences(of: ":\(emoji.shortcode):", with: "")

View file

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

View file

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

View file

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

View file

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

View file

@ -1,68 +1,68 @@
import SwiftUI
#if canImport(UIKit)
import UIKit
import UIKit
#endif
public extension View {
func applyTheme(_ theme: Theme) -> some View {
modifier(ThemeApplier(theme: theme))
}
func applyTheme(_ theme: Theme) -> some View {
modifier(ThemeApplier(theme: theme))
}
}
struct ThemeApplier: ViewModifier {
@ObservedObject var theme: Theme
func body(content: Content) -> some View {
content
.tint(theme.tintColor)
.preferredColorScheme(theme.selectedScheme == ColorScheme.dark ? .dark : .light)
#if canImport(UIKit)
.onAppear {
setWindowTint(theme.tintColor)
setWindowUserInterfaceStyle(theme.selectedScheme)
setBarsColor(theme.primaryBackgroundColor)
}
.onChange(of: theme.tintColor) { newValue in
setWindowTint(newValue)
}
.onChange(of: theme.selectedScheme) { newValue in
setWindowUserInterfaceStyle(newValue)
}
.onChange(of: theme.primaryBackgroundColor) { newValue in
setBarsColor(newValue)
}
#endif
}
@ObservedObject var theme: Theme
func body(content: Content) -> some View {
content
.tint(theme.tintColor)
.preferredColorScheme(theme.selectedScheme == ColorScheme.dark ? .dark : .light)
#if canImport(UIKit)
.onAppear {
setWindowTint(theme.tintColor)
setWindowUserInterfaceStyle(theme.selectedScheme)
setBarsColor(theme.primaryBackgroundColor)
}
.onChange(of: theme.tintColor) { newValue in
setWindowTint(newValue)
}
.onChange(of: theme.selectedScheme) { newValue in
setWindowUserInterfaceStyle(newValue)
}
.onChange(of: theme.primaryBackgroundColor) { newValue in
setBarsColor(newValue)
}
#endif
}
#if canImport(UIKit)
private func setWindowUserInterfaceStyle(_ colorScheme: ColorScheme) {
allWindows()
.forEach {
switch colorScheme {
case .dark:
$0.overrideUserInterfaceStyle = .dark
case .light:
$0.overrideUserInterfaceStyle = .light
}
}
allWindows()
.forEach {
switch colorScheme {
case .dark:
$0.overrideUserInterfaceStyle = .dark
case .light:
$0.overrideUserInterfaceStyle = .light
}
}
}
private func setWindowTint(_ color: Color) {
allWindows()
.forEach {
$0.tintColor = UIColor(color)
}
allWindows()
.forEach {
$0.tintColor = UIColor(color)
}
}
private func setBarsColor(_ color: Color) {
UINavigationBar.appearance().isTranslucent = true
UINavigationBar.appearance().barTintColor = UIColor(color)
UINavigationBar.appearance().isTranslucent = true
UINavigationBar.appearance().barTintColor = UIColor(color)
}
private func allWindows() -> [UIWindow] {
UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
}
#endif
#endif
}

View file

@ -1,6 +1,6 @@
import SwiftUI
import Shimmer
import NukeUI
import Shimmer
import SwiftUI
public struct AvatarView: View {
@Environment(\.redactionReasons) private var reasons
@ -48,21 +48,21 @@ public struct AvatarView: View {
RoundedRectangle(cornerRadius: size.cornerRadius)
.fill(.gray)
.frame(width: size.size.width, height: size.size.height)
} else {
LazyImage(url: url) { state in
if let image = state.image {
image
.resizingMode(.aspectFit)
} else if state.isLoading {
placeholderView
.shimmering()
} else {
placeholderView
}
} else {
LazyImage(url: url) { state in
if let image = state.image {
image
.resizingMode(.aspectFit)
} else if state.isLoading {
placeholderView
.shimmering()
} else {
placeholderView
}
.processors([.resize(size: size.size), .roundedCorners(radius: size.cornerRadius)])
.frame(width: size.size.width, height: size.size.height)
}
.processors([.resize(size: size.size), .roundedCorners(radius: size.cornerRadius)])
.frame(width: size.size.width, height: size.size.height)
}
}
.clipShape(clipShape)
.overlay(

View file

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

View file

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

View file

@ -40,5 +40,5 @@ public struct ScrollViewOffsetReader<Content: View>: View {
private struct OffsetPreferenceKey: PreferenceKey {
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 Models
import SwiftUI
@MainActor
extension View {
public func statusEditorToolbarItem(routeurPath: RouterPath, visibility: Models.Visibility) -> some ToolbarContent {
public extension View {
func statusEditorToolbarItem(routeurPath: RouterPath, visibility: Models.Visibility) -> some ToolbarContent {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
routeurPath.presentedSheet = .newStatusEditor(visibility: visibility)

View file

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

View file

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

View file

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

View file

@ -10,9 +10,9 @@ public class CurrentAccount: ObservableObject {
private var client: Client?
static public let shared = CurrentAccount()
public static let shared = CurrentAccount()
private init() { }
private init() {}
public func setClient(client: Client) {
self.client = client
@ -36,7 +36,7 @@ public class CurrentAccount: ObservableObject {
do {
let connections: [String] = try await client.get(endpoint: Instances.peers)
client.addConnections(connections)
} catch { }
} catch {}
}
public func fetchCurrentAccount() async {
@ -70,7 +70,7 @@ public class CurrentAccount: ObservableObject {
do {
let list: Models.List = try await client.post(endpoint: Lists.createList(title: title))
lists.append(list)
} catch { }
} catch {}
}
public func deleteList(list: Models.List) async {
@ -97,7 +97,7 @@ public class CurrentAccount: ObservableObject {
guard let client else { return nil }
do {
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
} catch {
return nil

View file

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

View file

@ -1,6 +1,6 @@
import Foundation
public enum PreferredBrowser: Int, CaseIterable {
case inAppSafari
case safari
case inAppSafari
case safari
}

View file

@ -1,10 +1,10 @@
import Foundation
import UserNotifications
import SwiftUI
import KeychainSwift
import CryptoKit
import Foundation
import KeychainSwift
import Models
import Network
import SwiftUI
import UserNotifications
@MainActor
public class PushNotificationsService: ObservableObject {
@ -36,6 +36,7 @@ public class PushNotificationsService: ObservableObject {
}
}
}
@Published public var isFollowNotificationEnabled: Bool = true
@Published public var isFavoriteNotificationEnabled: Bool = true
@Published public var isReblogNotificationEnabled: Bool = true
@ -54,7 +55,7 @@ public class PushNotificationsService: ObservableObject {
}
public func requestPushNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (_, _) in
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { _, _ in
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
@ -68,7 +69,7 @@ public class PushNotificationsService: ObservableObject {
do {
let sub: PushSubscription = try await client.get(endpoint: Push.subscription)
subscriptions.append(sub)
} catch { }
} catch {}
}
refreshSubscriptionsUI()
}
@ -80,15 +81,15 @@ public class PushNotificationsService: ObservableObject {
guard let pushToken = pushToken, isUserPushEnabled else { return }
for account in accounts {
let client = Client(server: account.server, oauthToken: account.token)
do {
var listenerURL = Constants.endpoint
listenerURL += "/push/"
listenerURL += pushToken.hexString
listenerURL += "/\(account.server)"
#if DEBUG
listenerURL += "?sandbox=true"
#endif
let sub: PushSubscription =
do {
var listenerURL = Constants.endpoint
listenerURL += "/push/"
listenerURL += pushToken.hexString
listenerURL += "/\(account.server)"
#if DEBUG
listenerURL += "?sandbox=true"
#endif
let sub: PushSubscription =
try await client.post(endpoint: Push.createSub(endpoint: listenerURL,
p256dh: key,
auth: authKey,
@ -98,9 +99,9 @@ public class PushNotificationsService: ObservableObject {
follow: isFollowNotificationEnabled,
favourite: isFavoriteNotificationEnabled,
poll: isPollNotificationEnabled))
subscriptions.append(sub)
} catch { }
}
subscriptions.append(sub)
} catch {}
}
refreshSubscriptionsUI()
}
@ -109,7 +110,7 @@ public class PushNotificationsService: ObservableObject {
let client = Client(server: account.server, oauthToken: account.token)
do {
_ = try await client.delete(endpoint: Push.subscription)
} catch { }
} catch {}
}
await fetchSubscriptions(accounts: accounts)
refreshSubscriptionsUI()
@ -133,7 +134,8 @@ public class PushNotificationsService: ObservableObject {
public var notificationsPrivateKeyAsKey: P256.KeyAgreement.PrivateKey {
if let key = keychain.get(Constants.keychainPrivateKey),
let data = Data(base64Encoded: key) {
let data = Data(base64Encoded: key)
{
do {
return try P256.KeyAgreement.PrivateKey(rawRepresentation: data)
} catch {
@ -154,7 +156,8 @@ public class PushNotificationsService: ObservableObject {
public var notificationsAuthKeyAsKey: Data {
if let key = keychain.get(Constants.keychainAuthKey),
let data = Data(base64Encoded: key) {
let data = Data(base64Encoded: key)
{
return data
} else {
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
var bytes = Data(count: byteCount)
_ = bytes.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, byteCount, $0.baseAddress!) }
@ -178,4 +181,3 @@ extension Data {
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 latestError: Error?
public init() {
}
public init() {}
public func prepareFor(urls: [URL], selectedURL: URL) async {
withAnimation {

View file

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

View file

@ -91,7 +91,7 @@ public class StreamWatcher: ObservableObject {
break
}
self.receiveMessage()
self.receiveMessage()
case .failure:
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10)) { [weak self] in

View file

@ -1,11 +1,11 @@
import SwiftUI
import Foundation
import Models
import Network
import SwiftUI
@MainActor
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()
private var client: Client?
@ -25,7 +25,7 @@ public class UserPreferences: ObservableObject {
@Published public var serverPreferences: ServerPreferences?
private init() { }
private init() {}
public func setClient(client: Client) {
self.client = client

View file

@ -11,7 +11,8 @@ let package = Package(
products: [
.library(
name: "Explore",
targets: ["Explore"]),
targets: ["Explore"]
),
],
dependencies: [
.package(name: "Account", path: "../Account"),
@ -30,8 +31,8 @@ let package = Package(
.product(name: "Models", package: "Models"),
.product(name: "Env", package: "Env"),
.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 DesignSystem
import Env
import Models
import Network
import Shimmer
import Status
import SwiftUI
public struct ExploreView: View {
@EnvironmentObject private var theme: Theme
@ -14,7 +14,7 @@ public struct ExploreView: View {
@StateObject private var viewModel = ExploreViewModel()
public init() { }
public init() {}
public var body: some View {
List {
@ -30,7 +30,7 @@ public struct ExploreView: View {
EmptyView(iconName: "magnifyingglass",
title: "Search your instance",
message: "From this screen you can search anything on \(client.server)")
.listRowBackground(theme.secondaryBackgroundColor)
.listRowBackground(theme.secondaryBackgroundColor)
} else {
if !viewModel.trendingTags.isEmpty {
trendingTagsSection
@ -64,8 +64,8 @@ public struct ExploreView: View {
suggestedTokens: $viewModel.suggestedToken,
prompt: Text("Search users, posts and tags"),
token: { token in
Text(token.rawValue)
})
Text(token.rawValue)
})
}
private var loadingView: some View {
@ -114,11 +114,11 @@ public struct ExploreView: View {
Section("Suggested Users") {
ForEach(viewModel.suggestedAccounts
.prefix(upTo: viewModel.suggestedAccounts.count > 3 ? 3 : viewModel.suggestedAccounts.count)) { account in
if let relationship = viewModel.suggestedAccountsRelationShips.first(where: { $0.id == account.id }) {
AccountsListRow(viewModel: .init(account: account, relationShip: relationship))
.listRowBackground(theme.primaryBackgroundColor)
if let relationship = viewModel.suggestedAccountsRelationShips.first(where: { $0.id == account.id }) {
AccountsListRow(viewModel: .init(account: account, relationShip: relationship))
.listRowBackground(theme.primaryBackgroundColor)
}
}
}
NavigationLink {
List {
ForEach(viewModel.suggestedAccounts) { account in
@ -145,10 +145,10 @@ public struct ExploreView: View {
Section("Trending Tags") {
ForEach(viewModel.trendingTags
.prefix(upTo: viewModel.trendingTags.count > 5 ? 5 : viewModel.trendingTags.count)) { tag in
TagRowView(tag: tag)
TagRowView(tag: tag)
.listRowBackground(theme.primaryBackgroundColor)
.padding(.vertical, 4)
}
.padding(.vertical, 4)
}
NavigationLink {
List {
ForEach(viewModel.trendingTags) { tag in
@ -174,10 +174,10 @@ public struct ExploreView: View {
Section("Trending Posts") {
ForEach(viewModel.trendingStatuses
.prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count)) { status in
StatusRowView(viewModel: .init(status: status, isCompact: false))
StatusRowView(viewModel: .init(status: status, isCompact: false))
.listRowBackground(theme.primaryBackgroundColor)
.padding(.vertical, 8)
}
.padding(.vertical, 8)
}
NavigationLink {
List {
@ -204,10 +204,10 @@ public struct ExploreView: View {
Section("Trending Links") {
ForEach(viewModel.trendingLinks
.prefix(upTo: viewModel.trendingLinks.count > 3 ? 3 : viewModel.trendingLinks.count)) { card in
StatusCardView(card: card)
StatusCardView(card: card)
.listRowBackground(theme.primaryBackgroundColor)
.padding(.vertical, 8)
}
.padding(.vertical, 8)
}
NavigationLink {
List {
ForEach(viewModel.trendingLinks) { card in
@ -228,5 +228,4 @@ public struct ExploreView: View {
.listRowBackground(theme.primaryBackgroundColor)
}
}
}

View file

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

View file

@ -11,7 +11,8 @@ let package = Package(
products: [
.library(
name: "Lists",
targets: ["Lists"]),
targets: ["Lists"]
),
],
dependencies: [
.package(name: "Network", path: "../Network"),
@ -26,8 +27,8 @@ let package = Package(
.product(name: "Network", package: "Network"),
.product(name: "Models", package: "Models"),
.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 Env
import Models
import Network
import SwiftUI
public struct ListAddAccountView: View {
@Environment(\.dismiss) private var dismiss
@ -14,7 +14,6 @@ public struct ListAddAccountView: View {
@State private var isCreateListAlertPresented: Bool = false
@State private var createListTitle: String = ""
public init(account: Account) {
_viewModel = StateObject(wrappedValue: .init(account: account))
}

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import SwiftUI
import Models
import Network
import SwiftUI
@MainActor
public class ListEditViewModel: ObservableObject {
@ -29,10 +29,10 @@ public class ListEditViewModel: ObservableObject {
func delete(account: Account) async {
guard let client else { return }
do {
let response = try await client.delete(endpoint: Lists.updateAccounts(listId: list.id, accounts: [account.id]))
let response = try await client.delete(endpoint: Lists.updateAccounts(listId: list.id, accounts: [account.id]))
if response?.statusCode == 200 {
accounts.removeAll(where: { $0.id == account.id })
}
} catch { }
} catch {}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,11 @@
import XCTest
@testable import Models
import XCTest
final class ModelsTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(Models().text, "Hello, World!")
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(Models().text, "Hello, World!")
}
}

View file

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

View file

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

View file

@ -33,7 +33,7 @@ public enum Accounts: Endpoint {
public func path() -> String {
switch self {
case .accounts(let id):
case let .accounts(id):
return "accounts/\(id)"
case .favourites:
return "favourites"
@ -41,29 +41,29 @@ public enum Accounts: Endpoint {
return "bookmarks"
case .followedTags:
return "followed_tags"
case .featuredTags(let id):
case let .featuredTags(id):
return "accounts/\(id)/featured_tags"
case .verifyCredentials:
return "accounts/verify_credentials"
case .updateCredentials:
return "accounts/update_credentials"
case .statuses(let id, _, _, _, _, _):
case let .statuses(id, _, _, _, _, _):
return "accounts/\(id)/statuses"
case .relationships:
return "accounts/relationships"
case .follow(let id, _):
case let .follow(id, _):
return "accounts/\(id)/follow"
case .unfollow(let id):
case let .unfollow(id):
return "accounts/\(id)/unfollow"
case .familiarFollowers:
return "accounts/familiar_followers"
case .suggestions:
return "suggestions"
case .following(let id, _):
case let .following(id, _):
return "accounts/\(id)/following"
case .followers(let id, _):
case let .followers(id, _):
return "accounts/\(id)/followers"
case .lists(let id):
case let .lists(id):
return "accounts/\(id)/lists"
case .preferences:
return "preferences"
@ -72,7 +72,7 @@ public enum Accounts: Endpoint {
public func queryItems() -> [URLQueryItem]? {
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] = []
if let 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: "redirect_uris", value: AppInfo.scheme),
.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 }
}
extension Endpoint {
public var jsonValue: Encodable? {
public extension Endpoint {
var jsonValue: Encodable? {
nil
}
}

View file

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

View file

@ -17,7 +17,7 @@ public enum Notifications: Endpoint {
public func queryItems() -> [URLQueryItem]? {
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) ?? []
if let types {
for type in types {

View file

@ -21,7 +21,7 @@ public enum Oauth: Endpoint {
.init(name: "response_type", value: "code"),
.init(name: "client_id", value: clientId),
.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):
return [
@ -30,7 +30,7 @@ public enum Oauth: Endpoint {
.init(name: "client_secret", value: clientSecret),
.init(name: "redirect_uri", value: AppInfo.scheme),
.init(name: "code", value: code),
.init(name: "scope", value: AppInfo.scopes)
.init(name: "scope", value: AppInfo.scopes),
]
}
}

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