mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-29 03:31:02 +00:00
SwiftFormat
This commit is contained in:
parent
2145bd5971
commit
8ff3e22d9f
55 changed files with 472 additions and 450 deletions
|
@ -10,11 +10,11 @@ extension IceCubesApp {
|
||||||
}
|
}
|
||||||
.keyboardShortcut("n", modifiers: .shift)
|
.keyboardShortcut("n", modifiers: .shift)
|
||||||
Button("menu.new-post") {
|
Button("menu.new-post") {
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
openWindow(value: WindowDestinationEditor.newStatusEditor(visibility: userPreferences.postVisibility))
|
openWindow(value: WindowDestinationEditor.newStatusEditor(visibility: userPreferences.postVisibility))
|
||||||
#else
|
#else
|
||||||
sidebarRouterPath.presentedSheet = .newStatusEditor(visibility: userPreferences.postVisibility)
|
sidebarRouterPath.presentedSheet = .newStatusEditor(visibility: userPreferences.postVisibility)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.keyboardShortcut("n", modifiers: .command)
|
.keyboardShortcut("n", modifiers: .command)
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ extension IceCubesApp {
|
||||||
}
|
}
|
||||||
.defaultSize(width: 600, height: 800)
|
.defaultSize(width: 600, height: 800)
|
||||||
.windowResizability(.contentMinSize)
|
.windowResizability(.contentMinSize)
|
||||||
|
|
||||||
WindowGroup(for: WindowDestinationMedia.self) { destination in
|
WindowGroup(for: WindowDestinationMedia.self) { destination in
|
||||||
Group {
|
Group {
|
||||||
switch destination.wrappedValue {
|
switch destination.wrappedValue {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
|
import Models
|
||||||
import Observation
|
import Observation
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Models
|
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
@MainActor func withSafariRouter() -> some View {
|
@MainActor func withSafariRouter() -> some View {
|
||||||
|
@ -43,9 +43,9 @@ private struct SafariRouter: ViewModifier {
|
||||||
return .handled
|
return .handled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if !targetEnvironment(macCatalyst)
|
#if !targetEnvironment(macCatalyst)
|
||||||
guard preferences.preferredBrowser == .inAppSafari else { return .systemAction }
|
guard preferences.preferredBrowser == .inAppSafari else { return .systemAction }
|
||||||
#endif
|
#endif
|
||||||
// SFSafariViewController only supports initial URLs with http:// or https:// schemes.
|
// SFSafariViewController only supports initial URLs with http:// or https:// schemes.
|
||||||
guard let scheme = url.scheme, ["https", "http"].contains(scheme.lowercased()) else {
|
guard let scheme = url.scheme, ["https", "http"].contains(scheme.lowercased()) else {
|
||||||
return .systemAction
|
return .systemAction
|
||||||
|
|
|
@ -57,11 +57,11 @@ struct SideBarView<Content: View>: View {
|
||||||
|
|
||||||
private var postButton: some View {
|
private var postButton: some View {
|
||||||
Button {
|
Button {
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
openWindow(value: WindowDestinationEditor.newStatusEditor(visibility: userPreferences.postVisibility))
|
openWindow(value: WindowDestinationEditor.newStatusEditor(visibility: userPreferences.postVisibility))
|
||||||
#else
|
#else
|
||||||
routerPath.presentedSheet = .newStatusEditor(visibility: userPreferences.postVisibility)
|
routerPath.presentedSheet = .newStatusEditor(visibility: userPreferences.postVisibility)
|
||||||
#endif
|
#endif
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "square.and.pencil")
|
Image(systemName: "square.and.pencil")
|
||||||
.resizable()
|
.resizable()
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
import Account
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
import SwiftUI
|
|
||||||
import Account
|
|
||||||
import Network
|
|
||||||
import Models
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct AboutView: View {
|
struct AboutView: View {
|
||||||
|
@ -13,7 +13,7 @@ struct AboutView: View {
|
||||||
|
|
||||||
@State private var dimillianAccount: AccountsListRowViewModel?
|
@State private var dimillianAccount: AccountsListRowViewModel?
|
||||||
@State private var iceCubesAccount: AccountsListRowViewModel?
|
@State private var iceCubesAccount: AccountsListRowViewModel?
|
||||||
|
|
||||||
let versionNumber: String
|
let versionNumber: String
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
@ -59,7 +59,6 @@ struct AboutView: View {
|
||||||
}
|
}
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
|
||||||
|
|
||||||
followAccountsSection
|
followAccountsSection
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
@ -109,8 +108,7 @@ struct AboutView: View {
|
||||||
routerPath.handle(url: url)
|
routerPath.handle(url: url)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var followAccountsSection: some View {
|
private var followAccountsSection: some View {
|
||||||
if let iceCubesAccount, let dimillianAccount {
|
if let iceCubesAccount, let dimillianAccount {
|
||||||
|
@ -132,18 +130,18 @@ struct AboutView: View {
|
||||||
group.addTask {
|
group.addTask {
|
||||||
let viewModel = try await fetchAccountViewModel(account: "dimillian@mastodon.social")
|
let viewModel = try await fetchAccountViewModel(account: "dimillian@mastodon.social")
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self.dimillianAccount = viewModel
|
dimillianAccount = viewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
group.addTask {
|
group.addTask {
|
||||||
let viewModel = try await fetchAccountViewModel(account: "icecubesapp@mastodon.online")
|
let viewModel = try await fetchAccountViewModel(account: "icecubesapp@mastodon.online")
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self.iceCubesAccount = viewModel
|
iceCubesAccount = viewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchAccountViewModel(account: String) async throws -> AccountsListRowViewModel {
|
private func fetchAccountViewModel(account: String) async throws -> AccountsListRowViewModel {
|
||||||
let dimillianAccount: Account = try await client.get(endpoint: Accounts.lookup(name: account))
|
let dimillianAccount: Account = try await client.get(endpoint: Accounts.lookup(name: account))
|
||||||
let rel: [Relationship] = try await client.get(endpoint: Accounts.relationships(ids: [dimillianAccount.id]))
|
let rel: [Relationship] = try await client.get(endpoint: Accounts.relationships(ids: [dimillianAccount.id]))
|
||||||
|
|
|
@ -218,7 +218,8 @@ struct AddAccountView: View {
|
||||||
signInClient = .init(server: sanitizedName)
|
signInClient = .init(server: sanitizedName)
|
||||||
if let oauthURL = try? await signInClient?.oauthURL(),
|
if let oauthURL = try? await signInClient?.oauthURL(),
|
||||||
let url = try? await webAuthenticationSession.authenticate(using: oauthURL,
|
let url = try? await webAuthenticationSession.authenticate(using: oauthURL,
|
||||||
callbackURLScheme: AppInfo.scheme.replacingOccurrences(of: "://", with: "")){
|
callbackURLScheme: AppInfo.scheme.replacingOccurrences(of: "://", with: ""))
|
||||||
|
{
|
||||||
await continueSignIn(url: url)
|
await continueSignIn(url: url)
|
||||||
} else {
|
} else {
|
||||||
isSigninIn = false
|
isSigninIn = false
|
||||||
|
|
|
@ -212,7 +212,7 @@ struct DisplaySettingsView: View {
|
||||||
Double(userPreferences.maxReplyIndentation)
|
Double(userPreferences.maxReplyIndentation)
|
||||||
}, set: { newVal in
|
}, set: { newVal in
|
||||||
userPreferences.maxReplyIndentation = UInt(newVal)
|
userPreferences.maxReplyIndentation = UInt(newVal)
|
||||||
}), in: 1...20, step: 1)
|
}), in: 1 ... 20, step: 1)
|
||||||
Text("settings.display.max-reply-indentation-\(String(userPreferences.maxReplyIndentation))")
|
Text("settings.display.max-reply-indentation-\(String(userPreferences.maxReplyIndentation))")
|
||||||
.font(.scaledBody)
|
.font(.scaledBody)
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ struct IconSelectorView: View {
|
||||||
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,
|
||||||
.alt43, .alt44, .alt45, .alt46, .alt47]),
|
.alt43, .alt44, .alt45, .alt46, .alt47]),
|
||||||
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]),
|
||||||
|
|
|
@ -29,7 +29,7 @@ struct SettingsTabs: View {
|
||||||
@State private var timelineCache = TimelineCache()
|
@State private var timelineCache = TimelineCache()
|
||||||
|
|
||||||
@Binding var popToRootTab: Tab
|
@Binding var popToRootTab: Tab
|
||||||
|
|
||||||
let isModal: Bool
|
let isModal: Bool
|
||||||
|
|
||||||
@Query(sort: \LocalTimeline.creationDate, order: .reverse) var localTimelines: [LocalTimeline]
|
@Query(sort: \LocalTimeline.creationDate, order: .reverse) var localTimelines: [LocalTimeline]
|
||||||
|
@ -160,10 +160,10 @@ struct SettingsTabs: View {
|
||||||
Label("settings.general.translate", systemImage: "captions.bubble")
|
Label("settings.general.translate", systemImage: "captions.bubble")
|
||||||
}
|
}
|
||||||
#if !targetEnvironment(macCatalyst)
|
#if !targetEnvironment(macCatalyst)
|
||||||
Link(destination: URL(string: UIApplication.openSettingsURLString)!) {
|
Link(destination: URL(string: UIApplication.openSettingsURLString)!) {
|
||||||
Label("settings.system", systemImage: "gear")
|
Label("settings.system", systemImage: "gear")
|
||||||
}
|
}
|
||||||
.tint(theme.labelColor)
|
.tint(theme.labelColor)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
@ -173,24 +173,24 @@ struct SettingsTabs: View {
|
||||||
private var otherSections: some View {
|
private var otherSections: some View {
|
||||||
@Bindable var preferences = preferences
|
@Bindable var preferences = preferences
|
||||||
Section("settings.section.other") {
|
Section("settings.section.other") {
|
||||||
#if !targetEnvironment(macCatalyst)
|
#if !targetEnvironment(macCatalyst)
|
||||||
Picker(selection: $preferences.preferredBrowser) {
|
Picker(selection: $preferences.preferredBrowser) {
|
||||||
ForEach(PreferredBrowser.allCases, id: \.rawValue) { browser in
|
ForEach(PreferredBrowser.allCases, id: \.rawValue) { browser in
|
||||||
switch browser {
|
switch browser {
|
||||||
case .inAppSafari:
|
case .inAppSafari:
|
||||||
Text("settings.general.browser.in-app").tag(browser)
|
Text("settings.general.browser.in-app").tag(browser)
|
||||||
case .safari:
|
case .safari:
|
||||||
Text("settings.general.browser.system").tag(browser)
|
Text("settings.general.browser.system").tag(browser)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} label: {
|
||||||
|
Label("settings.general.browser", systemImage: "network")
|
||||||
}
|
}
|
||||||
} label: {
|
Toggle(isOn: $preferences.inAppBrowserReaderView) {
|
||||||
Label("settings.general.browser", systemImage: "network")
|
Label("settings.general.browser.in-app.readerview", systemImage: "doc.plaintext")
|
||||||
}
|
}
|
||||||
Toggle(isOn: $preferences.inAppBrowserReaderView) {
|
.disabled(preferences.preferredBrowser != PreferredBrowser.inAppSafari)
|
||||||
Label("settings.general.browser.in-app.readerview", systemImage: "doc.plaintext")
|
#endif
|
||||||
}
|
|
||||||
.disabled(preferences.preferredBrowser != PreferredBrowser.inAppSafari)
|
|
||||||
#endif
|
|
||||||
Toggle(isOn: $preferences.isOpenAIEnabled) {
|
Toggle(isOn: $preferences.isOpenAIEnabled) {
|
||||||
Label("settings.other.hide-openai", systemImage: "faxmachine")
|
Label("settings.other.hide-openai", systemImage: "faxmachine")
|
||||||
}
|
}
|
||||||
|
@ -209,19 +209,19 @@ struct SettingsTabs: View {
|
||||||
|
|
||||||
private var appSection: some View {
|
private var appSection: some View {
|
||||||
Section {
|
Section {
|
||||||
#if !targetEnvironment(macCatalyst)
|
#if !targetEnvironment(macCatalyst)
|
||||||
NavigationLink(destination: IconSelectorView()) {
|
NavigationLink(destination: IconSelectorView()) {
|
||||||
Label {
|
Label {
|
||||||
Text("settings.app.icon")
|
Text("settings.app.icon")
|
||||||
} icon: {
|
} icon: {
|
||||||
let icon = IconSelectorView.Icon(string: UIApplication.shared.alternateIconName ?? "AppIcon")
|
let icon = IconSelectorView.Icon(string: UIApplication.shared.alternateIconName ?? "AppIcon")
|
||||||
Image(uiImage: .init(named: icon.iconName)!)
|
Image(uiImage: .init(named: icon.iconName)!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 25, height: 25)
|
.frame(width: 25, height: 25)
|
||||||
.cornerRadius(4)
|
.cornerRadius(4)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
|
||||||
|
|
||||||
Link(destination: URL(string: "https://github.com/Dimillian/IceCubesApp")!) {
|
Link(destination: URL(string: "https://github.com/Dimillian/IceCubesApp")!) {
|
||||||
Label("settings.app.source", systemImage: "link")
|
Label("settings.app.source", systemImage: "link")
|
||||||
|
|
|
@ -21,9 +21,9 @@ enum Tab: Int, Identifiable, Hashable {
|
||||||
|
|
||||||
static func loggedInTabs() -> [Tab] {
|
static func loggedInTabs() -> [Tab] {
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||||
return [.timeline, .trending, .federated, .local, .notifications, .mentions, .explore, .messages, .settings]
|
[.timeline, .trending, .federated, .local, .notifications, .mentions, .explore, .messages, .settings]
|
||||||
} else {
|
} else {
|
||||||
return [.timeline, .notifications, .explore, .messages, .profile]
|
[.timeline, .notifications, .explore, .messages, .profile]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,10 @@ import Env
|
||||||
import Models
|
import Models
|
||||||
import Network
|
import Network
|
||||||
import NukeUI
|
import NukeUI
|
||||||
import Shimmer
|
|
||||||
import SwiftUI
|
|
||||||
import SwiftData
|
|
||||||
import SFSafeSymbols
|
import SFSafeSymbols
|
||||||
|
import Shimmer
|
||||||
|
import SwiftData
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct EditTagGroupView: View {
|
struct EditTagGroupView: View {
|
||||||
|
@ -53,8 +53,8 @@ struct EditTagGroupView: View {
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
.navigationTitle(
|
.navigationTitle(
|
||||||
isNewGroup
|
isNewGroup
|
||||||
? "timeline.filter.add-tag-groups"
|
? "timeline.filter.add-tag-groups"
|
||||||
: "timeline.filter.edit-tag-groups"
|
: "timeline.filter.edit-tag-groups"
|
||||||
)
|
)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
|
@ -76,9 +76,9 @@ struct EditTagGroupView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
init(tagGroup: TagGroup = .emptyGroup(), onSaved: ((TagGroup) -> Void)? = nil) {
|
init(tagGroup: TagGroup = .emptyGroup(), onSaved: ((TagGroup) -> Void)? = nil) {
|
||||||
self._tagGroup = State(wrappedValue: tagGroup)
|
_tagGroup = State(wrappedValue: tagGroup)
|
||||||
self.onSaved = onSaved
|
self.onSaved = onSaved
|
||||||
self.isNewGroup = tagGroup.title.isEmpty
|
isNewGroup = tagGroup.title.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
private func save() {
|
private func save() {
|
||||||
|
@ -320,7 +320,7 @@ private struct SymbolSearchResultsView: View {
|
||||||
.onAppear {
|
.onAppear {
|
||||||
results = TagGroup.searchSymbol(for: symbolQuery, exclude: selectedSymbol)
|
results = TagGroup.searchSymbol(for: symbolQuery, exclude: selectedSymbol)
|
||||||
}
|
}
|
||||||
case .invalid(let description):
|
case let .invalid(description):
|
||||||
Text(description)
|
Text(description)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
@ -335,6 +335,7 @@ private struct SymbolSearchResultsView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: search results validation
|
// MARK: search results validation
|
||||||
|
|
||||||
enum ValidationStatus: Equatable {
|
enum ValidationStatus: Equatable {
|
||||||
case valid
|
case valid
|
||||||
case invalid(description: LocalizedStringKey)
|
case invalid(description: LocalizedStringKey)
|
||||||
|
@ -342,22 +343,23 @@ private struct SymbolSearchResultsView: View {
|
||||||
|
|
||||||
var validationStatus: ValidationStatus {
|
var validationStatus: ValidationStatus {
|
||||||
if results.isEmpty {
|
if results.isEmpty {
|
||||||
if symbolQuery == selectedSymbol
|
if symbolQuery == selectedSymbol,
|
||||||
&& !symbolQuery.isEmpty
|
!symbolQuery.isEmpty,
|
||||||
&& results.count == 0
|
results.count == 0
|
||||||
{
|
{
|
||||||
return .invalid(description: "\(symbolQuery) add-tag-groups.edit.tags.field.warning.search-results.already-selected")
|
.invalid(description: "\(symbolQuery) add-tag-groups.edit.tags.field.warning.search-results.already-selected")
|
||||||
} else {
|
} else {
|
||||||
return .invalid(description: "add-tag-groups.edit.tags.field.warning.search-results.no-symbol-found")
|
.invalid(description: "add-tag-groups.edit.tags.field.warning.search-results.no-symbol-found")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .valid
|
.valid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TagGroup {
|
extension TagGroup {
|
||||||
// MARK: title validation
|
// MARK: title validation
|
||||||
|
|
||||||
enum TitleValidationStatus: Equatable {
|
enum TitleValidationStatus: Equatable {
|
||||||
case valid
|
case valid
|
||||||
case invalid(description: LocalizedStringKey)
|
case invalid(description: LocalizedStringKey)
|
||||||
|
@ -365,11 +367,12 @@ extension TagGroup {
|
||||||
|
|
||||||
var titleValidationStatus: TitleValidationStatus {
|
var titleValidationStatus: TitleValidationStatus {
|
||||||
title.isEmpty
|
title.isEmpty
|
||||||
? .invalid(description: "add-tag-groups.edit.title.field.warning.empty-title")
|
? .invalid(description: "add-tag-groups.edit.title.field.warning.empty-title")
|
||||||
: .valid
|
: .valid
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: symbolName validation
|
// MARK: symbolName validation
|
||||||
|
|
||||||
enum SymbolNameValidationStatus: Equatable {
|
enum SymbolNameValidationStatus: Equatable {
|
||||||
case valid
|
case valid
|
||||||
case invalid(description: LocalizedStringKey)
|
case invalid(description: LocalizedStringKey)
|
||||||
|
@ -386,6 +389,7 @@ extension TagGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: tags validation
|
// MARK: tags validation
|
||||||
|
|
||||||
enum TagsValidationStatus: Equatable {
|
enum TagsValidationStatus: Equatable {
|
||||||
case valid
|
case valid
|
||||||
case invalid(description: LocalizedStringKey)
|
case invalid(description: LocalizedStringKey)
|
||||||
|
@ -399,19 +403,22 @@ extension TagGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: TagGroup validation
|
// MARK: TagGroup validation
|
||||||
|
|
||||||
var isValid: Bool {
|
var isValid: Bool {
|
||||||
titleValidationStatus == .valid
|
titleValidationStatus == .valid
|
||||||
&& symbolNameValidationStatus == .valid
|
&& symbolNameValidationStatus == .valid
|
||||||
&& tagsValidationStatus == .valid
|
&& tagsValidationStatus == .valid
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: format
|
// MARK: format
|
||||||
|
|
||||||
func format() {
|
func format() {
|
||||||
title = title.trimmingCharacters(in: .whitespacesAndNewlines)
|
title = title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
tags = tags.map { $0.lowercased() }
|
tags = tags.map { $0.lowercased() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: static members
|
// MARK: static members
|
||||||
|
|
||||||
static func emptyGroup() -> TagGroup {
|
static func emptyGroup() -> TagGroup {
|
||||||
TagGroup(title: "", symbolName: "", tags: [])
|
TagGroup(title: "", symbolName: "", tags: [])
|
||||||
}
|
}
|
||||||
|
@ -419,9 +426,9 @@ extension TagGroup {
|
||||||
static func searchSymbol(for query: String, exclude excludedSymbol: String) -> [String] {
|
static func searchSymbol(for query: String, exclude excludedSymbol: String) -> [String] {
|
||||||
guard !query.isEmpty else { return [] }
|
guard !query.isEmpty else { return [] }
|
||||||
|
|
||||||
return Self.allSymbols.filter {
|
return allSymbols.filter {
|
||||||
$0.contains(query) &&
|
$0.contains(query) &&
|
||||||
$0 != excludedSymbol
|
$0 != excludedSymbol
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,7 +439,7 @@ extension TagGroup {
|
||||||
|
|
||||||
extension Text {
|
extension Text {
|
||||||
func warningLabel() -> Text {
|
func warningLabel() -> Text {
|
||||||
self.font(.caption)
|
font(.caption)
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,13 +81,14 @@ struct AccountDetailHeaderView: View {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let attachement = MediaAttachment.imageWith(url: account.header)
|
let attachement = MediaAttachment.imageWith(url: account.header)
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
openWindow(value: WindowDestinationMedia.mediaViewer(
|
openWindow(value: WindowDestinationMedia.mediaViewer(
|
||||||
attachments: [attachement],
|
attachments: [attachement],
|
||||||
selectedAttachment: attachement))
|
selectedAttachment: attachement
|
||||||
#else
|
))
|
||||||
|
#else
|
||||||
quickLook.prepareFor(selectedMediaAttachment: attachement, mediaAttachments: [attachement])
|
quickLook.prepareFor(selectedMediaAttachment: attachement, mediaAttachments: [attachement])
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.accessibilityElement(children: .combine)
|
.accessibilityElement(children: .combine)
|
||||||
.accessibilityAddTraits([.isImage, .isButton])
|
.accessibilityAddTraits([.isImage, .isButton])
|
||||||
|
@ -117,12 +118,12 @@ struct AccountDetailHeaderView: View {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let attachement = MediaAttachment.imageWith(url: account.avatar)
|
let attachement = MediaAttachment.imageWith(url: account.avatar)
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
openWindow(value: WindowDestinationMedia.mediaViewer(attachments: [attachement],
|
openWindow(value: WindowDestinationMedia.mediaViewer(attachments: [attachement],
|
||||||
selectedAttachment: attachement))
|
selectedAttachment: attachement))
|
||||||
#else
|
#else
|
||||||
quickLook.prepareFor(selectedMediaAttachment: attachement, mediaAttachments: [attachement])
|
quickLook.prepareFor(selectedMediaAttachment: attachement, mediaAttachments: [attachement])
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.accessibilityElement(children: .combine)
|
.accessibilityElement(children: .combine)
|
||||||
.accessibilityAddTraits([.isImage, .isButton])
|
.accessibilityAddTraits([.isImage, .isButton])
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
|
import Env
|
||||||
import Models
|
import Models
|
||||||
import Network
|
import Network
|
||||||
import Env
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
@ -16,7 +16,7 @@ public struct EditAccountView: View {
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
NavigationStack{
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading {
|
||||||
loadingSection
|
loadingSection
|
||||||
|
|
|
@ -6,21 +6,21 @@ public extension Font {
|
||||||
// See https://gist.github.com/zacwest/916d31da5d03405809c4 for iOS values
|
// See https://gist.github.com/zacwest/916d31da5d03405809c4 for iOS values
|
||||||
// Custom values for Mac
|
// Custom values for Mac
|
||||||
private static let title = 28.0
|
private static let title = 28.0
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
private static let headline = 20.0
|
private static let headline = 20.0
|
||||||
private static let body = 19.0
|
private static let body = 19.0
|
||||||
private static let callout = 17.0
|
private static let callout = 17.0
|
||||||
private static let subheadline = 16.0
|
private static let subheadline = 16.0
|
||||||
private static let footnote = 15.0
|
private static let footnote = 15.0
|
||||||
private static let caption = 14.0
|
private static let caption = 14.0
|
||||||
#else
|
#else
|
||||||
private static let headline = 17.0
|
private static let headline = 17.0
|
||||||
private static let body = 17.0
|
private static let body = 17.0
|
||||||
private static let callout = 16.0
|
private static let callout = 16.0
|
||||||
private static let subheadline = 15.0
|
private static let subheadline = 15.0
|
||||||
private static let footnote = 13.0
|
private static let footnote = 13.0
|
||||||
private static let caption = 12.0
|
private static let caption = 12.0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private static func customFont(size: CGFloat, relativeTo textStyle: TextStyle) -> Font {
|
private static func customFont(size: CGFloat, relativeTo textStyle: TextStyle) -> Font {
|
||||||
if let chosenFont = Theme.shared.chosenFont {
|
if let chosenFont = Theme.shared.chosenFont {
|
||||||
|
|
|
@ -6,14 +6,14 @@ public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable {
|
||||||
public var window: UIWindow?
|
public var window: UIWindow?
|
||||||
public private(set) var windowWidth: CGFloat = UIScreen.main.bounds.size.width
|
public private(set) var windowWidth: CGFloat = UIScreen.main.bounds.size.width
|
||||||
public private(set) var windowHeight: CGFloat = UIScreen.main.bounds.size.height
|
public private(set) var windowHeight: CGFloat = UIScreen.main.bounds.size.height
|
||||||
|
|
||||||
public func scene(_ scene: UIScene,
|
public func scene(_ scene: UIScene,
|
||||||
willConnectTo _: UISceneSession,
|
willConnectTo _: UISceneSession,
|
||||||
options _: UIScene.ConnectionOptions)
|
options _: UIScene.ConnectionOptions)
|
||||||
{
|
{
|
||||||
guard let windowScene = scene as? UIWindowScene else { return }
|
guard let windowScene = scene as? UIWindowScene else { return }
|
||||||
window = windowScene.keyWindow
|
window = windowScene.keyWindow
|
||||||
|
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
if let titlebar = windowScene.titlebar {
|
if let titlebar = windowScene.titlebar {
|
||||||
titlebar.titleVisibility = .hidden
|
titlebar.titleVisibility = .hidden
|
||||||
|
@ -22,10 +22,10 @@ public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public override init() {
|
override public init() {
|
||||||
super.init()
|
super.init()
|
||||||
self.windowWidth = self.window?.bounds.size.width ?? UIScreen.main.bounds.size.width
|
windowWidth = window?.bounds.size.width ?? UIScreen.main.bounds.size.width
|
||||||
self.windowHeight = self.window?.bounds.size.height ?? UIScreen.main.bounds.size.height
|
windowHeight = window?.bounds.size.height ?? UIScreen.main.bounds.size.height
|
||||||
Self.observedSceneDelegate.insert(self)
|
Self.observedSceneDelegate.insert(self)
|
||||||
_ = Self.observer // just for activating the lazy static property
|
_ = Self.observer // just for activating the lazy static property
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import Env
|
import Env
|
||||||
|
import Models
|
||||||
import Nuke
|
import Nuke
|
||||||
import NukeUI
|
import NukeUI
|
||||||
import Shimmer
|
import Shimmer
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Models
|
|
||||||
|
|
||||||
struct AccountPopoverView: View {
|
struct AccountPopoverView: View {
|
||||||
let account: Account
|
let account: Account
|
||||||
|
@ -36,7 +36,7 @@ struct AccountPopoverView: View {
|
||||||
}
|
}
|
||||||
.frame(height: adaptiveConfig.height / 2, alignment: .bottom)
|
.frame(height: adaptiveConfig.height / 2, alignment: .bottom)
|
||||||
|
|
||||||
EmojiTextApp(.init(stringValue: account.safeDisplayName ), emojis: account.emojis)
|
EmojiTextApp(.init(stringValue: account.safeDisplayName), emojis: account.emojis)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(theme.labelColor)
|
.foregroundColor(theme.labelColor)
|
||||||
.emojiSize(Font.scaledHeadlineFont.emojiSize)
|
.emojiSize(Font.scaledHeadlineFont.emojiSize)
|
||||||
|
@ -122,11 +122,10 @@ struct AccountPopoverView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var adaptiveConfig: AvatarView.FrameConfig {
|
private var adaptiveConfig: AvatarView.FrameConfig {
|
||||||
var cornerRadius: CGFloat
|
var cornerRadius: CGFloat = if config == .badge || theme.avatarShape == .circle {
|
||||||
if config == .badge || theme.avatarShape == .circle {
|
config.width / 2
|
||||||
cornerRadius = config.width / 2
|
|
||||||
} else {
|
} else {
|
||||||
cornerRadius = config.cornerRadius
|
config.cornerRadius
|
||||||
}
|
}
|
||||||
return AvatarView.FrameConfig(width: config.width, height: config.height, cornerRadius: cornerRadius)
|
return AvatarView.FrameConfig(width: config.width, height: config.height, cornerRadius: cornerRadius)
|
||||||
}
|
}
|
||||||
|
@ -142,7 +141,7 @@ extension VerticalAlignment {
|
||||||
static let bottomAvatar = VerticalAlignment(BottomAvatarAlignment.self)
|
static let bottomAvatar = VerticalAlignment(BottomAvatarAlignment.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct AccountPopoverModifier : ViewModifier {
|
public struct AccountPopoverModifier: ViewModifier {
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
@Environment(UserPreferences.self) private var userPreferences
|
@Environment(UserPreferences.self) private var userPreferences
|
||||||
|
|
||||||
|
@ -156,7 +155,7 @@ public struct AccountPopoverModifier : ViewModifier {
|
||||||
if !userPreferences.showAccountPopover {
|
if !userPreferences.showAccountPopover {
|
||||||
return AnyView(content)
|
return AnyView(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
return AnyView(content
|
return AnyView(content
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
if hovering {
|
if hovering {
|
||||||
|
@ -191,8 +190,8 @@ public struct AccountPopoverModifier : ViewModifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension View {
|
public extension View {
|
||||||
public func accountPopover(_ account: Account) -> some View {
|
func accountPopover(_ account: Account) -> some View {
|
||||||
modifier(AccountPopoverModifier(account))
|
modifier(AccountPopoverModifier(account))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
import Models
|
||||||
import Nuke
|
import Nuke
|
||||||
import NukeUI
|
import NukeUI
|
||||||
import Shimmer
|
import Shimmer
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Models
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct AvatarView: View {
|
public struct AvatarView: View {
|
||||||
|
@ -21,11 +21,10 @@ public struct AvatarView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var adaptiveConfig: FrameConfig {
|
private var adaptiveConfig: FrameConfig {
|
||||||
var cornerRadius: CGFloat
|
var cornerRadius: CGFloat = if config == .badge || theme.avatarShape == .circle {
|
||||||
if config == .badge || theme.avatarShape == .circle {
|
config.width / 2
|
||||||
cornerRadius = config.width / 2
|
|
||||||
} else {
|
} else {
|
||||||
cornerRadius = config.cornerRadius
|
config.cornerRadius
|
||||||
}
|
}
|
||||||
return FrameConfig(width: config.width, height: config.height, cornerRadius: cornerRadius)
|
return FrameConfig(width: config.width, height: config.height, cornerRadius: cornerRadius)
|
||||||
}
|
}
|
||||||
|
@ -42,16 +41,16 @@ public struct AvatarView: View {
|
||||||
let cornerRadius: CGFloat
|
let cornerRadius: CGFloat
|
||||||
|
|
||||||
init(width: CGFloat, height: CGFloat, cornerRadius: CGFloat = 4) {
|
init(width: CGFloat, height: CGFloat, cornerRadius: CGFloat = 4) {
|
||||||
self.size = CGSize(width: width, height: height)
|
size = CGSize(width: width, height: height)
|
||||||
self.cornerRadius = cornerRadius
|
self.cornerRadius = cornerRadius
|
||||||
}
|
}
|
||||||
|
|
||||||
public static let account = FrameConfig(width: 80, height: 80)
|
public static let account = FrameConfig(width: 80, height: 80)
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
public static let status = FrameConfig(width: 48, height: 48)
|
public static let status = FrameConfig(width: 48, height: 48)
|
||||||
#else
|
#else
|
||||||
public static let status = FrameConfig(width: 40, height: 40)
|
public static let status = FrameConfig(width: 40, height: 40)
|
||||||
#endif
|
#endif
|
||||||
public static let embed = FrameConfig(width: 34, height: 34)
|
public static let embed = FrameConfig(width: 34, height: 34)
|
||||||
public static let badge = FrameConfig(width: 28, height: 28, cornerRadius: 14)
|
public static let badge = FrameConfig(width: 28, height: 28, cornerRadius: 14)
|
||||||
public static let list = FrameConfig(width: 20, height: 20, cornerRadius: 10)
|
public static let list = FrameConfig(width: 20, height: 20, cornerRadius: 10)
|
||||||
|
@ -77,10 +76,10 @@ struct PreviewWrapper: View {
|
||||||
Toggle("Avatar Shape", isOn: $isCircleAvatar)
|
Toggle("Avatar Shape", isOn: $isCircleAvatar)
|
||||||
}
|
}
|
||||||
.onChange(of: isCircleAvatar) {
|
.onChange(of: isCircleAvatar) {
|
||||||
Theme.shared.avatarShape = self.isCircleAvatar ? .circle : .rounded
|
Theme.shared.avatarShape = isCircleAvatar ? .circle : .rounded
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Theme.shared.avatarShape = self.isCircleAvatar ? .circle : .rounded
|
Theme.shared.avatarShape = isCircleAvatar ? .circle : .rounded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +102,8 @@ struct PreviewWrapper: View {
|
||||||
url: URL(string: "https://nondot.org/sabre/")!,
|
url: URL(string: "https://nondot.org/sabre/")!,
|
||||||
source: nil,
|
source: nil,
|
||||||
bot: false,
|
bot: false,
|
||||||
discoverable: true)
|
discoverable: true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AvatarImage: View {
|
struct AvatarImage: View {
|
||||||
|
|
|
@ -26,12 +26,12 @@ public struct StatusEditorToolbarItem: ToolbarContent {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
Button {
|
Button {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
openWindow(value: WindowDestinationEditor.newStatusEditor(visibility: visibility))
|
openWindow(value: WindowDestinationEditor.newStatusEditor(visibility: visibility))
|
||||||
#else
|
#else
|
||||||
routerPath.presentedSheet = .newStatusEditor(visibility: visibility)
|
routerPath.presentedSheet = .newStatusEditor(visibility: visibility)
|
||||||
HapticManager.shared.fireHaptic(.buttonPress)
|
HapticManager.shared.fireHaptic(.buttonPress)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "square.and.pencil")
|
Image(systemName: "square.and.pencil")
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import SwiftUI
|
|
||||||
import Charts
|
import Charts
|
||||||
import Models
|
import Models
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
public struct TagChartView: View {
|
public struct TagChartView: View {
|
||||||
@State private var sortedHistory: [Tag.History] = []
|
@State private var sortedHistory: [Tag.History] = []
|
||||||
|
|
||||||
public init(tag: Tag) {
|
public init(tag: Tag) {
|
||||||
_sortedHistory = .init(initialValue: tag.history.sorted {
|
_sortedHistory = .init(initialValue: tag.history.sorted {
|
||||||
Int($0.day) ?? 0 < Int($1.day) ?? 0
|
Int($0.day) ?? 0 < Int($1.day) ?? 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
Chart(sortedHistory) { data in
|
Chart(sortedHistory) { data in
|
||||||
AreaMark(x: .value("day", sortedHistory.firstIndex(where: { $0.id == data.id }) ?? 0),
|
AreaMark(x: .value("day", sortedHistory.firstIndex(where: { $0.id == data.id }) ?? 0),
|
||||||
y: .value("uses", Int(data.uses) ?? 0))
|
y: .value("uses", Int(data.uses) ?? 0))
|
||||||
.interpolationMethod(.catmullRom)
|
.interpolationMethod(.catmullRom)
|
||||||
}
|
}
|
||||||
.chartLegend(.hidden)
|
.chartLegend(.hidden)
|
||||||
.chartXAxis(.hidden)
|
.chartXAxis(.hidden)
|
||||||
|
|
|
@ -87,7 +87,7 @@ import Observation
|
||||||
tags = []
|
tags = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func deleteList(list: Models.List) async {
|
public func deleteList(list: Models.List) async {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
lists.removeAll(where: { $0.id == list.id })
|
lists.removeAll(where: { $0.id == list.id })
|
||||||
|
|
|
@ -68,7 +68,7 @@ public extension EnvironmentValues {
|
||||||
get { self[IndentationLevel.self] }
|
get { self[IndentationLevel.self] }
|
||||||
set { self[IndentationLevel.self] = newValue }
|
set { self[IndentationLevel.self] = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
var isHomeTimeline: Bool {
|
var isHomeTimeline: Bool {
|
||||||
get { self[IsHomeTimeline.self] }
|
get { self[IsHomeTimeline.self] }
|
||||||
set { self[IsHomeTimeline.self] = newValue }
|
set { self[IsHomeTimeline.self] = newValue }
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import EmojiText
|
import EmojiText
|
||||||
|
import Env
|
||||||
import Models
|
import Models
|
||||||
import Network
|
import Network
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Env
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct ListCreateView: View {
|
public struct ListCreateView: View {
|
||||||
|
@ -11,13 +11,13 @@ public struct ListCreateView: View {
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
@Environment(Client.self) private var client
|
@Environment(Client.self) private var client
|
||||||
@Environment(CurrentAccount.self) private var currentAccount
|
@Environment(CurrentAccount.self) private var currentAccount
|
||||||
|
|
||||||
@State private var title = ""
|
@State private var title = ""
|
||||||
@State private var repliesPolicy: Models.List.RepliesPolicy = .list
|
@State private var repliesPolicy: Models.List.RepliesPolicy = .list
|
||||||
@State private var isExclusive: Bool = false
|
@State private var isExclusive: Bool = false
|
||||||
@State private var isSaving: Bool = false
|
@State private var isSaving: Bool = false
|
||||||
|
|
||||||
public init() { }
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
|
@ -25,7 +25,8 @@ public struct ListCreateView: View {
|
||||||
Section("lists.edit.settings") {
|
Section("lists.edit.settings") {
|
||||||
TextField("list.edit.title", text: $title)
|
TextField("list.edit.title", text: $title)
|
||||||
Picker("list.edit.repliesPolicy",
|
Picker("list.edit.repliesPolicy",
|
||||||
selection: $repliesPolicy) {
|
selection: $repliesPolicy)
|
||||||
|
{
|
||||||
ForEach(Models.List.RepliesPolicy.allCases) { policy in
|
ForEach(Models.List.RepliesPolicy.allCases) { policy in
|
||||||
Text(policy.title)
|
Text(policy.title)
|
||||||
.tag(policy)
|
.tag(policy)
|
||||||
|
@ -43,8 +44,8 @@ public struct ListCreateView: View {
|
||||||
Task {
|
Task {
|
||||||
isSaving = true
|
isSaving = true
|
||||||
let _: Models.List = try await client.post(endpoint: Lists.createList(title: title,
|
let _: Models.List = try await client.post(endpoint: Lists.createList(title: title,
|
||||||
repliesPolicy: repliesPolicy,
|
repliesPolicy: repliesPolicy,
|
||||||
exclusive: isExclusive ))
|
exclusive: isExclusive))
|
||||||
await currentAccount.fetchLists()
|
await currentAccount.fetchLists()
|
||||||
isSaving = false
|
isSaving = false
|
||||||
dismiss()
|
dismiss()
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
import Account
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import EmojiText
|
import EmojiText
|
||||||
import Models
|
import Models
|
||||||
import Network
|
import Network
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Account
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct ListEditView: View {
|
public struct ListEditView: View {
|
||||||
|
@ -22,10 +22,11 @@ public struct ListEditView: View {
|
||||||
Form {
|
Form {
|
||||||
Section("lists.edit.settings") {
|
Section("lists.edit.settings") {
|
||||||
TextField("list.edit.title", text: $viewModel.title) {
|
TextField("list.edit.title", text: $viewModel.title) {
|
||||||
Task { await viewModel.update() }
|
Task { await viewModel.update() }
|
||||||
}
|
}
|
||||||
Picker("list.edit.repliesPolicy",
|
Picker("list.edit.repliesPolicy",
|
||||||
selection: $viewModel.repliesPolicy) {
|
selection: $viewModel.repliesPolicy)
|
||||||
|
{
|
||||||
ForEach(Models.List.RepliesPolicy.allCases) { policy in
|
ForEach(Models.List.RepliesPolicy.allCases) { policy in
|
||||||
Text(policy.title)
|
Text(policy.title)
|
||||||
.tag(policy)
|
.tag(policy)
|
||||||
|
@ -41,7 +42,7 @@ public struct ListEditView: View {
|
||||||
.onChange(of: viewModel.isExclusive) { _, _ in
|
.onChange(of: viewModel.isExclusive) { _, _ in
|
||||||
Task { await viewModel.update() }
|
Task { await viewModel.update() }
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("lists.edit.users-in-list") {
|
Section("lists.edit.users-in-list") {
|
||||||
HStack {
|
HStack {
|
||||||
TextField("lists.edit.users-search",
|
TextField("lists.edit.users-search",
|
||||||
|
@ -91,7 +92,7 @@ public struct ListEditView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var loadingView: some View {
|
private var loadingView: some View {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
@ -100,7 +101,7 @@ public struct ListEditView: View {
|
||||||
}
|
}
|
||||||
.id(UUID())
|
.id(UUID())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var searchAccountsView: some View {
|
private var searchAccountsView: some View {
|
||||||
if viewModel.isSearching {
|
if viewModel.isSearching {
|
||||||
|
@ -138,15 +139,15 @@ public struct ListEditView: View {
|
||||||
relationship: relationship,
|
relationship: relationship,
|
||||||
shouldDisplayNotify: false,
|
shouldDisplayNotify: false,
|
||||||
relationshipUpdated: { relationship in
|
relationshipUpdated: { relationship in
|
||||||
viewModel.searchedRelationships[account.id] = relationship
|
viewModel.searchedRelationships[account.id] = relationship
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var listAccountsView: some View {
|
private var listAccountsView: some View {
|
||||||
if viewModel.isLoadingAccounts {
|
if viewModel.isLoadingAccounts {
|
||||||
|
@ -176,5 +177,4 @@ public struct ListEditView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import Combine
|
import Combine
|
||||||
|
import Env
|
||||||
import Models
|
import Models
|
||||||
import Network
|
import Network
|
||||||
import Observation
|
import Observation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Env
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Observable public class ListEditViewModel {
|
@Observable public class ListEditViewModel {
|
||||||
|
@ -13,13 +13,13 @@ import Env
|
||||||
|
|
||||||
var isLoadingAccounts: Bool = true
|
var isLoadingAccounts: Bool = true
|
||||||
var accounts: [Account] = []
|
var accounts: [Account] = []
|
||||||
|
|
||||||
var title: String
|
var title: String
|
||||||
var repliesPolicy: Models.List.RepliesPolicy
|
var repliesPolicy: Models.List.RepliesPolicy
|
||||||
var isExclusive: Bool
|
var isExclusive: Bool
|
||||||
|
|
||||||
var isUpdating: Bool = false
|
var isUpdating: Bool = false
|
||||||
|
|
||||||
var searchUserQuery: String = ""
|
var searchUserQuery: String = ""
|
||||||
var searchedAccounts: [Account] = []
|
var searchedAccounts: [Account] = []
|
||||||
var searchedRelationships: [String: Relationship] = [:]
|
var searchedRelationships: [String: Relationship] = [:]
|
||||||
|
@ -27,9 +27,9 @@ import Env
|
||||||
|
|
||||||
init(list: Models.List) {
|
init(list: Models.List) {
|
||||||
self.list = list
|
self.list = list
|
||||||
self.title = list.title
|
title = list.title
|
||||||
self.repliesPolicy = list.repliesPolicy ?? .list
|
repliesPolicy = list.repliesPolicy ?? .list
|
||||||
self.isExclusive = list.exclusive ?? false
|
isExclusive = list.exclusive ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchAccounts() async {
|
func fetchAccounts() async {
|
||||||
|
@ -42,27 +42,27 @@ import Env
|
||||||
isLoadingAccounts = false
|
isLoadingAccounts = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update() async {
|
func update() async {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
do {
|
do {
|
||||||
isUpdating = true
|
isUpdating = true
|
||||||
let list: Models.List = try await client.put(endpoint:
|
let list: Models.List = try await client.put(endpoint:
|
||||||
Lists.updateList(id: list.id,
|
Lists.updateList(id: list.id,
|
||||||
title: title,
|
title: title,
|
||||||
repliesPolicy: repliesPolicy,
|
repliesPolicy: repliesPolicy,
|
||||||
exclusive: isExclusive ))
|
exclusive: isExclusive))
|
||||||
self.list = list
|
self.list = list
|
||||||
self.title = list.title
|
title = list.title
|
||||||
self.repliesPolicy = list.repliesPolicy ?? .list
|
repliesPolicy = list.repliesPolicy ?? .list
|
||||||
self.isExclusive = list.exclusive ?? false
|
isExclusive = list.exclusive ?? false
|
||||||
self.isUpdating = false
|
isUpdating = false
|
||||||
await CurrentAccount.shared.fetchLists()
|
await CurrentAccount.shared.fetchLists()
|
||||||
} catch {
|
} catch {
|
||||||
isUpdating = false
|
isUpdating = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func add(account: Account) async {
|
func add(account: Account) async {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
do {
|
do {
|
||||||
|
@ -76,7 +76,7 @@ import Env
|
||||||
isUpdating = false
|
isUpdating = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func delete(account: Account) async {
|
func delete(account: Account) async {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
do {
|
do {
|
||||||
|
@ -90,7 +90,7 @@ import Env
|
||||||
isUpdating = false
|
isUpdating = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchUsers() async {
|
func searchUsers() async {
|
||||||
guard let client, !searchUserQuery.isEmpty else { return }
|
guard let client, !searchUserQuery.isEmpty else { return }
|
||||||
do {
|
do {
|
||||||
|
@ -107,7 +107,7 @@ import Env
|
||||||
}
|
}
|
||||||
searchedAccounts = results.accounts
|
searchedAccounts = results.accounts
|
||||||
isSearching = false
|
isSearching = false
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,11 +115,11 @@ extension Models.List.RepliesPolicy {
|
||||||
var title: LocalizedStringKey {
|
var title: LocalizedStringKey {
|
||||||
switch self {
|
switch self {
|
||||||
case .followed:
|
case .followed:
|
||||||
return "list.repliesPolicy.followed"
|
"list.repliesPolicy.followed"
|
||||||
case .list:
|
case .list:
|
||||||
return "list.repliesPolicy.list"
|
"list.repliesPolicy.list"
|
||||||
case .none:
|
case .none:
|
||||||
return "list.repliesPolicy.none"
|
"list.repliesPolicy.none"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ public struct MediaUIView: View, @unchecked Sendable {
|
||||||
data = attachments.compactMap { DisplayData(from: $0) }
|
data = attachments.compactMap { DisplayData(from: $0) }
|
||||||
initialItem = DisplayData(from: selectedAttachment)
|
initialItem = DisplayData(from: selectedAttachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func scrollToPrevious() {
|
private func scrollToPrevious() {
|
||||||
if let scrolledItem, let index = data.firstIndex(of: scrolledItem), index > 0 {
|
if let scrolledItem, let index = data.firstIndex(of: scrolledItem), index > 0 {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
|
@ -62,9 +62,9 @@ public struct MediaUIView: View, @unchecked Sendable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func scrollToNext() {
|
private func scrollToNext() {
|
||||||
if let scrolledItem, let index = data.firstIndex(of: scrolledItem), index < data.count - 1{
|
if let scrolledItem, let index = data.firstIndex(of: scrolledItem), index < data.count - 1 {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
self.scrolledItem = data[index + 1]
|
self.scrolledItem = data[index + 1]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@ public struct List: Codable, Identifiable, Equatable, Hashable {
|
||||||
public let title: String
|
public let title: String
|
||||||
public let repliesPolicy: RepliesPolicy?
|
public let repliesPolicy: RepliesPolicy?
|
||||||
public let exclusive: Bool?
|
public let exclusive: Bool?
|
||||||
|
|
||||||
public enum RepliesPolicy: String, Sendable, Codable, CaseIterable, Identifiable {
|
public enum RepliesPolicy: String, Sendable, Codable, CaseIterable, Identifiable {
|
||||||
public var id: String {
|
public var id: String {
|
||||||
rawValue
|
rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
case followed, list, `none`
|
case followed, list, none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ public struct Tag: Codable, Identifiable, Equatable, Hashable {
|
||||||
public var id: String {
|
public var id: String {
|
||||||
day
|
day
|
||||||
}
|
}
|
||||||
|
|
||||||
public let day: String
|
public let day: String
|
||||||
public let accounts: String
|
public let accounts: String
|
||||||
public let uses: String
|
public let uses: String
|
||||||
|
|
|
@ -14,7 +14,7 @@ public enum Apps: Endpoint {
|
||||||
public func queryItems() -> [URLQueryItem]? {
|
public func queryItems() -> [URLQueryItem]? {
|
||||||
switch self {
|
switch self {
|
||||||
case .registerApp:
|
case .registerApp:
|
||||||
return [
|
[
|
||||||
.init(name: "client_name", value: AppInfo.clientName),
|
.init(name: "client_name", value: AppInfo.clientName),
|
||||||
.init(name: "redirect_uris", value: AppInfo.scheme),
|
.init(name: "redirect_uris", value: AppInfo.scheme),
|
||||||
.init(name: "scopes", value: AppInfo.scopes),
|
.init(name: "scopes", value: AppInfo.scopes),
|
||||||
|
|
|
@ -35,14 +35,14 @@ public enum Oauth: Endpoint {
|
||||||
public func queryItems() -> [URLQueryItem]? {
|
public func queryItems() -> [URLQueryItem]? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .authorize(clientId):
|
case let .authorize(clientId):
|
||||||
return [
|
[
|
||||||
.init(name: "response_type", value: "code"),
|
.init(name: "response_type", value: "code"),
|
||||||
.init(name: "client_id", value: clientId),
|
.init(name: "client_id", value: clientId),
|
||||||
.init(name: "redirect_uri", value: AppInfo.scheme),
|
.init(name: "redirect_uri", value: AppInfo.scheme),
|
||||||
.init(name: "scope", value: AppInfo.scopes),
|
.init(name: "scope", value: AppInfo.scopes),
|
||||||
]
|
]
|
||||||
default:
|
default:
|
||||||
return nil
|
nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,10 +29,10 @@ public enum ServerFilters: Endpoint {
|
||||||
public func queryItems() -> [URLQueryItem]? {
|
public func queryItems() -> [URLQueryItem]? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .addKeyword(_, keyword, wholeWord):
|
case let .addKeyword(_, keyword, wholeWord):
|
||||||
return [.init(name: "keyword", value: keyword),
|
[.init(name: "keyword", value: keyword),
|
||||||
.init(name: "whole_word", value: wholeWord ? "true" : "false")]
|
.init(name: "whole_word", value: wholeWord ? "true" : "false")]
|
||||||
default:
|
default:
|
||||||
return nil
|
nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,18 +35,19 @@ public struct OpenAIClient {
|
||||||
self.temperature = temperature
|
self.temperature = temperature
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct VisionRequest: OpenAIRequest {
|
public struct VisionRequest: OpenAIRequest {
|
||||||
public struct Message: Encodable {
|
public struct Message: Encodable {
|
||||||
public struct MessageContent: Encodable {
|
public struct MessageContent: Encodable {
|
||||||
public struct ImageUrl: Encodable {
|
public struct ImageUrl: Encodable {
|
||||||
public let url: URL
|
public let url: URL
|
||||||
}
|
}
|
||||||
|
|
||||||
public let type: String
|
public let type: String
|
||||||
public let text: String?
|
public let text: String?
|
||||||
public let imageUrl: ImageUrl?
|
public let imageUrl: ImageUrl?
|
||||||
}
|
}
|
||||||
|
|
||||||
public let role = "user"
|
public let role = "user"
|
||||||
public let content: [MessageContent]
|
public let content: [MessageContent]
|
||||||
}
|
}
|
||||||
|
@ -77,8 +78,8 @@ public struct OpenAIClient {
|
||||||
case let .emphasize(input):
|
case let .emphasize(input):
|
||||||
ChatRequest(content: "Make this text catchy, more fun: \(input)", temperature: 1)
|
ChatRequest(content: "Make this text catchy, more fun: \(input)", temperature: 1)
|
||||||
case let .imageDescription(image):
|
case let .imageDescription(image):
|
||||||
VisionRequest(messages: [.init(content: [.init(type: "text", text: "What’s in this image? Be brief, it's for image alt description on a social network. Don't write in the first person.", imageUrl: nil)
|
VisionRequest(messages: [.init(content: [.init(type: "text", text: "What’s in this image? Be brief, it's for image alt description on a social network. Don't write in the first person.", imageUrl: nil),
|
||||||
, .init(type: "image_url", text: nil, imageUrl: .init(url: image))])])
|
.init(type: "image_url", text: nil, imageUrl: .init(url: image))])])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,8 +54,8 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadSelectedType() {
|
func loadSelectedType() {
|
||||||
self.client = client
|
client = client
|
||||||
|
|
||||||
guard let value = UserDefaults.standard.string(forKey: filterKey)
|
guard let value = UserDefaults.standard.string(forKey: filterKey)
|
||||||
else {
|
else {
|
||||||
selectedType = nil
|
selectedType = nil
|
||||||
|
|
|
@ -114,7 +114,8 @@ import SwiftUI
|
||||||
indentationLevelPreviousCache = [:]
|
indentationLevelPreviousCache = [:]
|
||||||
for status in statuses {
|
for status in statuses {
|
||||||
if let inReplyToId = status.inReplyToId,
|
if let inReplyToId = status.inReplyToId,
|
||||||
let prevIndent = indentationLevelPreviousCache[inReplyToId] {
|
let prevIndent = indentationLevelPreviousCache[inReplyToId]
|
||||||
|
{
|
||||||
indentationLevelPreviousCache[status.id] = prevIndent + 1
|
indentationLevelPreviousCache[status.id] = prevIndent + 1
|
||||||
} else {
|
} else {
|
||||||
indentationLevelPreviousCache[status.id] = 0
|
indentationLevelPreviousCache[status.id] = 0
|
||||||
|
@ -137,14 +138,14 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIndentationLevel(id: String, maxIndent: UInt) -> (indentationLevel: UInt, extraInset: Double) {
|
func getIndentationLevel(id: String, maxIndent: UInt) -> (indentationLevel: UInt, extraInset: Double) {
|
||||||
let level = min(indentationLevelPreviousCache[id] ?? 0, maxIndent)
|
let level = min(indentationLevelPreviousCache[id] ?? 0, maxIndent)
|
||||||
|
|
||||||
let barSize = Double(level) * 2
|
let barSize = Double(level) * 2
|
||||||
let spaceBetween = (Double(level) - 1) * 3
|
let spaceBetween = (Double(level) - 1) * 3
|
||||||
let size = barSize + spaceBetween + 8
|
let size = barSize + spaceBetween + 8
|
||||||
|
|
||||||
return (level, size)
|
return (level, size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,49 @@
|
||||||
import SwiftUI
|
|
||||||
import GiphyUISDK
|
|
||||||
import UIKit
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
|
import GiphyUISDK
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
struct GifPickerView: UIViewControllerRepresentable {
|
struct GifPickerView: UIViewControllerRepresentable {
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
var completion: ((String) -> Void)
|
var completion: (String) -> Void
|
||||||
var onShouldDismissGifPicker: () -> Void
|
var onShouldDismissGifPicker: () -> Void
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> GiphyViewController {
|
func makeUIViewController(context: Context) -> GiphyViewController {
|
||||||
Giphy.configure(apiKey: "MIylJkNX57vcUNZxmSODKU9dQKBgXCkV")
|
Giphy.configure(apiKey: "MIylJkNX57vcUNZxmSODKU9dQKBgXCkV")
|
||||||
|
|
||||||
let controller = GiphyViewController()
|
let controller = GiphyViewController()
|
||||||
controller.swiftUIEnabled = true
|
controller.swiftUIEnabled = true
|
||||||
controller.mediaTypeConfig = [.gifs, .stickers, .recents]
|
controller.mediaTypeConfig = [.gifs, .stickers, .recents]
|
||||||
controller.delegate = context.coordinator
|
controller.delegate = context.coordinator
|
||||||
controller.navigationController?.isNavigationBarHidden = true
|
controller.navigationController?.isNavigationBarHidden = true
|
||||||
controller.navigationController?.setNavigationBarHidden(true, animated: false)
|
controller.navigationController?.setNavigationBarHidden(true, animated: false)
|
||||||
|
|
||||||
GiphyViewController.trayHeightMultiplier = 1.0
|
GiphyViewController.trayHeightMultiplier = 1.0
|
||||||
|
|
||||||
controller.theme = GPHTheme(type: theme.selectedScheme == .dark ? .darkBlur : .lightBlur)
|
controller.theme = GPHTheme(type: theme.selectedScheme == .dark ? .darkBlur : .lightBlur)
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
|
func updateUIViewController(_: UIViewControllerType, context _: Context) {}
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
func makeCoordinator() -> Coordinator {
|
||||||
GifPickerView.Coordinator(parent: self)
|
GifPickerView.Coordinator(parent: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Coordinator: NSObject, GiphyDelegate {
|
class Coordinator: NSObject, GiphyDelegate {
|
||||||
var parent: GifPickerView
|
var parent: GifPickerView
|
||||||
|
|
||||||
init(parent: GifPickerView) {
|
init(parent: GifPickerView) {
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
func didDismiss(controller: GiphyViewController?) {
|
func didDismiss(controller _: GiphyViewController?) {
|
||||||
self.parent.onShouldDismissGifPicker()
|
parent.onShouldDismissGifPicker()
|
||||||
}
|
}
|
||||||
|
|
||||||
func didSelectMedia(giphyViewController: GiphyViewController, media: GPHMedia) {
|
func didSelectMedia(giphyViewController _: GiphyViewController, media: GPHMedia) {
|
||||||
let url = media.url(rendition: .fixedWidth, fileType: .gif)
|
let url = media.url(rendition: .fixedWidth, fileType: .gif)
|
||||||
parent.completion(url ?? "")
|
parent.completion(url ?? "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
|
import GiphyUISDK
|
||||||
import Models
|
import Models
|
||||||
import NukeUI
|
import NukeUI
|
||||||
import PhotosUI
|
import PhotosUI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import GiphyUISDK
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct StatusEditorAccessoryView: View {
|
struct StatusEditorAccessoryView: View {
|
||||||
|
@ -41,19 +41,19 @@ struct StatusEditorAccessoryView: View {
|
||||||
} label: {
|
} label: {
|
||||||
Label("status.editor.photo-library", systemImage: "photo")
|
Label("status.editor.photo-library", systemImage: "photo")
|
||||||
}
|
}
|
||||||
#if !targetEnvironment(macCatalyst)
|
#if !targetEnvironment(macCatalyst)
|
||||||
Button {
|
Button {
|
||||||
isCameraPickerPresented = true
|
isCameraPickerPresented = true
|
||||||
} label: {
|
} label: {
|
||||||
Label("status.editor.camera-picker", systemImage: "camera")
|
Label("status.editor.camera-picker", systemImage: "camera")
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
Button {
|
Button {
|
||||||
isFileImporterPresented = true
|
isFileImporterPresented = true
|
||||||
} label: {
|
} label: {
|
||||||
Label("status.editor.browse-file", systemImage: "folder")
|
Label("status.editor.browse-file", systemImage: "folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
isGIFPickerPresented = true
|
isGIFPickerPresented = true
|
||||||
} label: {
|
} label: {
|
||||||
|
@ -70,8 +70,7 @@ struct StatusEditorAccessoryView: View {
|
||||||
selection: $viewModel.mediaPickers,
|
selection: $viewModel.mediaPickers,
|
||||||
maxSelectionCount: 4,
|
maxSelectionCount: 4,
|
||||||
matching: .any(of: [.images, .videos]),
|
matching: .any(of: [.images, .videos]),
|
||||||
photoLibrary: .shared()
|
photoLibrary: .shared())
|
||||||
)
|
|
||||||
.fileImporter(isPresented: $isFileImporterPresented,
|
.fileImporter(isPresented: $isFileImporterPresented,
|
||||||
allowedContentTypes: [.image, .video],
|
allowedContentTypes: [.image, .video],
|
||||||
allowsMultipleSelection: true)
|
allowsMultipleSelection: true)
|
||||||
|
@ -92,7 +91,7 @@ struct StatusEditorAccessoryView: View {
|
||||||
})
|
})
|
||||||
.sheet(isPresented: $isGIFPickerPresented, content: {
|
.sheet(isPresented: $isGIFPickerPresented, content: {
|
||||||
GifPickerView { url in
|
GifPickerView { url in
|
||||||
GPHCache.shared.downloadAssetData(url) { data, error in
|
GPHCache.shared.downloadAssetData(url) { data, _ in
|
||||||
guard let data else { return }
|
guard let data else { return }
|
||||||
viewModel.processGIFData(data: data)
|
viewModel.processGIFData(data: data)
|
||||||
}
|
}
|
||||||
|
@ -109,7 +108,7 @@ struct StatusEditorAccessoryView: View {
|
||||||
// all SEVM have the same visibility value
|
// all SEVM have the same visibility value
|
||||||
followUpSEVMs.append(StatusEditorViewModel(mode: .new(visibility: focusedSEVM.visibility)))
|
followUpSEVMs.append(StatusEditorViewModel(mode: .new(visibility: focusedSEVM.visibility)))
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "arrowshape.turn.up.left.circle.fill")
|
Image(systemName: "arrowshape.turn.up.left.circle.fill")
|
||||||
}
|
}
|
||||||
.disabled(!canAddNewSEVM)
|
.disabled(!canAddNewSEVM)
|
||||||
|
|
||||||
|
@ -213,8 +212,8 @@ struct StatusEditorAccessoryView: View {
|
||||||
private var canAddNewSEVM: Bool {
|
private var canAddNewSEVM: Bool {
|
||||||
guard followUpSEVMs.count < 5 else { return false }
|
guard followUpSEVMs.count < 5 else { return false }
|
||||||
|
|
||||||
if followUpSEVMs.isEmpty, // there is only mainSEVM on the editor
|
if followUpSEVMs.isEmpty, // there is only mainSEVM on the editor
|
||||||
!focusedSEVM.statusText.string.isEmpty // focusedSEVM is also mainSEVM
|
!focusedSEVM.statusText.string.isEmpty // focusedSEVM is also mainSEVM
|
||||||
{ return true }
|
{ return true }
|
||||||
|
|
||||||
if let lastSEVMs = followUpSEVMs.last,
|
if let lastSEVMs = followUpSEVMs.last,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Foundation
|
||||||
import Models
|
import Models
|
||||||
|
|
||||||
struct StatusEditorCategorizedEmojiContainer: Identifiable, Equatable {
|
struct StatusEditorCategorizedEmojiContainer: Identifiable, Equatable {
|
||||||
let id = UUID().uuidString
|
let id = UUID().uuidString
|
||||||
let categoryName: String
|
let categoryName: String
|
||||||
var emojis: [Emoji]
|
var emojis: [Emoji]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct StatusEditorMediaEditView: View {
|
struct StatusEditorMediaEditView: View {
|
||||||
|
@ -11,7 +11,7 @@ struct StatusEditorMediaEditView: View {
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
@Environment(CurrentInstance.self) private var currentInstance
|
@Environment(CurrentInstance.self) private var currentInstance
|
||||||
@Environment(UserPreferences.self) private var preferences
|
@Environment(UserPreferences.self) private var preferences
|
||||||
|
|
||||||
var viewModel: StatusEditorViewModel
|
var viewModel: StatusEditorViewModel
|
||||||
let container: StatusEditorMediaContainer
|
let container: StatusEditorMediaContainer
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ struct StatusEditorMediaEditView: View {
|
||||||
|
|
||||||
@State private var didAppear: Bool = false
|
@State private var didAppear: Bool = false
|
||||||
@State private var isGeneratingDescription: Bool = false
|
@State private var isGeneratingDescription: Bool = false
|
||||||
|
|
||||||
@State private var showTranslateButton: Bool = false
|
@State private var showTranslateButton: Bool = false
|
||||||
@State private var isTranslating: Bool = false
|
@State private var isTranslating: Bool = false
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ struct StatusEditorMediaEditView: View {
|
||||||
.preferredColorScheme(theme.selectedScheme == .dark ? .dark : .light)
|
.preferredColorScheme(theme.selectedScheme == .dark ? .dark : .light)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var generateButton: some View {
|
private var generateButton: some View {
|
||||||
if let url = container.mediaAttachment?.url, preferences.isOpenAIEnabled {
|
if let url = container.mediaAttachment?.url, preferences.isOpenAIEnabled {
|
||||||
|
@ -133,7 +133,7 @@ struct StatusEditorMediaEditView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var translateButton: some View {
|
private var translateButton: some View {
|
||||||
if showTranslateButton {
|
if showTranslateButton {
|
||||||
|
@ -153,10 +153,9 @@ struct StatusEditorMediaEditView: View {
|
||||||
Text("status.action.translate")
|
Text("status.action.translate")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateDescription(url: URL) async -> String? {
|
private func generateDescription(url: URL) async -> String? {
|
||||||
isGeneratingDescription = true
|
isGeneratingDescription = true
|
||||||
let client = OpenAIClient()
|
let client = OpenAIClient()
|
||||||
|
@ -164,7 +163,7 @@ struct StatusEditorMediaEditView: View {
|
||||||
isGeneratingDescription = false
|
isGeneratingDescription = false
|
||||||
return response?.trimmedText
|
return response?.trimmedText
|
||||||
}
|
}
|
||||||
|
|
||||||
private func translateDescription() async -> String? {
|
private func translateDescription() async -> String? {
|
||||||
isTranslating = true
|
isTranslating = true
|
||||||
let userAPIKey = DeepLUserAPIHandler.readIfAllowed()
|
let userAPIKey = DeepLUserAPIHandler.readIfAllowed()
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import AVKit
|
import AVKit
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
|
import MediaUI
|
||||||
import Models
|
import Models
|
||||||
import NukeUI
|
import NukeUI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import MediaUI
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct StatusEditorMediaView: View {
|
struct StatusEditorMediaView: View {
|
||||||
|
@ -49,17 +49,17 @@ struct StatusEditorMediaView: View {
|
||||||
private let containerHeight: CGFloat = 300
|
private let containerHeight: CGFloat = 300
|
||||||
private var containerWidth: CGFloat { containerHeight / 1.5 }
|
private var containerWidth: CGFloat { containerHeight / 1.5 }
|
||||||
|
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
private var showsScrollIndicators : Bool { count > 1 }
|
private var showsScrollIndicators: Bool { count > 1 }
|
||||||
private var scrollBottomPadding : CGFloat? = nil
|
private var scrollBottomPadding: CGFloat?
|
||||||
#else
|
#else
|
||||||
private var showsScrollIndicators : Bool = false
|
private var showsScrollIndicators: Bool = false
|
||||||
private var scrollBottomPadding : CGFloat? = 0
|
private var scrollBottomPadding: CGFloat? = 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
init(viewModel: StatusEditorViewModel, editingMediaContainer: Binding<StatusEditorMediaContainer?>) {
|
init(viewModel: StatusEditorViewModel, editingMediaContainer: Binding<StatusEditorMediaContainer?>) {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
self._editingMediaContainer = editingMediaContainer
|
_editingMediaContainer = editingMediaContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
private func pixel(at index: Int) -> some View {
|
private func pixel(at index: Int) -> some View {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import SwiftUI
|
|
||||||
import Models
|
|
||||||
import Env
|
|
||||||
import DesignSystem
|
|
||||||
import Accounts
|
import Accounts
|
||||||
import AppAccount
|
import AppAccount
|
||||||
|
import DesignSystem
|
||||||
|
import Env
|
||||||
|
import Models
|
||||||
import Network
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct StatusEditorCoreView: View {
|
struct StatusEditorCoreView: View {
|
||||||
|
@ -22,11 +22,11 @@ struct StatusEditorCoreView: View {
|
||||||
@Environment(CurrentAccount.self) private var currentAccount
|
@Environment(CurrentAccount.self) private var currentAccount
|
||||||
@Environment(AppAccountsManager.self) private var appAccounts
|
@Environment(AppAccountsManager.self) private var appAccounts
|
||||||
@Environment(Client.self) private var client
|
@Environment(Client.self) private var client
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
@Environment(\.dismissWindow) private var dismissWindow
|
@Environment(\.dismissWindow) private var dismissWindow
|
||||||
#else
|
#else
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
|
@ -145,11 +145,11 @@ struct StatusEditorCoreView: View {
|
||||||
viewModel.preferences = preferences
|
viewModel.preferences = preferences
|
||||||
viewModel.prepareStatusText()
|
viewModel.prepareStatusText()
|
||||||
if !client.isAuth {
|
if !client.isAuth {
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
dismissWindow()
|
dismissWindow()
|
||||||
#else
|
#else
|
||||||
dismiss()
|
dismiss()
|
||||||
#endif
|
#endif
|
||||||
NotificationCenter.default.post(name: .shareSheetClose, object: nil)
|
NotificationCenter.default.post(name: .shareSheetClose, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import SwiftUI
|
|
||||||
import Models
|
import Models
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusEditorPrivacyMenu: View {
|
struct StatusEditorPrivacyMenu: View {
|
||||||
@Binding var visibility: Models.Visibility
|
@Binding var visibility: Models.Visibility
|
||||||
|
@ -8,7 +8,7 @@ struct StatusEditorPrivacyMenu: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Menu {
|
Menu {
|
||||||
ForEach(Models.Visibility.allCases, id: \.self) { vis in
|
ForEach(Models.Visibility.allCases, id: \.self) { vis in
|
||||||
Button { self.visibility = vis } label: {
|
Button { visibility = vis } label: {
|
||||||
Label(vis.title, systemImage: vis.iconName)
|
Label(vis.title, systemImage: vis.iconName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,4 +29,3 @@ struct StatusEditorPrivacyMenu: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import SwiftUI
|
|
||||||
import Env
|
import Env
|
||||||
import Models
|
import Models
|
||||||
import StoreKit
|
import StoreKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct StatusEditorToolbarItems: ToolbarContent {
|
struct StatusEditorToolbarItems: ToolbarContent {
|
||||||
|
@ -12,11 +12,11 @@ struct StatusEditorToolbarItems: ToolbarContent {
|
||||||
|
|
||||||
@Environment(\.modelContext) private var context
|
@Environment(\.modelContext) private var context
|
||||||
@Environment(UserPreferences.self) private var preferences
|
@Environment(UserPreferences.self) private var preferences
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
@Environment(\.dismissWindow) private var dismissWindow
|
@Environment(\.dismissWindow) private var dismissWindow
|
||||||
#else
|
#else
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var body: some ToolbarContent {
|
var body: some ToolbarContent {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
@ -81,18 +81,18 @@ struct StatusEditorToolbarItems: ToolbarContent {
|
||||||
private func postStatus(with model: StatusEditorViewModel, isMainPost: Bool) async -> Status? {
|
private func postStatus(with model: StatusEditorViewModel, isMainPost: Bool) async -> Status? {
|
||||||
let status = await model.postStatus()
|
let status = await model.postStatus()
|
||||||
|
|
||||||
if status != nil && isMainPost {
|
if status != nil, isMainPost {
|
||||||
close()
|
close()
|
||||||
SoundEffectManager.shared.playSound(.tootSent)
|
SoundEffectManager.shared.playSound(.tootSent)
|
||||||
NotificationCenter.default.post(name: .shareSheetClose, object: nil)
|
NotificationCenter.default.post(name: .shareSheetClose, object: nil)
|
||||||
#if !targetEnvironment(macCatalyst)
|
#if !targetEnvironment(macCatalyst)
|
||||||
if !mainSEVM.mode.isInShareExtension, !preferences.requestedReview {
|
if !mainSEVM.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
|
#endif
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
@ -109,11 +109,11 @@ struct StatusEditorToolbarItems: ToolbarContent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
private func close() { dismissWindow() }
|
private func close() { dismissWindow() }
|
||||||
#else
|
#else
|
||||||
private func close() { dismiss() }
|
private func close() { dismiss() }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var languageConfirmationDialog: some View {
|
private var languageConfirmationDialog: some View {
|
||||||
|
|
|
@ -24,7 +24,7 @@ public struct StatusEditorView: View {
|
||||||
@State private var scrollID: UUID?
|
@State private var scrollID: UUID?
|
||||||
|
|
||||||
@FocusState private var editorFocusState: StatusEditorFocusState?
|
@FocusState private var editorFocusState: StatusEditorFocusState?
|
||||||
|
|
||||||
private var focusedSEVM: StatusEditorViewModel {
|
private var focusedSEVM: StatusEditorViewModel {
|
||||||
if case let .followUp(id) = editorFocusState,
|
if case let .followUp(id) = editorFocusState,
|
||||||
let sevm = followUpSEVMs.first(where: { $0.id == id })
|
let sevm = followUpSEVMs.first(where: { $0.id == id })
|
||||||
|
@ -38,7 +38,7 @@ public struct StatusEditorView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
@Bindable var focusedSEVM = self.focusedSEVM
|
@Bindable var focusedSEVM = focusedSEVM
|
||||||
|
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
@ -54,7 +54,7 @@ public struct StatusEditorView: View {
|
||||||
)
|
)
|
||||||
.id(mainSEVM.id)
|
.id(mainSEVM.id)
|
||||||
|
|
||||||
ForEach(followUpSEVMs) { sevm in
|
ForEach(followUpSEVMs) { sevm in
|
||||||
@Bindable var sevm: StatusEditorViewModel = sevm
|
@Bindable var sevm: StatusEditorViewModel = sevm
|
||||||
|
|
||||||
StatusEditorCoreView(
|
StatusEditorCoreView(
|
||||||
|
@ -93,7 +93,8 @@ public struct StatusEditorView: View {
|
||||||
Button("OK") {}
|
Button("OK") {}
|
||||||
}, message: {
|
}, message: {
|
||||||
Text(mainSEVM.postingError ?? "")
|
Text(mainSEVM.postingError ?? "")
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.interactiveDismissDisabled(mainSEVM.shouldDisplayDismissWarning)
|
.interactiveDismissDisabled(mainSEVM.shouldDisplayDismissWarning)
|
||||||
.onChange(of: appAccounts.currentClient) { _, newValue in
|
.onChange(of: appAccounts.currentClient) { _, newValue in
|
||||||
if mainSEVM.mode.isInShareExtension {
|
if mainSEVM.mode.isInShareExtension {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import SwiftUI
|
||||||
@MainActor
|
@MainActor
|
||||||
@Observable public class StatusEditorViewModel: NSObject, Identifiable {
|
@Observable public class StatusEditorViewModel: NSObject, Identifiable {
|
||||||
public let id = UUID()
|
public let id = UUID()
|
||||||
|
|
||||||
var mode: Mode
|
var mode: Mode
|
||||||
|
|
||||||
var client: Client?
|
var client: Client?
|
||||||
|
@ -94,7 +94,7 @@ import SwiftUI
|
||||||
|
|
||||||
let removedIDs = oldValue
|
let removedIDs = oldValue
|
||||||
.filter { !mediaPickers.contains($0) }
|
.filter { !mediaPickers.contains($0) }
|
||||||
.compactMap { $0.itemIdentifier }
|
.compactMap(\.itemIdentifier)
|
||||||
mediaContainers.removeAll { removedIDs.contains($0.id) }
|
mediaContainers.removeAll { removedIDs.contains($0.id) }
|
||||||
|
|
||||||
let newPickerItems = mediaPickers.filter { !oldValue.contains($0) }
|
let newPickerItems = mediaPickers.filter { !oldValue.contains($0) }
|
||||||
|
@ -297,7 +297,8 @@ import SwiftUI
|
||||||
movieTransferable: nil,
|
movieTransferable: nil,
|
||||||
gifTransferable: nil,
|
gifTransferable: nil,
|
||||||
mediaAttachment: $0,
|
mediaAttachment: $0,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
case let .quote(status):
|
case let .quote(status):
|
||||||
embeddedStatus = status
|
embeddedStatus = status
|
||||||
|
@ -384,7 +385,7 @@ import SwiftUI
|
||||||
.compactMap { NSItemProvider(contentsOf: $0) }
|
.compactMap { NSItemProvider(contentsOf: $0) }
|
||||||
processItemsProvider(items: items)
|
processItemsProvider(items: items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func processGIFData(data: Data) {
|
func processGIFData(data: Data) {
|
||||||
isMediasLoading = true
|
isMediasLoading = true
|
||||||
let url = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).gif")
|
let url = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).gif")
|
||||||
|
@ -405,7 +406,8 @@ import SwiftUI
|
||||||
movieTransferable: nil,
|
movieTransferable: nil,
|
||||||
gifTransferable: nil,
|
gifTransferable: nil,
|
||||||
mediaAttachment: nil,
|
mediaAttachment: nil,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
prepareToPost(for: container)
|
prepareToPost(for: container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,7 +430,8 @@ import SwiftUI
|
||||||
movieTransferable: nil,
|
movieTransferable: nil,
|
||||||
gifTransferable: nil,
|
gifTransferable: nil,
|
||||||
mediaAttachment: nil,
|
mediaAttachment: nil,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
prepareToPost(for: container)
|
prepareToPost(for: container)
|
||||||
} else if let content = content as? ImageFileTranseferable,
|
} else if let content = content as? ImageFileTranseferable,
|
||||||
let compressedData = await compressor.compressImageFrom(url: content.url),
|
let compressedData = await compressor.compressImageFrom(url: content.url),
|
||||||
|
@ -440,7 +443,8 @@ import SwiftUI
|
||||||
movieTransferable: nil,
|
movieTransferable: nil,
|
||||||
gifTransferable: nil,
|
gifTransferable: nil,
|
||||||
mediaAttachment: nil,
|
mediaAttachment: nil,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
prepareToPost(for: container)
|
prepareToPost(for: container)
|
||||||
} else if let video = content as? MovieFileTranseferable {
|
} else if let video = content as? MovieFileTranseferable {
|
||||||
let container = StatusEditorMediaContainer(
|
let container = StatusEditorMediaContainer(
|
||||||
|
@ -449,7 +453,8 @@ import SwiftUI
|
||||||
movieTransferable: video,
|
movieTransferable: video,
|
||||||
gifTransferable: nil,
|
gifTransferable: nil,
|
||||||
mediaAttachment: nil,
|
mediaAttachment: nil,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
prepareToPost(for: container)
|
prepareToPost(for: container)
|
||||||
} else if let gif = content as? GifFileTranseferable {
|
} else if let gif = content as? GifFileTranseferable {
|
||||||
let container = StatusEditorMediaContainer(
|
let container = StatusEditorMediaContainer(
|
||||||
|
@ -458,7 +463,8 @@ import SwiftUI
|
||||||
movieTransferable: nil,
|
movieTransferable: nil,
|
||||||
gifTransferable: gif,
|
gifTransferable: gif,
|
||||||
mediaAttachment: nil,
|
mediaAttachment: nil,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
prepareToPost(for: container)
|
prepareToPost(for: container)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -619,7 +625,8 @@ import SwiftUI
|
||||||
movieTransferable: nil,
|
movieTransferable: nil,
|
||||||
gifTransferable: gifFile,
|
gifTransferable: gifFile,
|
||||||
mediaAttachment: nil,
|
mediaAttachment: nil,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func makeMovieContainer(from pickerItem: PhotosPickerItem) async -> StatusEditorMediaContainer? {
|
private static func makeMovieContainer(from pickerItem: PhotosPickerItem) async -> StatusEditorMediaContainer? {
|
||||||
|
@ -631,7 +638,8 @@ import SwiftUI
|
||||||
movieTransferable: movieFile,
|
movieTransferable: movieFile,
|
||||||
gifTransferable: nil,
|
gifTransferable: nil,
|
||||||
mediaAttachment: nil,
|
mediaAttachment: nil,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func makeImageContainer(from pickerItem: PhotosPickerItem) async -> StatusEditorMediaContainer? {
|
private static func makeImageContainer(from pickerItem: PhotosPickerItem) async -> StatusEditorMediaContainer? {
|
||||||
|
@ -649,7 +657,8 @@ import SwiftUI
|
||||||
movieTransferable: nil,
|
movieTransferable: nil,
|
||||||
gifTransferable: nil,
|
gifTransferable: nil,
|
||||||
mediaAttachment: nil,
|
mediaAttachment: nil,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func upload(container: StatusEditorMediaContainer) async {
|
func upload(container: StatusEditorMediaContainer) async {
|
||||||
|
@ -662,7 +671,8 @@ import SwiftUI
|
||||||
movieTransferable: originalContainer.movieTransferable,
|
movieTransferable: originalContainer.movieTransferable,
|
||||||
gifTransferable: nil,
|
gifTransferable: nil,
|
||||||
mediaAttachment: nil,
|
mediaAttachment: nil,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
mediaContainers[index] = newContainer
|
mediaContainers[index] = newContainer
|
||||||
do {
|
do {
|
||||||
let compressor = StatusEditorCompressor()
|
let compressor = StatusEditorCompressor()
|
||||||
|
@ -676,7 +686,8 @@ import SwiftUI
|
||||||
movieTransferable: nil,
|
movieTransferable: nil,
|
||||||
gifTransferable: nil,
|
gifTransferable: nil,
|
||||||
mediaAttachment: uploadedMedia,
|
mediaAttachment: uploadedMedia,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if let uploadedMedia, uploadedMedia.url == nil {
|
if let uploadedMedia, uploadedMedia.url == nil {
|
||||||
scheduleAsyncMediaRefresh(mediaAttachement: uploadedMedia)
|
scheduleAsyncMediaRefresh(mediaAttachement: uploadedMedia)
|
||||||
|
@ -693,7 +704,8 @@ import SwiftUI
|
||||||
movieTransferable: originalContainer.movieTransferable,
|
movieTransferable: originalContainer.movieTransferable,
|
||||||
gifTransferable: nil,
|
gifTransferable: nil,
|
||||||
mediaAttachment: uploadedMedia,
|
mediaAttachment: uploadedMedia,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if let uploadedMedia, uploadedMedia.url == nil {
|
if let uploadedMedia, uploadedMedia.url == nil {
|
||||||
scheduleAsyncMediaRefresh(mediaAttachement: uploadedMedia)
|
scheduleAsyncMediaRefresh(mediaAttachement: uploadedMedia)
|
||||||
|
@ -707,7 +719,8 @@ import SwiftUI
|
||||||
movieTransferable: nil,
|
movieTransferable: nil,
|
||||||
gifTransferable: originalContainer.gifTransferable,
|
gifTransferable: originalContainer.gifTransferable,
|
||||||
mediaAttachment: uploadedMedia,
|
mediaAttachment: uploadedMedia,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if let uploadedMedia, uploadedMedia.url == nil {
|
if let uploadedMedia, uploadedMedia.url == nil {
|
||||||
scheduleAsyncMediaRefresh(mediaAttachement: uploadedMedia)
|
scheduleAsyncMediaRefresh(mediaAttachement: uploadedMedia)
|
||||||
|
@ -721,7 +734,8 @@ import SwiftUI
|
||||||
movieTransferable: nil,
|
movieTransferable: nil,
|
||||||
gifTransferable: nil,
|
gifTransferable: nil,
|
||||||
mediaAttachment: nil,
|
mediaAttachment: nil,
|
||||||
error: error)
|
error: error
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -747,7 +761,8 @@ import SwiftUI
|
||||||
movieTransferable: oldContainer.movieTransferable,
|
movieTransferable: oldContainer.movieTransferable,
|
||||||
gifTransferable: oldContainer.gifTransferable,
|
gifTransferable: oldContainer.gifTransferable,
|
||||||
mediaAttachment: newAttachement,
|
mediaAttachment: newAttachement,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
print(error.localizedDescription)
|
print(error.localizedDescription)
|
||||||
|
@ -770,7 +785,8 @@ import SwiftUI
|
||||||
movieTransferable: nil,
|
movieTransferable: nil,
|
||||||
gifTransferable: nil,
|
gifTransferable: nil,
|
||||||
mediaAttachment: media,
|
mediaAttachment: media,
|
||||||
error: nil)
|
error: nil
|
||||||
|
)
|
||||||
} catch { print(error) }
|
} catch { print(error) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -815,9 +831,9 @@ import SwiftUI
|
||||||
|
|
||||||
return dict
|
return dict
|
||||||
}.sorted(by: { lhs, rhs in
|
}.sorted(by: { lhs, rhs in
|
||||||
if rhs.key == "Uncategorized" { return false }
|
if rhs.key == "Uncategorized" { false }
|
||||||
else if lhs.key == "Uncategorized" { return true }
|
else if lhs.key == "Uncategorized" { true }
|
||||||
else { return lhs.key < rhs.key }
|
else { lhs.key < rhs.key }
|
||||||
}).forEach { key, value in
|
}).forEach { key, value in
|
||||||
emojiContainers.append(.init(categoryName: key, emojis: value))
|
emojiContainers.append(.init(categoryName: key, emojis: value))
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,10 +45,9 @@ extension TextView.Representable {
|
||||||
textView.allowsEditingTextAttributes = false
|
textView.allowsEditingTextAttributes = false
|
||||||
textView.returnKeyType = .default
|
textView.returnKeyType = .default
|
||||||
textView.allowsEditingTextAttributes = true
|
textView.allowsEditingTextAttributes = true
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
textView.inlinePredictionType = .no
|
textView.inlinePredictionType = .no
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
self.getTextView?(textView)
|
self.getTextView?(textView)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ public struct StatusRowView: View {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
if !isCompact {
|
if !isCompact {
|
||||||
HStack(spacing: 3) {
|
HStack(spacing: 3) {
|
||||||
ForEach(0..<indentationLevel, id: \.self) { level in
|
ForEach(0 ..< indentationLevel, id: \.self) { level in
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(theme.tintColor)
|
.fill(theme.tintColor)
|
||||||
.frame(width: 2)
|
.frame(width: 2)
|
||||||
|
@ -225,13 +225,14 @@ public struct StatusRowView: View {
|
||||||
Button("accessibility.status.media-viewer-action.label") {
|
Button("accessibility.status.media-viewer-action.label") {
|
||||||
HapticManager.shared.fireHaptic(.notification(.success))
|
HapticManager.shared.fireHaptic(.notification(.success))
|
||||||
let attachments = viewModel.finalStatus.mediaAttachments
|
let attachments = viewModel.finalStatus.mediaAttachments
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
openWindow(value: WindowDestinationMedia.mediaViewer(
|
openWindow(value: WindowDestinationMedia.mediaViewer(
|
||||||
attachments: attachments,
|
attachments: attachments,
|
||||||
selectedAttachment: attachments[0]))
|
selectedAttachment: attachments[0]
|
||||||
#else
|
))
|
||||||
quickLook.prepareFor(selectedMediaAttachment: attachments[0], mediaAttachments: attachments)
|
#else
|
||||||
#endif
|
quickLook.prepareFor(selectedMediaAttachment: attachments[0], mediaAttachments: attachments)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ import SwiftUI
|
||||||
var isLoadingRemoteContent: Bool = false
|
var isLoadingRemoteContent: Bool = false
|
||||||
var localStatusId: String?
|
var localStatusId: String?
|
||||||
var localStatus: Status?
|
var localStatus: Status?
|
||||||
|
|
||||||
private var scrollToId = nil as Binding<String?>?
|
private var scrollToId = nil as Binding<String?>?
|
||||||
|
|
||||||
// The relationship our user has to the author of this post, if available
|
// The relationship our user has to the author of this post, if available
|
||||||
|
@ -199,9 +199,9 @@ import SwiftUI
|
||||||
routerPath.navigate(to: .accountDetail(id: mention.id))
|
routerPath.navigate(to: .accountDetail(id: mention.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func goToParent() {
|
func goToParent() {
|
||||||
guard let id = status.inReplyToId else {return}
|
guard let id = status.inReplyToId else { return }
|
||||||
if let _ = scrollToId {
|
if let _ = scrollToId {
|
||||||
scrollToId?.wrappedValue = id
|
scrollToId?.wrappedValue = id
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -205,11 +205,11 @@ struct StatusRowActionsView: View {
|
||||||
switch action {
|
switch action {
|
||||||
case .respond:
|
case .respond:
|
||||||
SoundEffectManager.shared.playSound(.share)
|
SoundEffectManager.shared.playSound(.share)
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
openWindow(value: WindowDestinationEditor.replyToStatusEditor(status: viewModel.localStatus ?? viewModel.status))
|
openWindow(value: WindowDestinationEditor.replyToStatusEditor(status: viewModel.localStatus ?? viewModel.status))
|
||||||
#else
|
#else
|
||||||
viewModel.routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.localStatus ?? viewModel.status)
|
viewModel.routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.localStatus ?? viewModel.status)
|
||||||
#endif
|
#endif
|
||||||
case .favorite:
|
case .favorite:
|
||||||
SoundEffectManager.shared.playSound(.favorite)
|
SoundEffectManager.shared.playSound(.favorite)
|
||||||
await statusDataController.toggleFavorite(remoteStatus: viewModel.localStatusId)
|
await statusDataController.toggleFavorite(remoteStatus: viewModel.localStatusId)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import NukeUI
|
||||||
import Shimmer
|
import Shimmer
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
@MainActor
|
||||||
public struct StatusRowCardView: View {
|
public struct StatusRowCardView: View {
|
||||||
@Environment(\.openURL) private var openURL
|
@Environment(\.openURL) private var openURL
|
||||||
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
||||||
|
@ -46,7 +47,7 @@ public struct StatusRowCardView: View {
|
||||||
} label: {
|
} label: {
|
||||||
if let title = card.title, let url = URL(string: card.url) {
|
if let title = card.title, let url = URL(string: card.url) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
let sitesWithIcons = [ "apps.apple.com", "music.apple.com", "open.spotify.com" ]
|
let sitesWithIcons = ["apps.apple.com", "music.apple.com", "open.spotify.com"]
|
||||||
if let host = url.host(), sitesWithIcons.contains(host) {
|
if let host = url.host(), sitesWithIcons.contains(host) {
|
||||||
iconLinkPreview(title, url)
|
iconLinkPreview(title, url)
|
||||||
} else {
|
} else {
|
||||||
|
@ -83,56 +84,53 @@ public struct StatusRowCardView: View {
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@ViewBuilder
|
||||||
private func defaultLinkPreview(_ title: String, _ url: URL) -> some View {
|
private func defaultLinkPreview(_ title: String, _ url: URL) -> some View {
|
||||||
Group {
|
if let imageURL = card.image, !isInCaptureMode {
|
||||||
if let imageURL = card.image, !isInCaptureMode {
|
LazyResizableImage(url: imageURL) { state, proxy in
|
||||||
LazyResizableImage(url: imageURL) { state, proxy in
|
let width = imageWidthFor(proxy: proxy)
|
||||||
let width = imageWidthFor(proxy: proxy)
|
if let image = state.image {
|
||||||
if let image = state.image {
|
image
|
||||||
image
|
.resizable()
|
||||||
.resizable()
|
.aspectRatio(contentMode: .fill)
|
||||||
.aspectRatio(contentMode: .fill)
|
.frame(height: imageHeight)
|
||||||
.frame(height: imageHeight)
|
.frame(maxWidth: width)
|
||||||
.frame(maxWidth: width)
|
.clipped()
|
||||||
.clipped()
|
} else if state.isLoading {
|
||||||
} else if state.isLoading {
|
Rectangle()
|
||||||
Rectangle()
|
.fill(Color.gray)
|
||||||
.fill(Color.gray)
|
.frame(height: imageHeight)
|
||||||
.frame(height: imageHeight)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// This image is decorative
|
|
||||||
.accessibilityHidden(true)
|
|
||||||
.frame(height: imageHeight)
|
|
||||||
}
|
}
|
||||||
HStack {
|
// This image is decorative
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
.accessibilityHidden(true)
|
||||||
Text(title)
|
.frame(height: imageHeight)
|
||||||
.font(.scaledHeadline)
|
|
||||||
.lineLimit(3)
|
|
||||||
if let description = card.description, !description.isEmpty {
|
|
||||||
Text(description)
|
|
||||||
.font(.scaledBody)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.lineLimit(3)
|
|
||||||
}
|
|
||||||
Text(url.host() ?? url.absoluteString)
|
|
||||||
.font(.scaledFootnote)
|
|
||||||
.foregroundColor(theme.tintColor)
|
|
||||||
.lineLimit(1)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}.padding(16)
|
|
||||||
}
|
}
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
|
Text(title)
|
||||||
|
.font(.scaledHeadline)
|
||||||
|
.lineLimit(3)
|
||||||
|
if let description = card.description, !description.isEmpty {
|
||||||
|
Text(description)
|
||||||
|
.font(.scaledBody)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.lineLimit(3)
|
||||||
|
}
|
||||||
|
Text(url.host() ?? url.absoluteString)
|
||||||
|
.font(.scaledFootnote)
|
||||||
|
.foregroundColor(theme.tintColor)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}.padding(16)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
|
||||||
private func iconLinkPreview(_ title: String, _ url: URL) -> some View {
|
private func iconLinkPreview(_ title: String, _ url: URL) -> some View {
|
||||||
// ..where the image is known to be a square icon
|
// ..where the image is known to be a square icon
|
||||||
HStack {
|
HStack {
|
||||||
if let imageURL = card.image, !isInCaptureMode {
|
if let imageURL = card.image, !isInCaptureMode {
|
||||||
LazyResizableImage(url: imageURL) { state, proxy in
|
LazyResizableImage(url: imageURL) { state, _ in
|
||||||
if let image = state.image {
|
if let image = state.image {
|
||||||
image
|
image
|
||||||
.resizable()
|
.resizable()
|
||||||
|
@ -149,7 +147,7 @@ public struct StatusRowCardView: View {
|
||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
.frame(width: imageHeight, height: imageHeight)
|
.frame(width: imageHeight, height: imageHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(.scaledHeadline)
|
.font(.scaledHeadline)
|
||||||
|
|
|
@ -55,20 +55,20 @@ struct StatusRowContextMenu: View {
|
||||||
systemImage: "bookmark")
|
systemImage: "bookmark")
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
openWindow(value: WindowDestinationEditor.replyToStatusEditor(status: viewModel.status))
|
openWindow(value: WindowDestinationEditor.replyToStatusEditor(status: viewModel.status))
|
||||||
#else
|
#else
|
||||||
viewModel.routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)
|
viewModel.routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)
|
||||||
#endif
|
#endif
|
||||||
} label: {
|
} label: {
|
||||||
Label("status.action.reply", systemImage: "arrowshape.turn.up.left")
|
Label("status.action.reply", systemImage: "arrowshape.turn.up.left")
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
openWindow(value: WindowDestinationEditor.quoteStatusEditor(status: viewModel.status))
|
openWindow(value: WindowDestinationEditor.quoteStatusEditor(status: viewModel.status))
|
||||||
#else
|
#else
|
||||||
viewModel.routerPath.presentedSheet = .quoteStatusEditor(status: viewModel.status)
|
viewModel.routerPath.presentedSheet = .quoteStatusEditor(status: viewModel.status)
|
||||||
#endif
|
#endif
|
||||||
} label: {
|
} label: {
|
||||||
Label("status.action.quote", systemImage: "quote.bubble")
|
Label("status.action.quote", systemImage: "quote.bubble")
|
||||||
}
|
}
|
||||||
|
@ -171,11 +171,11 @@ struct StatusRowContextMenu: View {
|
||||||
}
|
}
|
||||||
if currentInstance.isEditSupported {
|
if currentInstance.isEditSupported {
|
||||||
Button {
|
Button {
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
openWindow(value: WindowDestinationEditor.editStatusEditor(status: viewModel.status.reblogAsAsStatus ?? viewModel.status))
|
openWindow(value: WindowDestinationEditor.editStatusEditor(status: viewModel.status.reblogAsAsStatus ?? viewModel.status))
|
||||||
#else
|
#else
|
||||||
viewModel.routerPath.presentedSheet = .editStatusEditor(status: viewModel.status.reblogAsAsStatus ?? viewModel.status)
|
viewModel.routerPath.presentedSheet = .editStatusEditor(status: viewModel.status.reblogAsAsStatus ?? viewModel.status)
|
||||||
#endif
|
#endif
|
||||||
} label: {
|
} label: {
|
||||||
Label("status.action.edit", systemImage: "pencil")
|
Label("status.action.edit", systemImage: "pencil")
|
||||||
}
|
}
|
||||||
|
@ -307,7 +307,7 @@ struct SelectTextView: View {
|
||||||
struct SelectableText: UIViewRepresentable {
|
struct SelectableText: UIViewRepresentable {
|
||||||
let content: AttributedString
|
let content: AttributedString
|
||||||
|
|
||||||
func makeUIView(context: Context) -> UITextView {
|
func makeUIView(context _: Context) -> UITextView {
|
||||||
let attributedText = NSMutableAttributedString(content)
|
let attributedText = NSMutableAttributedString(content)
|
||||||
attributedText.addAttribute(
|
attributedText.addAttribute(
|
||||||
.font,
|
.font,
|
||||||
|
@ -323,6 +323,6 @@ struct SelectableText: UIViewRepresentable {
|
||||||
return textView
|
return textView
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIView(_ uiView: UITextView, context: Context) {}
|
func updateUIView(_: UITextView, context _: Context) {}
|
||||||
func makeCoordinator() -> Void {}
|
func makeCoordinator() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,13 +55,13 @@ struct StatusRowHeaderView: View {
|
||||||
Group {
|
Group {
|
||||||
EmojiTextApp(.init(stringValue: viewModel.finalStatus.account.safeDisplayName),
|
EmojiTextApp(.init(stringValue: viewModel.finalStatus.account.safeDisplayName),
|
||||||
emojis: viewModel.finalStatus.account.emojis)
|
emojis: viewModel.finalStatus.account.emojis)
|
||||||
.font(.scaledSubheadline)
|
.font(.scaledSubheadline)
|
||||||
.foregroundColor(theme.labelColor)
|
.foregroundColor(theme.labelColor)
|
||||||
.emojiSize(Font.scaledSubheadlineFont.emojiSize)
|
.emojiSize(Font.scaledSubheadlineFont.emojiSize)
|
||||||
.emojiBaselineOffset(Font.scaledSubheadlineFont.emojiBaselineOffset)
|
.emojiBaselineOffset(Font.scaledSubheadlineFont.emojiBaselineOffset)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.accountPopover(viewModel.finalStatus.account)
|
.accountPopover(viewModel.finalStatus.account)
|
||||||
|
|
||||||
accountBadgeView
|
accountBadgeView
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
|
|
|
@ -102,19 +102,19 @@ public struct StatusRowMediaPreviewView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func tabAction(for index: Int) {
|
private func tabAction(for index: Int) {
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
openWindow(
|
openWindow(
|
||||||
value: WindowDestinationMedia.mediaViewer(
|
value: WindowDestinationMedia.mediaViewer(
|
||||||
attachments: attachments,
|
attachments: attachments,
|
||||||
selectedAttachment: attachments[index]
|
selectedAttachment: attachments[index]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
#else
|
#else
|
||||||
quickLook.prepareFor(
|
quickLook.prepareFor(
|
||||||
selectedMediaAttachment: attachments[index],
|
selectedMediaAttachment: attachments[index],
|
||||||
mediaAttachments: attachments
|
mediaAttachments: attachments
|
||||||
)
|
)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func accessibilityLabel(for attachment: MediaAttachment) -> Text {
|
private static func accessibilityLabel(for attachment: MediaAttachment) -> Text {
|
||||||
|
@ -137,10 +137,10 @@ private struct MediaPreview: View {
|
||||||
@Environment(\.isCompact) private var isCompact: Bool
|
@Environment(\.isCompact) private var isCompact: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { proxy in
|
GeometryReader { _ in
|
||||||
switch displayData.type {
|
switch displayData.type {
|
||||||
case .image:
|
case .image:
|
||||||
LazyResizableImage(url: displayData.previewUrl) { state, proxy in
|
LazyResizableImage(url: displayData.previewUrl) { state, _ in
|
||||||
if let image = state.image {
|
if let image = state.image {
|
||||||
image
|
image
|
||||||
.resizable()
|
.resizable()
|
||||||
|
@ -247,7 +247,7 @@ private struct FeaturedImagePreView: View {
|
||||||
let boxWidth = availableWidth - appLayoutWidth
|
let boxWidth = availableWidth - appLayoutWidth
|
||||||
let boxHeight = availableHeight * 0.8 // use only 80% of window height to leave room for text
|
let boxHeight = availableHeight * 0.8 // use only 80% of window height to leave room for text
|
||||||
|
|
||||||
if from.width <= boxWidth && from.height <= boxHeight {
|
if from.width <= boxWidth, from.height <= boxHeight {
|
||||||
// intrinsic size of image fits just fine
|
// intrinsic size of image fits just fine
|
||||||
return from
|
return from
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,30 +7,30 @@ struct StatusRowReplyView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if let accountId = viewModel.status.inReplyToAccountId {
|
if let accountId = viewModel.status.inReplyToAccountId {
|
||||||
Group {
|
Group {
|
||||||
if let mention = viewModel.status.mentions.first(where: { $0.id == accountId }) {
|
if let mention = viewModel.status.mentions.first(where: { $0.id == accountId }) {
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
Image(systemName: "arrowshape.turn.up.left.fill")
|
Image(systemName: "arrowshape.turn.up.left.fill")
|
||||||
Text("status.row.was-reply \(mention.username)")
|
Text("status.row.was-reply \(mention.username)")
|
||||||
}
|
}
|
||||||
.accessibilityElement(children: .combine)
|
.accessibilityElement(children: .combine)
|
||||||
.accessibilityLabel(
|
.accessibilityLabel(
|
||||||
Text("status.row.was-reply \(mention.username)")
|
Text("status.row.was-reply \(mention.username)")
|
||||||
)
|
)
|
||||||
} else if viewModel.isThread && accountId == viewModel.status.account.id {
|
} else if viewModel.isThread, accountId == viewModel.status.account.id {
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
Image(systemName: "quote.opening")
|
Image(systemName: "quote.opening")
|
||||||
Text("status.row.is-thread")
|
Text("status.row.is-thread")
|
||||||
}
|
}
|
||||||
.accessibilityElement(children: .combine)
|
.accessibilityElement(children: .combine)
|
||||||
.accessibilityLabel(
|
.accessibilityLabel(
|
||||||
Text("status.row.is-thread")
|
Text("status.row.is-thread")
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
.onTapGesture {
|
|
||||||
viewModel.goToParent()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
viewModel.goToParent()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.font(.scaledFootnote)
|
.font(.scaledFootnote)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import SwiftUI
|
|
||||||
import Env
|
import Env
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusRowTagView: View {
|
struct StatusRowTagView: View {
|
||||||
@Environment(CurrentAccount.self) private var currentAccount
|
@Environment(CurrentAccount.self) private var currentAccount
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import Charts
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
import Models
|
import Models
|
||||||
|
@ -7,7 +8,6 @@ import Status
|
||||||
import SwiftData
|
import SwiftData
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SwiftUIIntrospect
|
import SwiftUIIntrospect
|
||||||
import Charts
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct TimelineView: View {
|
public struct TimelineView: View {
|
||||||
|
@ -157,7 +157,7 @@ public struct TimelineView: View {
|
||||||
HStack {
|
HStack {
|
||||||
TagChartView(tag: tag)
|
TagChartView(tag: tag)
|
||||||
.padding(.top, 12)
|
.padding(.top, 12)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text("#\(tag.name)")
|
Text("#\(tag.name)")
|
||||||
.font(.scaledHeadline)
|
.font(.scaledHeadline)
|
||||||
|
|
|
@ -202,7 +202,7 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
// Else we fetch top most page from the API.
|
// Else we fetch top most page from the API.
|
||||||
if let cachedStatuses = await getCachedStatuses(),
|
if let cachedStatuses = await getCachedStatuses(),
|
||||||
!cachedStatuses.isEmpty,
|
!cachedStatuses.isEmpty,
|
||||||
timeline == .home && !UserPreferences.shared.fastRefreshEnabled
|
timeline == .home, !UserPreferences.shared.fastRefreshEnabled
|
||||||
{
|
{
|
||||||
await datasource.set(cachedStatuses)
|
await datasource.set(cachedStatuses)
|
||||||
if let latestSeenId = await cache.getLatestSeenStatus(for: client)?.last,
|
if let latestSeenId = await cache.getLatestSeenStatus(for: client)?.last,
|
||||||
|
|
Loading…
Reference in a new issue