mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-02-16 17:55:13 +00:00
Swiftformat
This commit is contained in:
parent
425a4eef4f
commit
ca4e80101f
39 changed files with 168 additions and 160 deletions
|
@ -62,7 +62,8 @@ struct IceCubesApp: App {
|
||||||
pushNotificationsService.handledNotification = nil
|
pushNotificationsService.handledNotification = nil
|
||||||
if appAccountsManager.currentAccount.oauthToken?.accessToken != notification?.account.token.accessToken,
|
if appAccountsManager.currentAccount.oauthToken?.accessToken != notification?.account.token.accessToken,
|
||||||
let account = appAccountsManager.availableAccounts.first(where:
|
let account = appAccountsManager.availableAccounts.first(where:
|
||||||
{ $0.oauthToken?.accessToken == notification?.account.token.accessToken }) {
|
{ $0.oauthToken?.accessToken == notification?.account.token.accessToken })
|
||||||
|
{
|
||||||
appAccountsManager.currentAccount = account
|
appAccountsManager.currentAccount = account
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||||
selectedTab = .notifications
|
selectedTab = .notifications
|
||||||
|
@ -143,7 +144,7 @@ struct IceCubesApp: App {
|
||||||
sideBarLoadedTabs.removeAll()
|
sideBarLoadedTabs.removeAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var notificationsSecondaryColumn: some View {
|
private var notificationsSecondaryColumn: some View {
|
||||||
NotificationsTab(popToRootTab: $popToRootTab, lockedType: nil)
|
NotificationsTab(popToRootTab: $popToRootTab, lockedType: nil)
|
||||||
.environment(\.isSecondaryColumn, true)
|
.environment(\.isSecondaryColumn, true)
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
import SwiftUI
|
|
||||||
import Models
|
|
||||||
import Env
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Status
|
import Env
|
||||||
|
import Models
|
||||||
import Network
|
import Network
|
||||||
|
import Status
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
public struct ReportView: View {
|
public struct ReportView: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
@EnvironmentObject private var client: Client
|
@EnvironmentObject private var client: Client
|
||||||
|
|
||||||
let status: Status
|
let status: Status
|
||||||
@State private var commentText: String = ""
|
@State private var commentText: String = ""
|
||||||
@State private var isSendingReport: Bool = false
|
@State private var isSendingReport: Bool = false
|
||||||
|
|
||||||
struct ReportSent: Decodable {
|
struct ReportSent: Decodable {
|
||||||
let id : String
|
let id: String
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
|
@ -28,7 +28,7 @@ public struct ReportView: View {
|
||||||
axis: .vertical)
|
axis: .vertical)
|
||||||
}
|
}
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
|
||||||
StatusEmbeddedView(status: status, client: .init(server: ""), routerPath: RouterPath())
|
StatusEmbeddedView(status: status, client: .init(server: ""), routerPath: RouterPath())
|
||||||
.allowsHitTesting(false)
|
.allowsHitTesting(false)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
@ -45,9 +45,9 @@ public struct ReportView: View {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let _: ReportSent =
|
let _: ReportSent =
|
||||||
try await client.post(endpoint: Statuses.report(accountId: status.account.id,
|
try await client.post(endpoint: Statuses.report(accountId: status.account.id,
|
||||||
statusId: status.id,
|
statusId: status.id,
|
||||||
comment: commentText))
|
comment: commentText))
|
||||||
dismiss()
|
dismiss()
|
||||||
isSendingReport = false
|
isSendingReport = false
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -62,7 +62,7 @@ public struct ReportView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
Button {
|
Button {
|
||||||
dismiss()
|
dismiss()
|
||||||
|
|
|
@ -21,7 +21,7 @@ struct ContentSettingsView: View {
|
||||||
|
|
||||||
Section("settings.content.media") {
|
Section("settings.content.media") {
|
||||||
Toggle(isOn: $userPreferences.showAltTextForMedia) {
|
Toggle(isOn: $userPreferences.showAltTextForMedia) {
|
||||||
Text("settings.content.media.show.alt")
|
Text("settings.content.media.show.alt")
|
||||||
}
|
}
|
||||||
}.listRowBackground(theme.primaryBackgroundColor)
|
}.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
|
||||||
|
@ -69,7 +69,6 @@ struct ContentSettingsView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
|
||||||
}
|
}
|
||||||
.navigationTitle("settings.content.navigation-title")
|
.navigationTitle("settings.content.navigation-title")
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
import Models
|
import Models
|
||||||
|
import Network
|
||||||
import Status
|
import Status
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Network
|
|
||||||
|
|
||||||
struct DisplaySettingsView: View {
|
struct DisplaySettingsView: View {
|
||||||
typealias FontState = Theme.FontState
|
typealias FontState = Theme.FontState
|
||||||
|
@ -67,10 +67,10 @@ struct DisplaySettingsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationDestination(isPresented: $isFontSelectorPresented, destination: { FontPicker() })
|
.navigationDestination(isPresented: $isFontSelectorPresented, destination: { FontPicker() })
|
||||||
|
|
||||||
Toggle("settings.display.font.rounded", isOn: $userPreferences.useSFRoundedFont)
|
Toggle("settings.display.font.rounded", isOn: $userPreferences.useSFRoundedFont)
|
||||||
.disabled(userPreferences.chosenFont != nil)
|
.disabled(userPreferences.chosenFont != nil)
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
Slider(value: $userPreferences.fontSizeScale, in: 0.5 ... 1.5, step: 0.1)
|
Slider(value: $userPreferences.fontSizeScale, in: 0.5 ... 1.5, step: 0.1)
|
||||||
Text("settings.display.font.scaling-\(String(format: "%.1f", userPreferences.fontSizeScale))")
|
Text("settings.display.font.scaling-\(String(format: "%.1f", userPreferences.fontSizeScale))")
|
||||||
|
@ -81,7 +81,7 @@ struct DisplaySettingsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
|
||||||
Section("settings.display.section.display") {
|
Section("settings.display.section.display") {
|
||||||
Picker("settings.display.avatar.position", selection: $theme.avatarPosition) {
|
Picker("settings.display.avatar.position", selection: $theme.avatarPosition) {
|
||||||
ForEach(Theme.AvatarPosition.allCases, id: \.rawValue) { position in
|
ForEach(Theme.AvatarPosition.allCases, id: \.rawValue) { position in
|
||||||
|
|
|
@ -44,9 +44,9 @@ struct IconSelectorView: View {
|
||||||
let icons: [Icon]
|
let icons: [Icon]
|
||||||
|
|
||||||
static let items = [
|
static let items = [
|
||||||
IconSelector(title:"settings.app.icon.official".localized, icons: [.primary, .alt1, .alt2, .alt3, .alt4, .alt5, .alt6, .alt7, .alt8,
|
IconSelector(title: "settings.app.icon.official".localized, icons: [.primary, .alt1, .alt2, .alt3, .alt4, .alt5, .alt6, .alt7, .alt8,
|
||||||
.alt9, .alt10, .alt11, .alt12, .alt13, .alt14,
|
.alt9, .alt10, .alt11, .alt12, .alt13, .alt14,
|
||||||
.alt15, .alt16, .alt17, .alt18, .alt19, .alt25]),
|
.alt15, .alt16, .alt17, .alt18, .alt19, .alt25]),
|
||||||
IconSelector(title: "\("settings.app.icon.designed-by".localized) Albert Kinng", icons: [.alt20, .alt21, .alt22, .alt23, .alt24]),
|
IconSelector(title: "\("settings.app.icon.designed-by".localized) Albert Kinng", icons: [.alt20, .alt21, .alt22, .alt23, .alt24]),
|
||||||
IconSelector(title: "\("settings.app.icon.designed-by".localized) Dan van Moll", icons: [.alt26, .alt27, .alt28]),
|
IconSelector(title: "\("settings.app.icon.designed-by".localized) Dan van Moll", icons: [.alt26, .alt27, .alt28]),
|
||||||
IconSelector(title: "\("settings.app.icon.designed-by".localized) @te6-in (GitHub)", icons: [.alt29, .alt30, .alt31, .alt32]),
|
IconSelector(title: "\("settings.app.icon.designed-by".localized) @te6-in (GitHub)", icons: [.alt29, .alt30, .alt31, .alt32]),
|
||||||
|
@ -108,7 +108,7 @@ struct IconSelectorView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
var localized: String {
|
var localized: String {
|
||||||
return NSLocalizedString(self, comment:"")
|
return NSLocalizedString(self, comment: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ struct SettingsTabs: View {
|
||||||
}
|
}
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func logoutAccount(account: AppAccount) async {
|
private func logoutAccount(account: AppAccount) async {
|
||||||
if let token = account.oauthToken,
|
if let token = account.oauthToken,
|
||||||
let sub = pushNotifications.subscriptions.first(where: { $0.account.token == token })
|
let sub = pushNotifications.subscriptions.first(where: { $0.account.token == token })
|
||||||
|
@ -235,7 +235,7 @@ struct SettingsTabs: View {
|
||||||
AddAccountView()
|
AddAccountView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var editAccountButton: some View {
|
private var editAccountButton: some View {
|
||||||
Button(role: isEditingAccount ? .none : .destructive) {
|
Button(role: isEditingAccount ? .none : .destructive) {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import AppAccount
|
import AppAccount
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import Env
|
import Env
|
||||||
|
import Intents
|
||||||
import KeychainSwift
|
import KeychainSwift
|
||||||
import Models
|
import Models
|
||||||
import UIKit
|
|
||||||
import UserNotifications
|
|
||||||
import Intents
|
|
||||||
import Network
|
import Network
|
||||||
import Notifications
|
import Notifications
|
||||||
|
import UIKit
|
||||||
|
import UserNotifications
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class NotificationService: UNNotificationServiceExtension {
|
class NotificationService: UNNotificationServiceExtension {
|
||||||
|
@ -62,13 +62,12 @@ class NotificationService: UNNotificationServiceExtension {
|
||||||
bestAttemptContent.body = notification.body.escape()
|
bestAttemptContent.body = notification.body.escape()
|
||||||
bestAttemptContent.userInfo["plaintext"] = plaintextData
|
bestAttemptContent.userInfo["plaintext"] = plaintextData
|
||||||
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "glass.caf"))
|
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "glass.caf"))
|
||||||
|
|
||||||
|
|
||||||
let preferences = UserPreferences.shared
|
let preferences = UserPreferences.shared
|
||||||
preferences.pushNotificationsCount += 1
|
preferences.pushNotificationsCount += 1
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
@ -81,9 +80,10 @@ class NotificationService: UNNotificationServiceExtension {
|
||||||
if let (data, _) = try? await URLSession.shared.data(for: .init(url: url)) {
|
if let (data, _) = try? await URLSession.shared.data(for: .init(url: url)) {
|
||||||
if let image = UIImage(data: data) {
|
if let image = UIImage(data: data) {
|
||||||
try? image.pngData()?.write(to: fileURL)
|
try? image.pngData()?.write(to: fileURL)
|
||||||
|
|
||||||
if let remoteNotification = await toRemoteNotification(localNotification: notification),
|
if let remoteNotification = await toRemoteNotification(localNotification: notification),
|
||||||
let type = remoteNotification.supportedType {
|
let type = remoteNotification.supportedType
|
||||||
|
{
|
||||||
let intent = buildMessageIntent(remoteNotification: remoteNotification,
|
let intent = buildMessageIntent(remoteNotification: remoteNotification,
|
||||||
currentUser: bestAttemptContent.userInfo["i"] as? String ?? "",
|
currentUser: bestAttemptContent.userInfo["i"] as? String ?? "",
|
||||||
avatarURL: fileURL)
|
avatarURL: fileURL)
|
||||||
|
@ -111,7 +111,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func toRemoteNotification(localNotification: MastodonPushNotification) async -> Models.Notification? {
|
private func toRemoteNotification(localNotification: MastodonPushNotification) async -> Models.Notification? {
|
||||||
do {
|
do {
|
||||||
if let account = AppAccountsManager.shared.availableAccounts.first(where: { $0.oauthToken?.accessToken == localNotification.accessToken }) {
|
if let account = AppAccountsManager.shared.availableAccounts.first(where: { $0.oauthToken?.accessToken == localNotification.accessToken }) {
|
||||||
|
@ -124,10 +124,11 @@ class NotificationService: UNNotificationServiceExtension {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildMessageIntent(remoteNotification: Models.Notification,
|
private func buildMessageIntent(remoteNotification: Models.Notification,
|
||||||
currentUser: String,
|
currentUser: String,
|
||||||
avatarURL: URL) -> INSendMessageIntent {
|
avatarURL: URL) -> INSendMessageIntent
|
||||||
|
{
|
||||||
let handle = INPersonHandle(value: remoteNotification.account.id, type: .unknown)
|
let handle = INPersonHandle(value: remoteNotification.account.id, type: .unknown)
|
||||||
let avatar = INImage(url: avatarURL)
|
let avatar = INImage(url: avatarURL)
|
||||||
let sender = INPerson(personHandle: handle,
|
let sender = INPerson(personHandle: handle,
|
||||||
|
|
|
@ -19,6 +19,7 @@ public class AppAccountViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var roundedAvatar: UIImage?
|
@Published var roundedAvatar: UIImage?
|
||||||
|
|
||||||
var acct: String {
|
var acct: String {
|
||||||
|
@ -39,31 +40,32 @@ public class AppAccountViewModel: ObservableObject {
|
||||||
do {
|
do {
|
||||||
account = Self.accountsCache[appAccount.id]
|
account = Self.accountsCache[appAccount.id]
|
||||||
roundedAvatar = Self.avatarsCache[appAccount.id]
|
roundedAvatar = Self.avatarsCache[appAccount.id]
|
||||||
|
|
||||||
account = try await client.get(endpoint: Accounts.verifyCredentials)
|
account = try await client.get(endpoint: Accounts.verifyCredentials)
|
||||||
Self.accountsCache[appAccount.id] = account
|
Self.accountsCache[appAccount.id] = account
|
||||||
|
|
||||||
if let account {
|
if let account {
|
||||||
await refreshAvatar(account: account)
|
await refreshAvatar(account: account)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func refreshAcct(account: Account) {
|
private func refreshAcct(account: Account) {
|
||||||
do {
|
do {
|
||||||
if appAccount.accountName == nil {
|
if appAccount.accountName == nil {
|
||||||
appAccount.accountName = "\(account.acct)@\(appAccount.server)"
|
appAccount.accountName = "\(account.acct)@\(appAccount.server)"
|
||||||
try appAccount.save()
|
try appAccount.save()
|
||||||
}
|
}
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func refreshAvatar(account: Account) async {
|
private func refreshAvatar(account: Account) async {
|
||||||
if let (data, _) = try? await URLSession.shared.data(from: account.avatar),
|
if let (data, _) = try? await URLSession.shared.data(from: account.avatar),
|
||||||
let image = UIImage(data: data)?.roundedImage {
|
let image = UIImage(data: data)?.roundedImage
|
||||||
roundedAvatar = image
|
{
|
||||||
Self.avatarsCache[account.id] = image
|
roundedAvatar = image
|
||||||
|
Self.avatarsCache[account.id] = image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ public struct AppAccountsSelectorView: View {
|
||||||
.frame(width: 9, height: 9)
|
.frame(width: 9, height: 9)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.accessibilityLabel("accessibility.app-account.selector.accounts")
|
.accessibilityLabel("accessibility.app-account.selector.accounts")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -4,8 +4,8 @@ import Network
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public class CurrentAccount: ObservableObject {
|
public class CurrentAccount: ObservableObject {
|
||||||
static private var accountsCache: [String: Account] = [:]
|
private static var accountsCache: [String: Account] = [:]
|
||||||
|
|
||||||
@Published public private(set) var account: Account?
|
@Published public private(set) var account: Account?
|
||||||
@Published public private(set) var lists: [List] = []
|
@Published public private(set) var lists: [List] = []
|
||||||
@Published public private(set) var tags: [Tag] = []
|
@Published public private(set) var tags: [Tag] = []
|
||||||
|
|
|
@ -18,12 +18,12 @@ public extension EnvironmentValues {
|
||||||
get { self[SecondaryColumnKey.self] }
|
get { self[SecondaryColumnKey.self] }
|
||||||
set { self[SecondaryColumnKey.self] = newValue }
|
set { self[SecondaryColumnKey.self] = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
var extraLeadingInset: CGFloat {
|
var extraLeadingInset: CGFloat {
|
||||||
get { self[ExtraLeadingInset.self] }
|
get { self[ExtraLeadingInset.self] }
|
||||||
set { self[ExtraLeadingInset.self] = newValue }
|
set { self[ExtraLeadingInset.self] = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
var isCompact: Bool {
|
var isCompact: Bool {
|
||||||
get { self[IsCompact.self] }
|
get { self[IsCompact.self] }
|
||||||
set { self[IsCompact.self] = newValue }
|
set { self[IsCompact.self] = newValue }
|
||||||
|
|
|
@ -30,21 +30,21 @@ public class PushNotificationsService: NSObject, ObservableObject {
|
||||||
static let keychainAuthKey = "notifications_auth_key"
|
static let keychainAuthKey = "notifications_auth_key"
|
||||||
static let keychainPrivateKey = "notifications_private_key"
|
static let keychainPrivateKey = "notifications_private_key"
|
||||||
}
|
}
|
||||||
|
|
||||||
public static let shared = PushNotificationsService()
|
public static let shared = PushNotificationsService()
|
||||||
|
|
||||||
public private(set) var subscriptions: [PushNotificationSubscriptionSettings] = []
|
public private(set) var subscriptions: [PushNotificationSubscriptionSettings] = []
|
||||||
|
|
||||||
@Published public var pushToken: Data?
|
@Published public var pushToken: Data?
|
||||||
|
|
||||||
@Published public var handledNotification: HandledNotification?
|
@Published public var handledNotification: HandledNotification?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
UNUserNotificationCenter.current().delegate = self
|
UNUserNotificationCenter.current().delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
private var keychain: KeychainSwift {
|
private var keychain: KeychainSwift {
|
||||||
let keychain = KeychainSwift()
|
let keychain = KeychainSwift()
|
||||||
#if !DEBUG && !targetEnvironment(simulator)
|
#if !DEBUG && !targetEnvironment(simulator)
|
||||||
|
@ -135,18 +135,19 @@ public class PushNotificationsService: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PushNotificationsService: UNUserNotificationCenterDelegate {
|
extension PushNotificationsService: UNUserNotificationCenterDelegate {
|
||||||
public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
|
public func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
|
||||||
guard let plaintext = response.notification.request.content.userInfo["plaintext"] as? Data,
|
guard let plaintext = response.notification.request.content.userInfo["plaintext"] as? Data,
|
||||||
let mastodonPushNotification = try? JSONDecoder().decode(MastodonPushNotification.self, from: plaintext),
|
let mastodonPushNotification = try? JSONDecoder().decode(MastodonPushNotification.self, from: plaintext),
|
||||||
let account = subscriptions.first(where: { $0.account.token.accessToken == mastodonPushNotification.accessToken }) else {
|
let account = subscriptions.first(where: { $0.account.token.accessToken == mastodonPushNotification.accessToken })
|
||||||
return
|
else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
let client = Client(server: account.account.server, oauthToken: account.account.token)
|
let client = Client(server: account.account.server, oauthToken: account.account.token)
|
||||||
let notification: Models.Notification =
|
let notification: Models.Notification =
|
||||||
try await client.get(endpoint: Notifications.notification(id:String(mastodonPushNotification.notificationID)))
|
try await client.get(endpoint: Notifications.notification(id: String(mastodonPushNotification.notificationID)))
|
||||||
self.handledNotification = .init(account: account.account, notification: notification)
|
handledNotification = .init(account: account.account, notification: notification)
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,10 +42,10 @@ public enum StatusAction: String, CaseIterable, Identifiable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func color(themeTintColor: Color, useThemeColor: Bool, outside: Bool) -> Color {
|
public func color(themeTintColor: Color, useThemeColor: Bool, outside: Bool) -> Color {
|
||||||
if (useThemeColor) {
|
if useThemeColor {
|
||||||
return outside ? themeTintColor : .gray
|
return outside ? themeTintColor : .gray
|
||||||
}
|
}
|
||||||
|
|
||||||
switch self {
|
switch self {
|
||||||
case .none:
|
case .none:
|
||||||
return .gray
|
return .gray
|
||||||
|
|
|
@ -38,7 +38,7 @@ public class UserPreferences: ObservableObject {
|
||||||
|
|
||||||
@AppStorage("show_tab_label_iphone") public var showiPhoneTabLabel = true
|
@AppStorage("show_tab_label_iphone") public var showiPhoneTabLabel = true
|
||||||
@AppStorage("show_alt_text_for_media") public var showAltTextForMedia = true
|
@AppStorage("show_alt_text_for_media") public var showAltTextForMedia = true
|
||||||
|
|
||||||
@AppStorage("show_second_column_ipad") public var showiPadSecondaryColumn = true
|
@AppStorage("show_second_column_ipad") public var showiPadSecondaryColumn = true
|
||||||
|
|
||||||
@AppStorage("swipeactions-status-trailing-right") public var swipeActionsStatusTrailingRight = StatusAction.favorite
|
@AppStorage("swipeactions-status-trailing-right") public var swipeActionsStatusTrailingRight = StatusAction.favorite
|
||||||
|
@ -47,9 +47,9 @@ public class UserPreferences: ObservableObject {
|
||||||
@AppStorage("swipeactions-status-leading-right") public var swipeActionsStatusLeadingRight = StatusAction.none
|
@AppStorage("swipeactions-status-leading-right") public var swipeActionsStatusLeadingRight = StatusAction.none
|
||||||
@AppStorage("swipeactions-use-theme-color") public var swipeActionsUseThemeColor = false
|
@AppStorage("swipeactions-use-theme-color") public var swipeActionsUseThemeColor = false
|
||||||
@AppStorage("swipeactions-icon-style") public var swipeActionsIconStyle: SwipeActionsIconStyle = .iconWithText
|
@AppStorage("swipeactions-icon-style") public var swipeActionsIconStyle: SwipeActionsIconStyle = .iconWithText
|
||||||
|
|
||||||
@AppStorage("font_use_sf_rounded") public var useSFRoundedFont = false
|
@AppStorage("font_use_sf_rounded") public var useSFRoundedFont = false
|
||||||
|
|
||||||
@AppStorage("requested_review") public var requestedReview = false
|
@AppStorage("requested_review") public var requestedReview = false
|
||||||
|
|
||||||
public enum SwipeActionsIconStyle: String, CaseIterable {
|
public enum SwipeActionsIconStyle: String, CaseIterable {
|
||||||
|
|
|
@ -4,7 +4,7 @@ public final class Account: Codable, Identifiable, Equatable, Hashable {
|
||||||
public static func == (lhs: Account, rhs: Account) -> Bool {
|
public static func == (lhs: Account, rhs: Account) -> Bool {
|
||||||
lhs.id == rhs.id
|
lhs.id == rhs.id
|
||||||
}
|
}
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(id)
|
hasher.combine(id)
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,6 @@ public final class Account: Codable, Identifiable, Equatable, Hashable {
|
||||||
return header.lastPathComponent != "missing.png"
|
return header.lastPathComponent != "missing.png"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public init(id: String, username: String, displayName: String, avatar: URL, header: URL, acct: String, note: HTMLString, createdAt: ServerDate, followersCount: Int, followingCount: Int, statusesCount: Int, lastStatusAt: String? = nil, fields: [Account.Field], locked: Bool, emojis: [Emoji], url: URL? = nil, source: Account.Source? = nil, bot: Bool, discoverable: Bool? = nil) {
|
public init(id: String, username: String, displayName: String, avatar: URL, header: URL, acct: String, note: HTMLString, createdAt: ServerDate, followersCount: Int, followingCount: Int, statusesCount: Int, lastStatusAt: String? = nil, fields: [Account.Field], locked: Bool, emojis: [Emoji], url: URL? = nil, source: Account.Source? = nil, bot: Bool, discoverable: Bool? = nil) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.username = username
|
self.username = username
|
||||||
|
@ -77,7 +76,7 @@ public final class Account: Codable, Identifiable, Equatable, Hashable {
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.discoverable = discoverable
|
self.discoverable = discoverable
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func placeholder() -> Account {
|
public static func placeholder() -> Account {
|
||||||
.init(id: UUID().uuidString,
|
.init(id: UUID().uuidString,
|
||||||
username: "Username",
|
username: "Username",
|
||||||
|
|
|
@ -31,7 +31,7 @@ public struct Relationship: Codable {
|
||||||
notifying: false)
|
notifying: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Relationship {
|
public extension Relationship {
|
||||||
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)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension String {
|
public extension String {
|
||||||
public func escape() -> String {
|
func escape() -> String {
|
||||||
return replacingOccurrences(of: "&", with: "&")
|
return replacingOccurrences(of: "&", with: "&")
|
||||||
.replacingOccurrences(of: "<", with: "<")
|
.replacingOccurrences(of: "<", with: "<")
|
||||||
.replacingOccurrences(of: ">", with: ">")
|
.replacingOccurrences(of: ">", with: ">")
|
||||||
|
@ -10,7 +10,7 @@ extension String {
|
||||||
.replacingOccurrences(of: "'", with: "’")
|
.replacingOccurrences(of: "'", with: "’")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func URLSafeBase64ToBase64() -> String {
|
func URLSafeBase64ToBase64() -> String {
|
||||||
var base64 = replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
|
var base64 = replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
|
||||||
let countMod4 = count % 4
|
let countMod4 = count % 4
|
||||||
|
|
||||||
|
|
|
@ -5,32 +5,32 @@
|
||||||
// Created by Jérôme Danthinne on 31/01/2023.
|
// Created by Jérôme Danthinne on 31/01/2023.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Models
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Models
|
||||||
|
|
||||||
extension Array where Element == Models.Notification {
|
extension Array where Element == Models.Notification {
|
||||||
func consolidated(selectedType: Models.Notification.NotificationType?) async -> [ConsolidatedNotification] {
|
func consolidated(selectedType: Models.Notification.NotificationType?) async -> [ConsolidatedNotification] {
|
||||||
await withCheckedContinuation({ result in
|
await withCheckedContinuation { result in
|
||||||
DispatchQueue.global().async {
|
DispatchQueue.global().async {
|
||||||
let notifications: [ConsolidatedNotification] =
|
let notifications: [ConsolidatedNotification] =
|
||||||
Dictionary(grouping: self) { $0.consolidationId(selectedType: selectedType) }
|
Dictionary(grouping: self) { $0.consolidationId(selectedType: selectedType) }
|
||||||
.values
|
.values
|
||||||
.compactMap { notifications in
|
.compactMap { notifications in
|
||||||
guard let notification = notifications.first,
|
guard let notification = notifications.first,
|
||||||
let supportedType = notification.supportedType
|
let supportedType = notification.supportedType
|
||||||
else { return nil }
|
else { return nil }
|
||||||
|
|
||||||
return ConsolidatedNotification(notifications: notifications,
|
return ConsolidatedNotification(notifications: notifications,
|
||||||
type: supportedType,
|
type: supportedType,
|
||||||
createdAt: notification.createdAt,
|
createdAt: notification.createdAt,
|
||||||
accounts: notifications.map(\.account),
|
accounts: notifications.map(\.account),
|
||||||
status: notification.status)
|
status: notification.status)
|
||||||
}
|
}
|
||||||
.sorted {
|
.sorted {
|
||||||
$0.createdAt.asDate > $1.createdAt.asDate
|
$0.createdAt.asDate > $1.createdAt.asDate
|
||||||
}
|
}
|
||||||
result.resume(returning: notifications)
|
result.resume(returning: notifications)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@ import DesignSystem
|
||||||
import EmojiText
|
import EmojiText
|
||||||
import Env
|
import Env
|
||||||
import Models
|
import Models
|
||||||
|
import Network
|
||||||
import Status
|
import Status
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Network
|
|
||||||
|
|
||||||
struct NotificationRowView: View {
|
struct NotificationRowView: View {
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
@ -28,7 +28,8 @@ struct NotificationRowView: View {
|
||||||
makeMainLabel(type: notification.type)
|
makeMainLabel(type: notification.type)
|
||||||
makeContent(type: notification.type)
|
makeContent(type: notification.type)
|
||||||
if notification.type == .follow_request,
|
if notification.type == .follow_request,
|
||||||
followRequests.map(\.id).contains(notification.accounts[0].id) {
|
followRequests.map(\.id).contains(notification.accounts[0].id)
|
||||||
|
{
|
||||||
FollowRequestButtons(account: notification.accounts[0])
|
FollowRequestButtons(account: notification.accounts[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ extension Models.Notification.NotificationType {
|
||||||
return "notifications.label.update"
|
return "notifications.label.update"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func notificationKey() -> String {
|
public func notificationKey() -> String {
|
||||||
switch self {
|
switch self {
|
||||||
case .status:
|
case .status:
|
||||||
|
|
|
@ -93,13 +93,14 @@ public struct StatusDetailView: View {
|
||||||
.navigationTitle(viewModel.title)
|
.navigationTitle(viewModel.title)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeStatusesListView(statuses: [Status], date: Date) -> some View {
|
private func makeStatusesListView(statuses: [Status], date _: Date) -> some View {
|
||||||
ForEach(statuses) { status in
|
ForEach(statuses) { status in
|
||||||
var isReplyToPrevious: Bool = false
|
var isReplyToPrevious: Bool = false
|
||||||
if let index = statuses.firstIndex(where: { $0.id == status.id }),
|
if let index = statuses.firstIndex(where: { $0.id == status.id }),
|
||||||
index > 0,
|
index > 0,
|
||||||
statuses[index - 1].id == status.inReplyToId {
|
statuses[index - 1].id == status.inReplyToId
|
||||||
|
{
|
||||||
isReplyToPrevious = true
|
isReplyToPrevious = true
|
||||||
}
|
}
|
||||||
let viewModel: StatusRowViewModel = .init(status: status,
|
let viewModel: StatusRowViewModel = .init(status: status,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Models
|
||||||
import NukeUI
|
import NukeUI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusEditorMediaView: View {
|
struct StatusEditorMediaView: View {
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
@EnvironmentObject private var currentInstance: CurrentInstance
|
@EnvironmentObject private var currentInstance: CurrentInstance
|
||||||
@ObservedObject var viewModel: StatusEditorViewModel
|
@ObservedObject var viewModel: StatusEditorViewModel
|
||||||
|
|
|
@ -21,7 +21,7 @@ enum StatusEditorUTTypeSupported: String, CaseIterable {
|
||||||
case gif = "public.gif"
|
case gif = "public.gif"
|
||||||
case gif2 = "com.compuserve.gif"
|
case gif2 = "com.compuserve.gif"
|
||||||
case quickTimeMovie = "com.apple.quicktime-movie"
|
case quickTimeMovie = "com.apple.quicktime-movie"
|
||||||
|
|
||||||
case uiimage = "com.apple.uikit.image"
|
case uiimage = "com.apple.uikit.image"
|
||||||
|
|
||||||
static func types() -> [UTType] {
|
static func types() -> [UTType] {
|
||||||
|
@ -57,8 +57,8 @@ enum StatusEditorUTTypeSupported: String, CaseIterable {
|
||||||
if let image = result as? UIImage {
|
if let image = result as? UIImage {
|
||||||
return image
|
return image
|
||||||
} else if let imageURL = result as? URL,
|
} else if let imageURL = result as? URL,
|
||||||
let data = try? Data(contentsOf: imageURL),
|
let data = try? Data(contentsOf: imageURL),
|
||||||
let image = UIImage(data: data)
|
let image = UIImage(data: data)
|
||||||
{
|
{
|
||||||
return image
|
return image
|
||||||
} else if let data = result as? Data,
|
} else if let data = result as? Data,
|
||||||
|
|
|
@ -7,9 +7,9 @@ import Models
|
||||||
import Network
|
import Network
|
||||||
import NukeUI
|
import NukeUI
|
||||||
import PhotosUI
|
import PhotosUI
|
||||||
|
import StoreKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
import StoreKit
|
|
||||||
|
|
||||||
public struct StatusEditorView: View {
|
public struct StatusEditorView: View {
|
||||||
@EnvironmentObject private var appAccounts: AppAccountsManager
|
@EnvironmentObject private var appAccounts: AppAccountsManager
|
||||||
|
@ -204,8 +204,8 @@ public struct StatusEditorView: View {
|
||||||
object: nil)
|
object: nil)
|
||||||
if !viewModel.mode.isInShareExtension && !preferences.requestedReview {
|
if !viewModel.mode.isInShareExtension && !preferences.requestedReview {
|
||||||
if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
|
if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
|
||||||
SKStoreReviewController.requestReview(in: scene)
|
SKStoreReviewController.requestReview(in: scene)
|
||||||
}
|
}
|
||||||
preferences.requestedReview = true
|
preferences.requestedReview = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import EmojiText
|
import EmojiText
|
||||||
import Models
|
|
||||||
import SwiftUI
|
|
||||||
import Env
|
import Env
|
||||||
|
import Models
|
||||||
import Network
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct StatusEmbeddedView: View {
|
public struct StatusEmbeddedView: View {
|
||||||
|
@ -27,7 +27,7 @@ public struct StatusEmbeddedView: View {
|
||||||
client: client,
|
client: client,
|
||||||
routerPath: routerPath,
|
routerPath: routerPath,
|
||||||
showActions: false))
|
showActions: false))
|
||||||
.environment(\.isCompact, true)
|
.environment(\.isCompact, true)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
import Models
|
import Models
|
||||||
|
import Network
|
||||||
import Shimmer
|
import Shimmer
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Network
|
|
||||||
|
|
||||||
public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
|
public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
|
@ -11,7 +11,7 @@ public struct StatusRowView: View {
|
||||||
@Environment(\.isCompact) private var isCompact: Bool
|
@Environment(\.isCompact) private var isCompact: Bool
|
||||||
|
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
||||||
@StateObject var viewModel: StatusRowViewModel
|
@StateObject var viewModel: StatusRowViewModel
|
||||||
|
|
||||||
public init(viewModel: StatusRowViewModel) {
|
public init(viewModel: StatusRowViewModel) {
|
||||||
|
@ -41,7 +41,8 @@ public struct StatusRowView: View {
|
||||||
}
|
}
|
||||||
HStack(alignment: .top, spacing: .statusColumnsSpacing) {
|
HStack(alignment: .top, spacing: .statusColumnsSpacing) {
|
||||||
if !isCompact,
|
if !isCompact,
|
||||||
theme.avatarPosition == .leading {
|
theme.avatarPosition == .leading
|
||||||
|
{
|
||||||
Button {
|
Button {
|
||||||
viewModel.routerPath.navigate(to: .accountDetailWithAccount(account: status.account))
|
viewModel.routerPath.navigate(to: .accountDetailWithAccount(account: status.account))
|
||||||
} label: {
|
} label: {
|
||||||
|
@ -180,7 +181,7 @@ public struct StatusRowView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var remoteContentLoadingView: some View {
|
private var remoteContentLoadingView: some View {
|
||||||
ZStack(alignment: .center) {
|
ZStack(alignment: .center) {
|
||||||
VStack {
|
VStack {
|
||||||
|
|
|
@ -12,7 +12,7 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
let isFocused: Bool
|
let isFocused: Bool
|
||||||
let isRemote: Bool
|
let isRemote: Bool
|
||||||
let showActions: Bool
|
let showActions: Bool
|
||||||
|
|
||||||
@Published var favoritesCount: Int
|
@Published var favoritesCount: Int
|
||||||
@Published var isFavorited: Bool
|
@Published var isFavorited: Bool
|
||||||
@Published var isReblogged: Bool
|
@Published var isReblogged: Bool
|
||||||
|
@ -31,7 +31,7 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
|
|
||||||
@Published var favoriters: [Account] = []
|
@Published var favoriters: [Account] = []
|
||||||
@Published var rebloggers: [Account] = []
|
@Published var rebloggers: [Account] = []
|
||||||
|
|
||||||
@Published var isLoadingRemoteContent: Bool = false
|
@Published var isLoadingRemoteContent: Bool = false
|
||||||
@Published var localStatusId: String?
|
@Published var localStatusId: String?
|
||||||
@Published var localStatus: Status?
|
@Published var localStatus: Status?
|
||||||
|
@ -334,7 +334,7 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchRemoteStatus() async -> Bool {
|
func fetchRemoteStatus() async -> Bool {
|
||||||
guard isRemote, let remoteStatusURL = URL(string: status.reblog?.url ?? status.url ?? "") else { return false }
|
guard isRemote, let remoteStatusURL = URL(string: status.reblog?.url ?? status.url ?? "") else { return false }
|
||||||
isLoadingRemoteContent = true
|
isLoadingRemoteContent = true
|
||||||
|
@ -344,8 +344,8 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
following: nil),
|
following: nil),
|
||||||
forceVersion: .v2)
|
forceVersion: .v2)
|
||||||
if let status = results?.statuses.first {
|
if let status = results?.statuses.first {
|
||||||
self.localStatusId = status.id
|
localStatusId = status.id
|
||||||
self.localStatus = status
|
localStatus = status
|
||||||
isLoadingRemoteContent = false
|
isLoadingRemoteContent = false
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -63,7 +63,8 @@ struct StatusRowActionsView: View {
|
||||||
ForEach(Actions.allCases, id: \.self) { action in
|
ForEach(Actions.allCases, id: \.self) { action in
|
||||||
if action == .share {
|
if action == .share {
|
||||||
if let urlString = viewModel.status.reblog?.url ?? viewModel.status.url,
|
if let urlString = viewModel.status.reblog?.url ?? viewModel.status.url,
|
||||||
let url = URL(string: urlString) {
|
let url = URL(string: urlString)
|
||||||
|
{
|
||||||
ShareLink(item: url,
|
ShareLink(item: url,
|
||||||
subject: Text(viewModel.status.reblog?.account.safeDisplayName ?? viewModel.status.account.safeDisplayName),
|
subject: Text(viewModel.status.reblog?.account.safeDisplayName ?? viewModel.status.account.safeDisplayName),
|
||||||
message: Text(viewModel.status.reblog?.content.asRawText ?? viewModel.status.content.asRawText)) {
|
message: Text(viewModel.status.reblog?.content.asRawText ?? viewModel.status.content.asRawText)) {
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import SwiftUI
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Models
|
|
||||||
import Env
|
import Env
|
||||||
|
import Models
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusRowContentView: View {
|
struct StatusRowContentView: View {
|
||||||
@Environment(\.redactionReasons) private var reasons
|
@Environment(\.redactionReasons) private var reasons
|
||||||
@Environment(\.isCompact) private var isCompact
|
@Environment(\.isCompact) private var isCompact
|
||||||
|
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
||||||
let status: AnyStatus
|
let status: AnyStatus
|
||||||
@ObservedObject var viewModel: StatusRowViewModel
|
@ObservedObject var viewModel: StatusRowViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if !status.spoilerText.asRawText.isEmpty {
|
if !status.spoilerText.asRawText.isEmpty {
|
||||||
StatusRowSpoilerView(status: status, displaySpoiler: $viewModel.displaySpoiler)
|
StatusRowSpoilerView(status: status, displaySpoiler: $viewModel.displaySpoiler)
|
||||||
|
@ -26,7 +26,8 @@ struct StatusRowContentView: View {
|
||||||
|
|
||||||
if !reasons.contains(.placeholder),
|
if !reasons.contains(.placeholder),
|
||||||
!isCompact,
|
!isCompact,
|
||||||
(viewModel.isEmbedLoading || viewModel.embeddedStatus != nil) {
|
viewModel.isEmbedLoading || viewModel.embeddedStatus != nil
|
||||||
|
{
|
||||||
StatusEmbeddedView(status: viewModel.embeddedStatus ?? Status.placeholder(),
|
StatusEmbeddedView(status: viewModel.embeddedStatus ?? Status.placeholder(),
|
||||||
client: viewModel.client,
|
client: viewModel.client,
|
||||||
routerPath: viewModel.routerPath)
|
routerPath: viewModel.routerPath)
|
||||||
|
@ -35,7 +36,7 @@ struct StatusRowContentView: View {
|
||||||
.shimmering(active: viewModel.isEmbedLoading)
|
.shimmering(active: viewModel.isEmbedLoading)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !status.mediaAttachments.isEmpty {
|
if !status.mediaAttachments.isEmpty {
|
||||||
HStack {
|
HStack {
|
||||||
StatusRowMediaPreviewView(attachments: status.mediaAttachments,
|
StatusRowMediaPreviewView(attachments: status.mediaAttachments,
|
||||||
|
@ -47,7 +48,7 @@ struct StatusRowContentView: View {
|
||||||
}
|
}
|
||||||
.padding(.vertical, 4)
|
.padding(.vertical, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let card = status.card,
|
if let card = status.card,
|
||||||
!viewModel.isEmbedLoading,
|
!viewModel.isEmbedLoading,
|
||||||
!isCompact,
|
!isCompact,
|
||||||
|
|
|
@ -57,7 +57,8 @@ struct StatusRowContextMenu: View {
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
if let urlString = viewModel.status.reblog?.url ?? viewModel.status.url,
|
if let urlString = viewModel.status.reblog?.url ?? viewModel.status.url,
|
||||||
let url = URL(string: urlString) {
|
let url = URL(string: urlString)
|
||||||
|
{
|
||||||
ShareLink(item: url,
|
ShareLink(item: url,
|
||||||
subject: Text(viewModel.status.reblog?.account.safeDisplayName ?? viewModel.status.account.safeDisplayName),
|
subject: Text(viewModel.status.reblog?.account.safeDisplayName ?? viewModel.status.account.safeDisplayName),
|
||||||
message: Text(viewModel.status.reblog?.content.asRawText ?? viewModel.status.content.asRawText)) {
|
message: Text(viewModel.status.reblog?.content.asRawText ?? viewModel.status.content.asRawText)) {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import SwiftUI
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Models
|
import Models
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusRowHeaderView: View {
|
struct StatusRowHeaderView: View {
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
||||||
let status: AnyStatus
|
let status: AnyStatus
|
||||||
let viewModel: StatusRowViewModel
|
let viewModel: StatusRowViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Button {
|
Button {
|
||||||
|
@ -23,7 +23,7 @@ struct StatusRowHeaderView: View {
|
||||||
.accessibilityElement()
|
.accessibilityElement()
|
||||||
.accessibilityLabel(Text("\(status.account.displayName)"))
|
.accessibilityLabel(Text("\(status.account.displayName)"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func accountView(status: AnyStatus) -> some View {
|
private func accountView(status: AnyStatus) -> some View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
|
@ -46,7 +46,7 @@ struct StatusRowHeaderView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var threadIcon: some View {
|
private var threadIcon: some View {
|
||||||
if viewModel.status.reblog?.inReplyToAccountId != nil || viewModel.status.inReplyToAccountId != nil {
|
if viewModel.status.reblog?.inReplyToAccountId != nil || viewModel.status.inReplyToAccountId != nil {
|
||||||
|
@ -57,7 +57,7 @@ struct StatusRowHeaderView: View {
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var contextMenuButton: some View {
|
private var contextMenuButton: some View {
|
||||||
Menu {
|
Menu {
|
||||||
StatusRowContextMenu(viewModel: viewModel)
|
StatusRowContextMenu(viewModel: viewModel)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import SwiftUI
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusRowReblogView: View {
|
struct StatusRowReblogView: View {
|
||||||
let viewModel: StatusRowViewModel
|
let viewModel: StatusRowViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if viewModel.status.reblog != nil {
|
if viewModel.status.reblog != nil {
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
|
@ -27,4 +27,3 @@ struct StatusRowReblogView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import SwiftUI
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusRowReplyView: View {
|
struct StatusRowReplyView: View {
|
||||||
let viewModel: StatusRowViewModel
|
let viewModel: StatusRowViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let accountId = viewModel.status.inReplyToAccountId,
|
if let accountId = viewModel.status.inReplyToAccountId,
|
||||||
let mention = viewModel.status.mentions.first(where: { $0.id == accountId })
|
let mention = viewModel.status.mentions.first(where: { $0.id == accountId })
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import SwiftUI
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Models
|
import Models
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusRowSpoilerView: View {
|
struct StatusRowSpoilerView: View {
|
||||||
let status: AnyStatus
|
let status: AnyStatus
|
||||||
@Binding var displaySpoiler: Bool
|
@Binding var displaySpoiler: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
Text("⚠︎")
|
Text("⚠︎")
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import SwiftUI
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
import Models
|
import Models
|
||||||
import DesignSystem
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusRowSwipeView: View {
|
struct StatusRowSwipeView: View {
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
@EnvironmentObject private var preferences: UserPreferences
|
@EnvironmentObject private var preferences: UserPreferences
|
||||||
|
|
||||||
enum Mode {
|
enum Mode {
|
||||||
case leading, trailing
|
case leading, trailing
|
||||||
}
|
}
|
||||||
|
|
||||||
let viewModel: StatusRowViewModel
|
let viewModel: StatusRowViewModel
|
||||||
let mode: Mode
|
let mode: Mode
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
switch mode {
|
switch mode {
|
||||||
case .leading:
|
case .leading:
|
||||||
|
@ -22,7 +22,7 @@ struct StatusRowSwipeView: View {
|
||||||
trailingSwipeActions
|
trailingSwipeActions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var trailingSwipeActions: some View {
|
private var trailingSwipeActions: some View {
|
||||||
if preferences.swipeActionsStatusTrailingRight != StatusAction.none, !viewModel.isRemote {
|
if preferences.swipeActionsStatusTrailingRight != StatusAction.none, !viewModel.isRemote {
|
||||||
|
@ -105,10 +105,10 @@ struct StatusRowSwipeView: View {
|
||||||
makeSwipeLabel(action: action, style: preferences.swipeActionsIconStyle)
|
makeSwipeLabel(action: action, style: preferences.swipeActionsIconStyle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func makeSwipeLabel(action: StatusAction, style: UserPreferences.SwipeActionsIconStyle) -> some View {
|
private func makeSwipeLabel(action: StatusAction, style: UserPreferences.SwipeActionsIconStyle) -> some View {
|
||||||
switch (style) {
|
switch style {
|
||||||
case .iconOnly:
|
case .iconOnly:
|
||||||
Label(action.displayName(isReblogged: viewModel.isReblogged, isFavorited: viewModel.isFavorited, isBookmarked: viewModel.isBookmarked), systemImage: action.iconName(isReblogged: viewModel.isReblogged, isFavorited: viewModel.isFavorited, isBookmarked: viewModel.isBookmarked))
|
Label(action.displayName(isReblogged: viewModel.isReblogged, isFavorited: viewModel.isFavorited, isBookmarked: viewModel.isBookmarked), systemImage: action.iconName(isReblogged: viewModel.isReblogged, isFavorited: viewModel.isFavorited, isBookmarked: viewModel.isBookmarked))
|
||||||
.labelStyle(.iconOnly)
|
.labelStyle(.iconOnly)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import SwiftUI
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Models
|
import Models
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusRowTextView: View {
|
struct StatusRowTextView: View {
|
||||||
let status: AnyStatus
|
let status: AnyStatus
|
||||||
let viewModel: StatusRowViewModel
|
let viewModel: StatusRowViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
EmojiTextApp(status.content, emojis: status.emojis, language: status.language)
|
EmojiTextApp(status.content, emojis: status.emojis, language: status.language)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import SwiftUI
|
|
||||||
import Models
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
|
import Models
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusRowTranslateView: View {
|
struct StatusRowTranslateView: View {
|
||||||
@EnvironmentObject private var preferences: UserPreferences
|
@EnvironmentObject private var preferences: UserPreferences
|
||||||
|
|
||||||
let status: AnyStatus
|
let status: AnyStatus
|
||||||
@ObservedObject var viewModel: StatusRowViewModel
|
@ObservedObject var viewModel: StatusRowViewModel
|
||||||
|
|
||||||
private var shouldShowTranslateButton: Bool {
|
private var shouldShowTranslateButton: Bool {
|
||||||
let statusLang = viewModel.getStatusLang()
|
let statusLang = viewModel.getStatusLang()
|
||||||
|
|
||||||
|
@ -22,10 +22,10 @@ struct StatusRowTranslateView: View {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let userLang = preferences.serverPreferences?.postLanguage,
|
if let userLang = preferences.serverPreferences?.postLanguage,
|
||||||
shouldShowTranslateButton
|
shouldShowTranslateButton
|
||||||
{
|
{
|
||||||
Button {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
|
|
|
@ -248,7 +248,7 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
canStreamEvents = true
|
canStreamEvents = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// As this is a long runnign task we need to ensure that the user didn't changed the timeline filter.
|
// As this is a long runnign task we need to ensure that the user didn't changed the timeline filter.
|
||||||
guard initialTimeline == timeline else {
|
guard initialTimeline == timeline else {
|
||||||
canStreamEvents = true
|
canStreamEvents = true
|
||||||
|
|
Loading…
Reference in a new issue