Merge remote-tracking branch 'upstream/main' into zh-Hant-localization

This commit is contained in:
sh95014 2024-03-23 11:14:58 -07:00
commit 45878a4d91
162 changed files with 4918 additions and 2275 deletions

View file

@ -940,7 +940,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.10.32;
MARKETING_VERSION = 1.10.33;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesNotifications";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -975,7 +975,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.10.32;
MARKETING_VERSION = 1.10.33;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesNotifications";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -1011,7 +1011,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.10.32;
MARKETING_VERSION = 1.10.33;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -1045,7 +1045,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.10.32;
MARKETING_VERSION = 1.10.33;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -1225,7 +1225,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.10.32;
MARKETING_VERSION = 1.10.33;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
PRODUCT_NAME = "Ice Cubes";
SDKROOT = auto;
@ -1279,7 +1279,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.10.32;
MARKETING_VERSION = 1.10.33;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
PRODUCT_NAME = "Ice Cubes";
SDKROOT = auto;
@ -1314,7 +1314,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.10.32;
MARKETING_VERSION = 1.10.33;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -1349,7 +1349,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.10.32;
MARKETING_VERSION = 1.10.33;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;

View file

@ -68,8 +68,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Nuke",
"state" : {
"revision" : "15fde63470d782c897816a74bdd516a907e33147",
"version" : "12.3.0"
"revision" : "8ecbfc886da39bccb01c34abef5f2ff4073ad633",
"version" : "12.4.0"
}
},
{

View file

@ -140,6 +140,12 @@ extension View {
.presentationDetents([.medium])
.presentationBackground(.thinMaterial)
.withEnvironments()
case .accountEditInfo:
EditAccountView()
.withEnvironments()
case .accountFiltersList:
FiltersListView()
.withEnvironments()
}
}
}

View file

@ -49,7 +49,7 @@ struct AppView: View {
} else if UIDevice.current.userInterfaceIdiom == .vision {
return Tab.visionOSTab()
}
return sidebarTabs.tabs.map{ $0.tab }
return sidebarTabs.tabs.map { $0.tab }
}
var tabBarView: some View {

View file

@ -72,8 +72,8 @@ private struct SafariRouter: ViewModifier {
}
#if !os(visionOS)
@MainActor
@Observable private class InAppSafariManager: NSObject, SFSafariViewControllerDelegate {
@MainActor
@Observable private class InAppSafariManager: NSObject, SFSafariViewControllerDelegate {
var windowScene: UIWindowScene?
let viewController: UIViewController = .init()
var window: UIWindow?
@ -123,7 +123,7 @@ private struct SafariRouter: ViewModifier {
window = nil
}
}
}
}
#endif
private struct WindowReader: UIViewRepresentable {

View file

@ -1,7 +1,7 @@
import SwiftUI
import Env
import AppAccount
import DesignSystem
import Env
import SwiftUI
@MainActor
struct NavigationSheet<Content: View>: View {

View file

@ -1,8 +1,8 @@
import SwiftUI
import Env
import AppAccount
import DesignSystem
import Env
import Network
import SwiftUI
@MainActor
struct NavigationTab<Content: View>: View {

View file

@ -60,9 +60,9 @@ struct NotificationsTab: View {
}
}
}
.onChange(of: selectedTab, { _, newValue in
.onChange(of: selectedTab) { _, _ in
clearNotifications()
})
}
.onChange(of: pushNotificationsService.handledNotification) { _, newValue in
if let newValue, let type = newValue.notification.supportedType {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {

View file

@ -17,9 +17,8 @@ struct AccountSettingsView: View {
@Environment(Theme.self) private var theme
@Environment(AppAccountsManager.self) private var appAccountsManager
@Environment(Client.self) private var client
@Environment(RouterPath.self) private var routerPath
@State private var isEditingAccount: Bool = false
@State private var isEditingFilters: Bool = false
@State private var cachedPostsCount: Int = 0
@State private var timelineCache = TimelineCache()
@ -30,7 +29,7 @@ struct AccountSettingsView: View {
Form {
Section {
Button {
isEditingAccount = true
routerPath.presentedSheet = .accountFiltersList
} label: {
Label("account.action.edit-info", systemImage: "pencil")
.frame(maxWidth: .infinity, alignment: .leading)
@ -40,7 +39,7 @@ struct AccountSettingsView: View {
if currentInstance.isFiltersSupported {
Button {
isEditingFilters = true
routerPath.presentedSheet = .accountFiltersList
} label: {
Label("account.action.edit-filters", systemImage: "line.3.horizontal.decrease.circle")
.frame(maxWidth: .infinity, alignment: .leading)
@ -96,12 +95,6 @@ struct AccountSettingsView: View {
}
.listRowBackground(theme.primaryBackgroundColor)
}
.sheet(isPresented: $isEditingAccount, content: {
EditAccountView()
})
.sheet(isPresented: $isEditingFilters, content: {
FiltersListView()
})
.toolbar {
ToolbarItem(placement: .principal) {
HStack {

View file

@ -5,8 +5,8 @@ import Models
import Network
import NukeUI
import SwiftUI
import UserNotifications
import Timeline
import UserNotifications
@MainActor
struct ContentSettingsView: View {

View file

@ -7,6 +7,7 @@ import Observation
import StatusKit
import SwiftUI
@MainActor
@Observable class DisplaySettingsLocalValues {
var tintColor = Theme.shared.tintColor
var primaryBackgroundColor = Theme.shared.primaryBackgroundColor

View file

@ -1,8 +1,8 @@
import SwiftUI
import SwiftData
import Models
import Env
import DesignSystem
import Env
import Models
import SwiftData
import SwiftUI
struct RecenTagsSettingView: View {
@Environment(\.modelContext) private var context

View file

@ -1,8 +1,8 @@
import SwiftUI
import SwiftData
import Models
import Env
import DesignSystem
import Env
import Models
import SwiftData
import SwiftUI
struct RemoteTimelinesSettingView: View {
@Environment(\.modelContext) private var context

View file

@ -2,6 +2,7 @@ import DesignSystem
import Env
import SwiftUI
@MainActor
struct SidebarEntriesSettingsView: View {
@Environment(Theme.self) private var theme
@Environment(UserPreferences.self) private var userPreferences

View file

@ -1,8 +1,8 @@
import SwiftUI
import SwiftData
import Models
import Env
import DesignSystem
import Env
import Models
import SwiftData
import SwiftUI
struct TagsGroupSettingView: View {
@Environment(\.modelContext) private var context

View file

@ -71,7 +71,7 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
case .links:
NavigationTab { TrendingLinksListView(cards: []) }
case .post:
VStack { }
VStack {}
case .other:
EmptyView()
}
@ -114,7 +114,6 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
Label("explore.section.trending.links", systemImage: iconName)
case .other:
EmptyView()
}
}
@ -158,6 +157,7 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
}
}
@MainActor
@Observable
class SidebarTabs {
struct SidedebarTab: Hashable, Codable {
@ -203,6 +203,7 @@ class SidebarTabs {
}
}
@MainActor
@Observable
class iOSTabs {
enum TabEntries: String {

View file

@ -95,7 +95,7 @@ struct TimelineTab: View {
}
switch newValue {
case let .tagGroup(title, _, _):
if let group = tagGroups.first(where: { $0.title == title}) {
if let group = tagGroups.first(where: { $0.title == title }) {
selectedTagGroup = group
}
default:
@ -212,7 +212,7 @@ struct TimelineTab: View {
@ViewBuilder
private var pinButton: some View {
let index = pinnedFilters.firstIndex(where: { $0.id == timeline.id})
let index = pinnedFilters.firstIndex(where: { $0.id == timeline.id })
Button {
withAnimation {
if let index {

View file

@ -1,7 +1,7 @@
import SwiftUI
import Env
import AppAccount
import DesignSystem
import Env
import SwiftUI
@MainActor
struct ToolbarTab: ToolbarContent {
@ -17,7 +17,8 @@ struct ToolbarTab: ToolbarContent {
statusEditorToolbarItem(routerPath: routerPath,
visibility: userPreferences.postVisibility)
if UIDevice.current.userInterfaceIdiom != .pad ||
(UIDevice.current.userInterfaceIdiom == .pad && horizontalSizeClass == .compact) {
(UIDevice.current.userInterfaceIdiom == .pad && horizontalSizeClass == .compact)
{
ToolbarItem(placement: .navigationBarLeading) {
AppAccountsSelectorView(routerPath: routerPath)
}

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,7 @@ extension NotificationService {
var _plaintext: Data?
do {
_plaintext = try AES.GCM.open(sealedBox, using: key)
} catch { }
} catch {}
guard let plaintext = _plaintext else {
return nil
}

View file

@ -2,11 +2,11 @@ import Account
import AppAccount
import DesignSystem
import Env
import Models
import Network
import StatusKit
import SwiftUI
import UIKit
import Models
class ShareViewController: UIViewController {
override func viewDidLoad() {

View file

@ -31,7 +31,7 @@ let package = Package(
.product(name: "Models", package: "Models"),
.product(name: "StatusKit", package: "StatusKit"),
.product(name: "Env", package: "Env"),
.product(name: "ButtonKit", package: "ButtonKit")
.product(name: "ButtonKit", package: "ButtonKit"),
],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency"),

View file

@ -37,7 +37,7 @@ public struct AccountDetailContextMenu: View {
Task {
do {
viewModel.relationship = try await client.post(endpoint: Accounts.unblock(id: account.id))
} catch { }
} catch {}
}
} label: {
Label("account.action.unblock", systemImage: "person.crop.circle.badge.exclamationmark")
@ -55,7 +55,7 @@ public struct AccountDetailContextMenu: View {
Task {
do {
viewModel.relationship = try await client.post(endpoint: Accounts.unmute(id: account.id))
} catch { }
} catch {}
}
} label: {
Label("account.action.unmute", systemImage: "speaker")
@ -67,7 +67,7 @@ public struct AccountDetailContextMenu: View {
Task {
do {
viewModel.relationship = try await client.post(endpoint: Accounts.mute(id: account.id, json: MuteData(duration: duration.rawValue)))
} catch { }
} catch {}
}
}
}
@ -86,7 +86,7 @@ public struct AccountDetailContextMenu: View {
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
notify: false,
reblogs: relationship.showingReblogs))
} catch { }
} catch {}
}
} label: {
Label("account.action.notify-disable", systemImage: "bell.fill")
@ -98,7 +98,7 @@ public struct AccountDetailContextMenu: View {
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
notify: true,
reblogs: relationship.showingReblogs))
} catch { }
} catch {}
}
} label: {
Label("account.action.notify-enable", systemImage: "bell")
@ -111,7 +111,7 @@ public struct AccountDetailContextMenu: View {
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
notify: relationship.notifying,
reblogs: false))
} catch { }
} catch {}
}
} label: {
Label("account.action.reboosts-hide", image: "Rocket.Fill")
@ -123,7 +123,7 @@ public struct AccountDetailContextMenu: View {
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
notify: relationship.notifying,
reblogs: true))
} catch { }
} catch {}
}
} label: {
Label("account.action.reboosts-show", image: "Rocket")

View file

@ -207,6 +207,7 @@ struct AccountDetailHeaderView: View {
.foregroundStyle(.secondary)
.textSelection(.enabled)
.accessibilityRespondsToUserInteraction(false)
movedToView
joinedAtView
}
.accessibilityElement(children: .contain)
@ -311,6 +312,17 @@ struct AccountDetailHeaderView: View {
}
}
@ViewBuilder
private var movedToView: some View {
if let movedTo = viewModel.account?.moved {
Button("account.movedto.redirect-\("@\(movedTo.acct)")") {
routerPath.navigate(to: .accountDetailWithAccount(account: movedTo))
}
.font(.scaledCallout)
.foregroundColor(.accentColor)
}
}
@ViewBuilder
private func makeNoteView(_ note: String) -> some View {
VStack(alignment: .leading, spacing: 4) {

View file

@ -23,9 +23,6 @@ public struct AccountDetailView: View {
@State private var viewModel: AccountDetailViewModel
@State private var isCurrentUser: Bool = false
@State private var showBlockConfirmation: Bool = false
@State private var isEditingAccount: Bool = false
@State private var isEditingFilters: Bool = false
@State private var isEditingRelationshipNote: Bool = false
@State private var displayTitle: Bool = false
@ -136,20 +133,14 @@ public struct AccountDetailView: View {
viewModel.handleEvent(event: latestEvent, currentAccount: currentAccount)
}
}
.onChange(of: isEditingAccount) { _, newValue in
if !newValue {
.onChange(of: routerPath.presentedSheet) { oldValue, newValue in
if oldValue == .accountEditInfo || newValue == .accountEditInfo {
Task {
await viewModel.fetchAccount()
await preferences.refreshServerPreferences()
}
}
}
.sheet(isPresented: $isEditingAccount, content: {
EditAccountView()
})
.sheet(isPresented: $isEditingFilters, content: {
FiltersListView()
})
.sheet(isPresented: $isEditingRelationshipNote, content: {
EditRelationshipNoteView(accountDetailViewModel: viewModel)
})
@ -220,7 +211,6 @@ public struct AccountDetailView: View {
AvatarView(account.avatar, config: .badge)
.padding(.leading, -4)
.accessibilityLabel(account.safeDisplayName)
}
.accessibilityAddTraits(.isImage)
.buttonStyle(.plain)
@ -288,7 +278,6 @@ public struct AccountDetailView: View {
routerPath.presentedSheet = .mentionStatusEditor(account: account,
visibility: preferences.postVisibility)
#endif
}
} label: {
Image(systemName: "arrowshape.turn.up.left")
@ -308,7 +297,7 @@ public struct AccountDetailView: View {
if isCurrentUser {
Button {
isEditingAccount = true
routerPath.presentedSheet = .accountEditInfo
} label: {
Label("account.action.edit-info", systemImage: "pencil")
}
@ -323,7 +312,7 @@ public struct AccountDetailView: View {
if currentInstance.isFiltersSupported {
Button {
isEditingFilters = true
routerPath.presentedSheet = .accountFiltersList
} label: {
Label("account.action.edit-filters", systemImage: "line.3.horizontal.decrease.circle")
}
@ -370,7 +359,7 @@ public struct AccountDetailView: View {
Task {
do {
viewModel.relationship = try await client.post(endpoint: Accounts.block(id: account.id))
} catch { }
} catch {}
}
}
}
@ -382,6 +371,7 @@ public struct AccountDetailView: View {
}
extension View {
@MainActor
func applyAccountDetailsRowStyle(theme: Theme) -> some View {
listRowInsets(.init())
.listRowSeparator(.hidden)

View file

@ -151,7 +151,7 @@ import SwiftUI
self.familiarFollowers = familiarFollowers?.first?.accounts ?? []
}
func fetchNewestStatuses(pullToRefresh: Bool) async {
func fetchNewestStatuses(pullToRefresh _: Bool) async {
guard let client else { return }
do {
statusesState = .loading
@ -166,7 +166,7 @@ import SwiftUI
pinned: nil))
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
if selectedTab == .boosts {
boosts = statuses.filter{ $0.reblog != nil }
boosts = statuses.filter { $0.reblog != nil }
}
if selectedTab == .statuses {
pinned =
@ -206,8 +206,8 @@ import SwiftUI
pinned: nil))
statuses.append(contentsOf: newStatuses)
if selectedTab == .boosts {
let newBoosts = statuses.filter{ $0.reblog != nil }
self.boosts.append(contentsOf: newBoosts)
let newBoosts = statuses.filter { $0.reblog != nil }
boosts.append(contentsOf: newBoosts)
}
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
if selectedTab == .boosts {
@ -253,7 +253,8 @@ import SwiftUI
if let event = event as? StreamEventUpdate {
if event.status.account.id == currentAccount.account?.id {
if (event.status.inReplyToId == nil && selectedTab == .statuses) ||
(event.status.inReplyToId != nil && selectedTab == .replies) {
(event.status.inReplyToId != nil && selectedTab == .replies)
{
statuses.insert(event.status, at: 0)
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
}

View file

@ -153,3 +153,13 @@ public struct AccountsListView: View {
}
}
}
#Preview {
List {
AccountsListRow(viewModel: .init(account: .placeholder(),
relationShip: .placeholder()))
}
.listStyle(.plain)
.withPreviewsEnv()
.environment(Theme.shared)
}

View file

@ -1,8 +1,8 @@
import Models
import Network
import Observation
import SwiftUI
import OSLog
import SwiftUI
public enum AccountsListMode {
case following(accountId: String), followers(accountId: String)
@ -144,8 +144,6 @@ public enum AccountsListMode {
relationships: relationships,
nextPageState: .none)
}
} catch {
}
} catch {}
}
}

View file

@ -2,8 +2,8 @@ import DesignSystem
import Env
import Models
import Network
import SwiftUI
import NukeUI
import SwiftUI
@MainActor
public struct EditAccountView: View {
@ -14,7 +14,7 @@ public struct EditAccountView: View {
@State private var viewModel = EditAccountViewModel()
public init() { }
public init() {}
public var body: some View {
NavigationStack {

View file

@ -1,9 +1,9 @@
import Models
import Network
import Observation
import SwiftUI
import PhotosUI
import StatusKit
import SwiftUI
@MainActor
@Observable class EditAccountViewModel {
@ -33,12 +33,13 @@ import StatusKit
var isPhotoPickerPresented: Bool = false {
didSet {
if !isPhotoPickerPresented && mediaPickers.isEmpty {
if !isPhotoPickerPresented, mediaPickers.isEmpty {
isChangingAvatar = false
isChangingHeader = false
}
}
}
var isChangingAvatar: Bool = false
var isChangingHeader: Bool = false

View file

@ -4,8 +4,8 @@ import Foundation
import Models
import Network
import Observation
import SwiftUI
import OSLog
import SwiftUI
@MainActor
@Observable public class FollowButtonViewModel {

View file

@ -1,7 +1,7 @@
import DesignSystem
import Env
import Models
import SwiftUI
import Env
public struct ListsListView: View {
@Environment(CurrentAccount.self) private var currentAccount
@ -43,4 +43,3 @@ public struct ListsListView: View {
.navigationBarTitleDisplayMode(.inline)
}
}

View file

@ -1,9 +1,9 @@
import StatusKit
import Network
import SwiftUI
import DesignSystem
import Env
import Models
import DesignSystem
import Network
import StatusKit
import SwiftUI
@MainActor
public struct AccountStatusesListView: View {

View file

@ -1,8 +1,8 @@
import SwiftUI
import Models
import StatusKit
import Network
import Env
import Models
import Network
import StatusKit
import SwiftUI
@MainActor
@Observable
@ -40,7 +40,7 @@ public class AccountStatusesListViewModel: StatusesFetcher {
self.mode = mode
}
public func fetchNewestStatuses(pullToRefresh: Bool) async {
public func fetchNewestStatuses(pullToRefresh _: Bool) async {
guard let client else { return }
statusesState = .loading
do {
@ -63,11 +63,7 @@ public class AccountStatusesListViewModel: StatusesFetcher {
nextPageState: nextPage?.maxId != nil ? .hasNextPage : .none)
}
public func statusDidAppear(status: Status) {
public func statusDidAppear(status _: Status) {}
}
public func statusDidDisappear(status: Status) {
}
public func statusDidDisappear(status _: Status) {}
}

View file

@ -1,7 +1,7 @@
import DesignSystem
import Env
import Models
import SwiftUI
import Env
public struct FollowedTagsListView: View {
@Environment(CurrentAccount.self) private var currentAccount
@ -32,4 +32,3 @@ public struct FollowedTagsListView: View {
.navigationBarTitleDisplayMode(.inline)
}
}

View file

@ -33,11 +33,11 @@ public struct AppAccountsSelectorView: View {
public init(routerPath: RouterPath,
accountCreationEnabled: Bool = true,
avatarConfig: AvatarView.FrameConfig = .badge)
avatarConfig: AvatarView.FrameConfig? = nil)
{
self.routerPath = routerPath
self.accountCreationEnabled = accountCreationEnabled
self.avatarConfig = avatarConfig
self.avatarConfig = avatarConfig ?? .badge
}
public var body: some View {

View file

@ -205,12 +205,12 @@ struct ConversationMessageView: View {
.frame(height: 200)
.contentShape(Rectangle())
.onTapGesture {
#if targetEnvironment(macCatalyst) || os(visionOS)
#if targetEnvironment(macCatalyst) || os(visionOS)
openWindow(value: WindowDestinationMedia.mediaViewer(attachments: [attachement],
selectedAttachment: attachement))
#else
#else
quickLook.prepareFor(selectedMediaAttachment: attachement, mediaAttachments: [attachement])
#endif
#endif
}
}

View file

@ -48,7 +48,7 @@ public struct ConversationsListView: View {
Divider()
}
} else if conversations.isEmpty, !viewModel.isLoadingFirstPage, !viewModel.isError {
EmptyView(iconName: "tray",
PlaceholderView(iconName: "tray",
title: "conversations.empty.title",
message: "conversations.empty.message")
} else if viewModel.isError {

View file

@ -19,7 +19,7 @@ let package = Package(
dependencies: [
.package(name: "Models", path: "../Models"),
.package(name: "Env", path: "../Env"),
.package(url: "https://github.com/kean/Nuke", from: "12.0.0"),
.package(url: "https://github.com/kean/Nuke", from: "12.4.0"),
.package(url: "https://github.com/divadretlaw/EmojiText", from: "4.0.0"),
],
targets: [

View file

@ -201,4 +201,3 @@ public struct ThreadsLight: ColorSet {
public init() {}
}

View file

@ -2,7 +2,7 @@ import Combine
import UIKit
@Observable
public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable {
@MainActor public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable {
public var window: UIWindow?
#if os(visionOS)
public private(set) var windowWidth: CGFloat = 0
@ -47,7 +47,7 @@ public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable {
}
private static var observedSceneDelegate: Set<SceneDelegate> = []
private static let observer = Task {
private static let observer = Task { @MainActor in
while true {
try? await Task.sleep(for: .seconds(0.1))
for delegate in observedSceneDelegate {
@ -70,7 +70,6 @@ public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable {
delegate.windowHeight = newHeight
}
#endif
}
}
}

View file

@ -1,8 +1,10 @@
import Combine
import SwiftUI
@Observable public class Theme {
class ThemeStorage {
@MainActor
@Observable
public final class Theme {
final class ThemeStorage {
enum ThemeKey: String {
case colorScheme, tint, label, primaryBackground, secondaryBackground
case avatarPosition2, avatarShape2, statusActionsDisplay, statusDisplayStyle
@ -167,12 +169,14 @@ import SwiftUI
public var tintColor: Color {
didSet {
themeStorage.tintColor = tintColor
computeContrastingTintColor()
}
}
public var primaryBackgroundColor: Color {
didSet {
themeStorage.primaryBackgroundColor = primaryBackgroundColor
computeContrastingTintColor()
}
}
@ -185,6 +189,31 @@ import SwiftUI
public var labelColor: Color {
didSet {
themeStorage.labelColor = labelColor
computeContrastingTintColor()
}
}
public private(set) var contrastingTintColor: Color
// set contrastingTintColor to either labelColor or primaryBackgroundColor, whichever contrasts
// better against the tintColor
private func computeContrastingTintColor() {
func luminance(_ color: Color.Resolved) -> Float {
return 0.299 * color.red + 0.587 * color.green + 0.114 * color.blue
}
let resolvedTintColor = tintColor.resolve(in: .init())
let resolvedLabelColor = labelColor.resolve(in: .init())
let resolvedPrimaryBackgroundColor = primaryBackgroundColor.resolve(in: .init())
let tintLuminance = luminance(resolvedTintColor)
let labelLuminance = luminance(resolvedLabelColor)
let primaryBackgroundLuminance = luminance(resolvedPrimaryBackgroundColor)
if abs(tintLuminance - labelLuminance) > abs(tintLuminance - primaryBackgroundLuminance) {
contrastingTintColor = labelColor
} else {
contrastingTintColor = primaryBackgroundColor
}
}
@ -281,6 +310,7 @@ import SwiftUI
primaryBackgroundColor = themeStorage.primaryBackgroundColor
secondaryBackgroundColor = themeStorage.secondaryBackgroundColor
labelColor = themeStorage.labelColor
contrastingTintColor = .red // real work done in computeContrastingTintColor()
avatarPosition = themeStorage.avatarPosition
avatarShape = themeStorage.avatarShape
storedSet = themeStorage.storedSet
@ -293,6 +323,8 @@ import SwiftUI
chosenFontData = themeStorage.chosenFontData
statusActionSecondary = themeStorage.statusActionSecondary
selectedSet = storedSet
computeContrastingTintColor()
}
public static var allColorSet: [ColorSet] {
@ -310,7 +342,7 @@ import SwiftUI
ConstellationLight(),
ConstellationDark(),
ThreadsLight(),
ThreadsDark()
ThreadsDark(),
]
}

View file

@ -4,7 +4,7 @@ import SwiftUI
#endif
public extension View {
func applyTheme(_ theme: Theme) -> some View {
@MainActor func applyTheme(_ theme: Theme) -> some View {
modifier(ThemeApplier(theme: theme))
}
}
@ -77,16 +77,14 @@ struct ThemeApplier: ViewModifier {
}
private func setWindowUserInterfaceStyle(_ userInterfaceStyle: UIUserInterfaceStyle) {
allWindows()
.forEach {
$0.overrideUserInterfaceStyle = userInterfaceStyle
for window in allWindows() {
window.overrideUserInterfaceStyle = userInterfaceStyle
}
}
private func setWindowTint(_ color: Color) {
allWindows()
.forEach {
$0.tintColor = UIColor(color)
for window in allWindows() {
window.tintColor = UIColor(color)
}
}

View file

@ -3,7 +3,7 @@ import SwiftUI
public struct CancelToolbarItem: ToolbarContent {
@Environment(\.dismiss) private var dismiss
public init() { }
public init() {}
public var body: some ToolbarContent {
ToolbarItem(placement: .navigationBarLeading) {

View file

@ -4,6 +4,7 @@ import Nuke
import NukeUI
import SwiftUI
@MainActor
struct AccountPopoverView: View {
let account: Account
let theme: Theme // using `@Environment(Theme.self) will crash the SwiftUI preview

View file

@ -33,7 +33,8 @@ public struct AvatarView: View {
self.config = config
}
public struct FrameConfig: Equatable {
@MainActor
public struct FrameConfig: Equatable, Sendable {
public let size: CGSize
public var width: CGFloat { size.width }
public var height: CGFloat { size.height }

View file

@ -1,32 +0,0 @@
import SwiftUI
public struct EmptyView: View {
public let iconName: String
public let title: LocalizedStringKey
public let message: LocalizedStringKey
public init(iconName: String, title: LocalizedStringKey, message: LocalizedStringKey) {
self.iconName = iconName
self.title = title
self.message = message
}
public var body: some View {
VStack {
Image(systemName: iconName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxHeight: 50)
Text(title)
.font(.scaledTitle)
.padding(.top, 16)
Text(message)
.font(.scaledSubheadline)
.multilineTextAlignment(.center)
.foregroundStyle(.secondary)
}
.padding(.top, 100)
.padding(.layoutPadding)
.fixedSize(horizontal: false, vertical: true)
}
}

View file

@ -42,3 +42,9 @@ public struct ErrorView: View {
}
}
}
#Preview {
ErrorView(title: "Error",
message: "Error loading. Please try again",
buttonTitle: "Retry") {}
}

View file

@ -1,10 +1,11 @@
import SwiftUI
@MainActor
public struct NextPageView: View {
@State private var isLoadingNextPage: Bool = false
@State private var showRetry: Bool = false
let loadNextPage: (() async throws -> Void)
let loadNextPage: () async throws -> Void
public init(loadNextPage: @escaping (() async throws -> Void)) {
self.loadNextPage = loadNextPage
@ -50,3 +51,11 @@ public struct NextPageView: View {
}
}
}
#Preview {
List {
Text("Item 1")
NextPageView {}
}
.listStyle(.plain)
}

View file

@ -0,0 +1,25 @@
import SwiftUI
public struct PlaceholderView: View {
public let iconName: String
public let title: LocalizedStringKey
public let message: LocalizedStringKey
public init(iconName: String, title: LocalizedStringKey, message: LocalizedStringKey) {
self.iconName = iconName
self.title = title
self.message = message
}
public var body: some View {
ContentUnavailableView(title,
systemImage: iconName,
description: Text(message))
}
}
#Preview {
PlaceholderView(iconName: "square.and.arrow.up.trianglebadge.exclamationmark",
title: "Nothing to see",
message: "This is a preview. Please try again.")
}

View file

@ -11,7 +11,7 @@ public struct ScrollToView: View {
public init() {}
public var body: some View {
HStack { SwiftUI.EmptyView() }
HStack { EmptyView() }
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(.init())

View file

@ -1,44 +0,0 @@
/*! @copyright 2021 Medium */
import SwiftUI
// Source: https://www.fivestars.blog/articles/scrollview-offset/
public struct ScrollViewOffsetReader<Content: View>: View {
let onOffsetChange: (CGFloat) -> Void
let content: () -> Content
public init(
onOffsetChange: @escaping (CGFloat) -> Void,
@ViewBuilder content: @escaping () -> Content
) {
self.onOffsetChange = onOffsetChange
self.content = content
}
public var body: some View {
ScrollView {
offsetReader
content()
.padding(.top, -8)
}
.coordinateSpace(name: "frameLayer")
.onPreferenceChange(OffsetPreferenceKey.self, perform: onOffsetChange)
}
var offsetReader: some View {
GeometryReader { proxy in
Color.clear
.preference(
key: OffsetPreferenceKey.self,
value: proxy.frame(in: .named("frameLayer")).minY
)
}
.frame(height: 0)
}
}
private struct OffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value _: inout CGFloat, nextValue _: () -> CGFloat) {}
}

View file

@ -0,0 +1,15 @@
import Network
import SwiftUI
@MainActor
public extension View {
func withPreviewsEnv() -> some View {
environment(RouterPath())
.environment(Client(server: ""))
.environment(CurrentAccount.shared)
.environment(UserPreferences.shared)
.environment(CurrentInstance.shared)
.environment(PushNotificationsService.shared)
.environment(QuickLook.shared)
}
}

View file

@ -38,7 +38,15 @@ public enum WindowDestinationMedia: Hashable, Codable {
case mediaViewer(attachments: [MediaAttachment], selectedAttachment: MediaAttachment)
}
public enum SheetDestination: Identifiable {
public enum SheetDestination: Identifiable, Hashable {
public static func == (lhs: SheetDestination, rhs: SheetDestination) -> Bool {
lhs.id == rhs.id
}
public func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
case newStatusEditor(visibility: Models.Visibility)
case editStatusEditor(status: Status)
case replyToStatusEditor(status: Status)
@ -60,6 +68,8 @@ public enum SheetDestination: Identifiable {
case shareImage(image: UIImage, status: Status)
case editTagGroup(tagGroup: TagGroup, onSaved: ((TagGroup) -> Void)?)
case timelineContentFilter
case accountEditInfo
case accountFiltersList
public var id: String {
switch self {
@ -90,6 +100,10 @@ public enum SheetDestination: Identifiable {
"settings"
case .timelineContentFilter:
"timelineContentFilter"
case .accountEditInfo:
"accountEditInfo"
case .accountFiltersList:
"accountFiltersList"
}
}
}
@ -148,7 +162,8 @@ public enum SheetDestination: Identifiable {
return .handled
} else if url.lastPathComponent.first == "@",
let host = url.host,
!host.hasPrefix("www") {
!host.hasPrefix("www")
{
let acct = "\(url.lastPathComponent)@\(host)"
Task {
await navigateToAccountFrom(acct: acct, url: url)

View file

@ -65,7 +65,7 @@ import OSLog
connect()
}
watchedStreams = streams
streams.forEach { stream in
for stream in streams {
sendMessage(message: StreamMessage(type: "subscribe", stream: stream.rawValue))
}
}
@ -159,19 +159,19 @@ import OSLog
public func emmitDeleteEvent(for status: String) {
let event = StreamEventDelete(status: status)
self.events.append(event)
self.latestEvent = event
events.append(event)
latestEvent = event
}
public func emmitEditEvent(for status: Status) {
let event = StreamEventStatusUpdate(status: status)
self.events.append(event)
self.latestEvent = event
events.append(event)
latestEvent = event
}
public func emmitPostEvent(for status: Status) {
let event = StreamEventUpdate(status: status)
self.events.append(event)
self.latestEvent = event
events.append(event)
latestEvent = event
}
}

View file

@ -183,7 +183,6 @@ import SwiftUI
}
}
public var alwaysUseDeepl: Bool {
didSet {
storage.alwaysUseDeepl = alwaysUseDeepl
@ -415,7 +414,7 @@ import SwiftUI
}
public var totalNotificationsCount: Int {
notificationsCount.compactMap{ $0.value }.reduce(0, +)
notificationsCount.compactMap { $0.value }.reduce(0, +)
}
public func reloadNotificationsCount(tokens: [OauthToken]) {

View file

@ -1,7 +1,7 @@
@testable import Env
import XCTest
import SwiftUI
import Network
import SwiftUI
import XCTest
@MainActor
final class RouterTests: XCTestCase {

View file

@ -32,7 +32,7 @@ public struct ExploreView: View {
} else if !viewModel.searchQuery.isEmpty {
if let results = viewModel.results[viewModel.searchQuery] {
if results.isEmpty, !viewModel.isSearching {
EmptyView(iconName: "magnifyingglass",
PlaceholderView(iconName: "magnifyingglass",
title: "explore.search.empty.title",
message: "explore.search.empty.message")
.listRowBackground(theme.secondaryBackgroundColor)
@ -53,7 +53,7 @@ public struct ExploreView: View {
.id(UUID())
}
} else if viewModel.allSectionsEmpty {
EmptyView(iconName: "magnifyingglass",
PlaceholderView(iconName: "magnifyingglass",
title: "explore.search.title",
message: "explore.search.message-\(client.server)")
#if !os(visionOS)

View file

@ -1,8 +1,8 @@
import DesignSystem
import Models
import Network
import StatusKit
import SwiftUI
import Network
public struct TrendingLinksListView: View {
@Environment(Theme.self) private var theme

View file

@ -1,5 +1,5 @@
import SwiftUI
import Models
import SwiftUI
enum DisplayType {
case image

View file

@ -1,9 +1,9 @@
import AVKit
import DesignSystem
import Env
import Models
import Observation
import SwiftUI
import Models
@MainActor
@Observable public class MediaUIAttachmentVideoViewModel {
@ -23,7 +23,7 @@ import Models
#if !os(visionOS)
player?.preventsDisplaySleepDuringVideoPlayback = false
#endif
if (autoPlay || forceAutoPlay) && !isCompact {
if autoPlay || forceAutoPlay, !isCompact {
player?.play()
isPlaying = true
} else {
@ -179,7 +179,8 @@ public struct MediaUIAttachmentVideoView: View {
!viewModel.forceAutoPlay,
!isFullScreen,
!viewModel.isPlaying,
!isCompact {
!isCompact
{
Button(action: {
viewModel.play()
}, label: {

View file

@ -1,8 +1,8 @@
import AVFoundation
import Models
import Nuke
import QuickLook
import SwiftUI
import AVFoundation
public struct MediaUIView: View, @unchecked Sendable {
private let data: [DisplayData]

View file

@ -1,6 +1,6 @@
import SwiftUI
import NukeUI
import Nuke
import NukeUI
import SwiftUI
struct QuickLookToolbarItem: ToolbarContent, @unchecked Sendable {
let itemUrl: URL

View file

@ -61,6 +61,7 @@ public final class Account: Codable, Identifiable, Hashable, Sendable, Equatable
public let source: Source?
public let bot: Bool
public let discoverable: Bool?
public let moved: Account?
public var haveAvatar: Bool {
avatar.lastPathComponent != "missing.png"
@ -70,7 +71,7 @@ public final class Account: Codable, Identifiable, Hashable, Sendable, Equatable
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, moved: Account? = nil) {
self.id = id
self.username = username
self.displayName = displayName
@ -90,11 +91,12 @@ public final class Account: Codable, Identifiable, Hashable, Sendable, Equatable
self.source = source
self.bot = bot
self.discoverable = discoverable
self.moved = moved
if let displayName, !displayName.isEmpty {
self.cachedDisplayName = .init(stringValue: displayName)
cachedDisplayName = .init(stringValue: displayName)
} else {
self.cachedDisplayName = .init(stringValue: "@\(username)")
cachedDisplayName = .init(stringValue: "@\(username)")
}
}
@ -118,33 +120,36 @@ public final class Account: Codable, Identifiable, Hashable, Sendable, Equatable
case source
case bot
case discoverable
case moved
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.username = try container.decode(String.self, forKey: .username)
self.displayName = try container.decodeIfPresent(String.self, forKey: .displayName)
self.avatar = try container.decode(URL.self, forKey: .avatar)
self.header = try container.decode(URL.self, forKey: .header)
self.acct = try container.decode(String.self, forKey: .acct)
self.note = try container.decode(HTMLString.self, forKey: .note)
self.createdAt = try container.decode(ServerDate.self, forKey: .createdAt)
self.followersCount = try container.decodeIfPresent(Int.self, forKey: .followersCount)
self.followingCount = try container.decodeIfPresent(Int.self, forKey: .followingCount)
self.statusesCount = try container.decodeIfPresent(Int.self, forKey: .statusesCount)
self.lastStatusAt = try container.decodeIfPresent(String.self, forKey: .lastStatusAt)
self.fields = try container.decode([Account.Field].self, forKey: .fields)
self.locked = try container.decode(Bool.self, forKey: .locked)
self.emojis = try container.decode([Emoji].self, forKey: .emojis)
self.url = try container.decodeIfPresent(URL.self, forKey: .url)
self.source = try container.decodeIfPresent(Account.Source.self, forKey: .source)
self.bot = try container.decode(Bool.self, forKey: .bot)
self.discoverable = try container.decodeIfPresent(Bool.self, forKey: .discoverable)
id = try container.decode(String.self, forKey: .id)
username = try container.decode(String.self, forKey: .username)
displayName = try container.decodeIfPresent(String.self, forKey: .displayName)
avatar = try container.decode(URL.self, forKey: .avatar)
header = try container.decode(URL.self, forKey: .header)
acct = try container.decode(String.self, forKey: .acct)
note = try container.decode(HTMLString.self, forKey: .note)
createdAt = try container.decode(ServerDate.self, forKey: .createdAt)
followersCount = try container.decodeIfPresent(Int.self, forKey: .followersCount)
followingCount = try container.decodeIfPresent(Int.self, forKey: .followingCount)
statusesCount = try container.decodeIfPresent(Int.self, forKey: .statusesCount)
lastStatusAt = try container.decodeIfPresent(String.self, forKey: .lastStatusAt)
fields = try container.decode([Account.Field].self, forKey: .fields)
locked = try container.decode(Bool.self, forKey: .locked)
emojis = try container.decode([Emoji].self, forKey: .emojis)
url = try container.decodeIfPresent(URL.self, forKey: .url)
source = try container.decodeIfPresent(Account.Source.self, forKey: .source)
bot = try container.decode(Bool.self, forKey: .bot)
discoverable = try container.decodeIfPresent(Bool.self, forKey: .discoverable)
moved = try container.decodeIfPresent(Account.self, forKey: .moved)
if let displayName, !displayName.isEmpty {
self.cachedDisplayName = .init(stringValue: displayName)
cachedDisplayName = .init(stringValue: displayName)
} else {
self.cachedDisplayName = .init(stringValue: "@\(username)")
cachedDisplayName = .init(stringValue: "@\(username)")
}
}

View file

@ -22,7 +22,7 @@ public struct StreamEventUpdate: StreamEvent {
public struct StreamEventStatusUpdate: StreamEvent {
public let date = Date()
public var id: String { status.id + (status.editedAt?.asDate.description ?? "")}
public var id: String { status.id + (status.editedAt?.asDate.description ?? "") }
public let status: Status
public init(status: Status) {
self.status = status

View file

@ -3,8 +3,8 @@ import Foundation
import Models
import Observation
import os
import SwiftUI
import OSLog
import SwiftUI
@Observable public final class Client: Equatable, Identifiable, Hashable, @unchecked Sendable {
public static func == (lhs: Client, rhs: Client) -> Bool {
@ -286,7 +286,8 @@ import OSLog
method: String,
mimeType: String,
filename: String,
data: Data) throws -> URLRequest {
data: Data) throws -> URLRequest
{
let url = try makeURL(endpoint: endpoint, forceVersion: version)
var request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method)
let boundary = UUID().uuidString

View file

@ -34,8 +34,8 @@ public struct InstanceSocialClient: Sendable {
}
}
extension Array where Self.Element == InstanceSocial {
fileprivate func sorted(by keyword: String) -> Self {
private extension Array where Self.Element == InstanceSocial {
func sorted(by keyword: String) -> Self {
let keyword = keyword.trimmingCharacters(in: .whitespacesAndNewlines)
var newArray = self

View file

@ -67,6 +67,7 @@ extension Models.Notification.NotificationType {
}
}
@MainActor
func tintColor(isPrivate: Bool) -> Color {
if isPrivate {
return Color.orange.opacity(0.80)

View file

@ -153,7 +153,7 @@ public struct NotificationsListView: View {
case let .display(notifications, nextPageState):
if notifications.isEmpty {
EmptyView(iconName: "bell.slash",
PlaceholderView(iconName: "bell.slash",
title: "notifications.empty.title",
message: "notifications.empty.message")
#if !os(visionOS)

View file

@ -164,7 +164,7 @@ import SwiftUI
Task {
do {
let _: Marker = try await client.post(endpoint: Markers.markNotifications(lastReadId: id))
} catch { }
} catch {}
}
}

View file

@ -37,5 +37,4 @@ extension StatusEditor {
}
}
}
}

View file

@ -1,7 +1,7 @@
import DesignSystem
import Env
#if !os(visionOS) && !DEBUG
import GiphyUISDK
import GiphyUISDK
#endif
import Models
import NukeUI
@ -159,7 +159,6 @@ extension StatusEditor {
.accessibilityLabel("accessibility.editor.button.attach-photo")
.disabled(viewModel.showPoll)
Button {
// all SEVM have the same visibility value
followUpSEVMs.append(ViewModel(mode: .new(visibility: focusedSEVM.visibility)))
@ -188,7 +187,6 @@ extension StatusEditor {
}
}
if preferences.isOpenAIEnabled {
AIMenu.disabled(!viewModel.canPost)
}
@ -268,7 +266,5 @@ extension StatusEditor {
}
}
}
}
}

View file

@ -1,12 +1,11 @@
import DesignSystem
import EmojiText
import Foundation
import SwiftUI
import Models
import SwiftData
import SwiftUI
extension StatusEditor {
@MainActor
struct AutoCompleteView: View {
@Environment(\.modelContext) var context
@ -22,7 +21,8 @@ extension StatusEditor {
var body: some View {
if !viewModel.mentionsSuggestions.isEmpty ||
!viewModel.tagsSuggestions.isEmpty ||
(viewModel.showRecentsTagsInline && !recentTags.isEmpty) {
(viewModel.showRecentsTagsInline && !recentTags.isEmpty)
{
VStack {
HStack {
ScrollView(.horizontal, showsIndicators: false) {

View file

@ -1,10 +1,10 @@
import DesignSystem
import EmojiText
import Env
import Foundation
import SwiftUI
import Models
import SwiftData
import Env
import SwiftUI
extension StatusEditor.AutoCompleteView {
@MainActor

View file

@ -1,10 +1,9 @@
import DesignSystem
import EmojiText
import Foundation
import SwiftUI
import Models
import SwiftData
import SwiftUI
extension StatusEditor.AutoCompleteView {
struct MentionsView: View {

View file

@ -1,10 +1,9 @@
import DesignSystem
import EmojiText
import Foundation
import SwiftUI
import Models
import SwiftData
import SwiftUI
extension StatusEditor.AutoCompleteView {
struct RecentTagsView: View {

View file

@ -1,10 +1,9 @@
import DesignSystem
import EmojiText
import Foundation
import SwiftUI
import Models
import SwiftData
import SwiftUI
extension StatusEditor.AutoCompleteView {
struct RemoteTagsView: View {

View file

@ -35,5 +35,4 @@ extension StatusEditor {
Coordinator(picker: self)
}
}
}

View file

@ -7,5 +7,4 @@ extension StatusEditor {
let categoryName: String
var emojis: [Emoji]
}
}

View file

@ -2,9 +2,9 @@ import AVFoundation
import Foundation
import UIKit
extension StatusEditor {
public actor Compressor {
public init() { }
public extension StatusEditor {
actor Compressor {
public init() {}
enum CompressorError: Error {
case noData
@ -106,5 +106,4 @@ extension StatusEditor {
}
}
}
}

View file

@ -1,11 +1,10 @@
import DesignSystem
import Env
import SwiftUI
import Models
import NukeUI
import SwiftUI
extension StatusEditor {
@MainActor
struct CustomEmojisView: View {
@Environment(\.dismiss) private var dismiss

View file

@ -1,10 +1,10 @@
#if !os(visionOS) && !DEBUG
import DesignSystem
import GiphyUISDK
import SwiftUI
import UIKit
import DesignSystem
import GiphyUISDK
import SwiftUI
import UIKit
struct GifPickerView: UIViewControllerRepresentable {
struct GifPickerView: UIViewControllerRepresentable {
@Environment(Theme.self) private var theme
var completion: (String) -> Void
@ -49,5 +49,5 @@ struct GifPickerView: UIViewControllerRepresentable {
parent.completion(url ?? "")
}
}
}
}
#endif

View file

@ -1,10 +1,9 @@
import DesignSystem
import Env
import SwiftUI
import Models
import SwiftUI
extension StatusEditor {
@MainActor
struct LangButton: View {
@Environment(Theme.self) private var theme

View file

@ -13,5 +13,4 @@ extension StatusEditor {
let mediaAttachment: MediaAttachment?
let error: Error?
}
}

View file

@ -171,5 +171,4 @@ extension StatusEditor {
return translation?.content.asRawText
}
}
}

View file

@ -249,5 +249,4 @@ extension StatusEditor {
.cornerRadius(8)
}
}
}

View file

@ -121,5 +121,4 @@ extension StatusEditor {
return index == count - 1 && count < maxEntries
}
}
}

View file

@ -86,10 +86,10 @@ extension StatusEditor {
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(importedContentType: .movie) { receivedTransferrable in
return MovieFileTranseferable(url: receivedTransferrable.localURL)
MovieFileTranseferable(url: receivedTransferrable.localURL)
}
FileRepresentation(importedContentType: .video) { receivedTransferrable in
return MovieFileTranseferable(url: receivedTransferrable.localURL)
MovieFileTranseferable(url: receivedTransferrable.localURL)
}
}
}
@ -112,7 +112,7 @@ extension StatusEditor {
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(importedContentType: .gif) { receivedTransferrable in
return GifFileTranseferable(url: receivedTransferrable.localURL)
GifFileTranseferable(url: receivedTransferrable.localURL)
}
}
}
@ -133,7 +133,7 @@ public extension StatusEditor {
public static var transferRepresentation: some TransferRepresentation {
FileRepresentation(importedContentType: .image) { receivedTransferrable in
return ImageFileTranseferable(url: receivedTransferrable.localURL)
ImageFileTranseferable(url: receivedTransferrable.localURL)
}
}
}
@ -141,15 +141,15 @@ public extension StatusEditor {
public extension ReceivedTransferredFile {
var localURL: URL {
if self.isOriginalFile {
if isOriginalFile {
return file
}
let copy = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(self.file.pathExtension)")
try? FileManager.default.copyItem(at: self.file, to: copy)
let copy = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(file.pathExtension)")
try? FileManager.default.copyItem(at: file, to: copy)
return copy
}
}
public extension URL {
func mimeType() -> String {
if let mimeType = UTType(filenameExtension: pathExtension)?.preferredMIMEType {

View file

@ -49,5 +49,4 @@ extension StatusEditor {
}
}
}
}

View file

@ -4,5 +4,4 @@ extension StatusEditor {
enum EditorFocusState: Hashable {
case main, followUp(index: UUID)
}
}

View file

@ -159,7 +159,6 @@ extension StatusEditor {
}
}
@ViewBuilder
private var characterCountAndLangView: some View {
let value = (currentInstance.instance?.configuration?.statuses.maxCharacters ?? 500) + viewModel.statusTextCharacterLength
@ -223,5 +222,4 @@ extension StatusEditor {
Task { await viewModel.fetchCustomEmojis() }
}
}
}

View file

@ -10,9 +10,9 @@ import StoreKit
import SwiftUI
import UIKit
extension StatusEditor {
public extension StatusEditor {
@MainActor
public struct MainView: View {
struct MainView: View {
@Environment(AppAccountsManager.self) private var appAccounts
@Environment(CurrentAccount.self) private var currentAccount
@Environment(Theme.self) private var theme
@ -151,5 +151,4 @@ extension StatusEditor {
.presentationBackgroundInteraction(.enabled)
}
}
}

View file

@ -1 +1 @@
public enum StatusEditor { }
public enum StatusEditor {}

View file

@ -30,5 +30,4 @@ extension StatusEditor {
}
}
}
}

View file

@ -1,8 +1,8 @@
import DesignSystem
import Env
import Models
import StoreKit
import SwiftUI
import DesignSystem
extension StatusEditor {
@MainActor

View file

@ -7,10 +7,9 @@ import Network
import PhotosUI
import SwiftUI
extension StatusEditor {
public extension StatusEditor {
@MainActor
@Observable public class ViewModel: NSObject, Identifiable {
@Observable class ViewModel: NSObject, Identifiable {
public let id = UUID()
var mode: Mode
@ -132,15 +131,16 @@ extension StatusEditor {
}
var allMediaHasDescription: Bool {
var everyMediaHasAltText: Bool = true;
mediaContainers.forEach { mediaContainer in
if (((mediaContainer.mediaAttachment?.description) == nil) ||
mediaContainer.mediaAttachment?.description?.count == 0) {
var everyMediaHasAltText = true
for mediaContainer in mediaContainers {
if ((mediaContainer.mediaAttachment?.description) == nil) ||
mediaContainer.mediaAttachment?.description?.count == 0
{
everyMediaHasAltText = false
}
}
return everyMediaHasAltText;
return everyMediaHasAltText
}
var shouldDisplayDismissWarning: Bool {
@ -200,12 +200,12 @@ extension StatusEditor {
func postStatus() async -> Status? {
guard let client else { return nil }
do {
if (!allMediaHasDescription && UserPreferences.shared.appRequireAltText) {
if !allMediaHasDescription && UserPreferences.shared.appRequireAltText {
throw PostError.missingAltText
}
if postingTimer == nil {
Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in
Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in
Task { @MainActor in
if self.postingProgress < 100 {
self.postingProgress += 0.5
@ -616,7 +616,8 @@ extension StatusEditor {
if !tagsSuggestions.isEmpty ||
!mentionsSuggestions.isEmpty ||
currentSuggestionRange != nil ||
showRecentsTagsInline {
showRecentsTagsInline
{
withAnimation {
tagsSuggestions = []
mentionsSuggestions = []
@ -838,7 +839,7 @@ extension StatusEditor {
error: nil
)
}
} catch { }
} catch {}
}
try? await Task.sleep(for: .seconds(5))
} while !Task.isCancelled
@ -859,7 +860,7 @@ extension StatusEditor {
mediaAttachment: media,
error: nil
)
} catch { }
} catch {}
}
}

View file

@ -56,7 +56,7 @@ public struct StatusEditHistoryView: View {
.task {
do {
history = try await client.get(endpoint: Statuses.history(id: statusId))
} catch { }
} catch {}
}
.listStyle(.plain)
.scrollContentBackground(.hidden)

View file

@ -8,7 +8,7 @@ private func stripToPureLanguage(inText: String) -> String {
var resultStr = inText
[hashtagRegex, emojiRegex, atRegex].forEach { regex in
for regex in [hashtagRegex, emojiRegex, atRegex] {
let splitArray = resultStr.split(separator: regex, omittingEmptySubsequences: true)
resultStr = splitArray.joined() as String
}

View file

@ -78,7 +78,7 @@ public struct StatusPollView: View {
// Make sure they're all the same width using a ZStack with 100% hiding behind the
// real percentage.
Text("100%").hidden().overlay(alignment: .trailing) {
Text("\(absolutePercent(for:option.votesCount ?? 0))%")
Text("\(absolutePercent(for: option.votesCount ?? 0))%")
.font(.scaledSubheadline)
}
}
@ -121,7 +121,7 @@ public struct StatusPollView: View {
return Text("accessibility.status.poll.option-prefix-\(index + 1)-of-\(viewModel.poll.options.count)") +
Text(", ") +
Text(option.title) +
Text(showPercentage ? ", \(absolutePercent(for:option.votesCount ?? 0))%" : "")
Text(showPercentage ? ", \(absolutePercent(for: option.votesCount ?? 0))%" : "")
}
private var footerView: some View {
@ -188,19 +188,20 @@ public struct StatusPollView: View {
private struct _PercentWidthLayout: Layout {
let percent: CGFloat
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache _: inout ()) -> CGSize {
guard let view = subviews.first else { return CGSize.zero }
return view.sizeThatFits(proposal)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache _: inout ()) {
guard let view = subviews.first,
let width = proposal.width
else { return }
view.place(
at: bounds.origin,
proposal: ProposedViewSize(width: percent / 100 * width, height: proposal.height))
proposal: ProposedViewSize(width: percent / 100 * width, height: proposal.height)
)
}
}
}

View file

@ -35,7 +35,7 @@ import SwiftUI
votes = poll.ownVotes ?? []
showResults = true
}
} catch { }
} catch {}
}
public func handleSelection(_ pollIndex: Int) {

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