Compare commits
No commits in common. "c7a56a9f6173ed284fe7849a34c0ecc8d90a256f" and "db534e5993987f129a404c6eb5188fef376d0ba2" have entirely different histories.
c7a56a9f61
...
db534e5993
|
@ -25,17 +25,6 @@ public struct Attachment: Codable, Sendable {
|
||||||
], nil))
|
], nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(id: String, kind: Attachment.Kind, url: URL, remoteURL: URL? = nil, previewURL: URL? = nil, meta: Attachment.Metadata? = nil, description: String? = nil, blurHash: String? = nil) {
|
|
||||||
self.id = id
|
|
||||||
self.kind = kind
|
|
||||||
self.url = url
|
|
||||||
self.remoteURL = remoteURL
|
|
||||||
self.previewURL = previewURL
|
|
||||||
self.meta = meta
|
|
||||||
self.description = description
|
|
||||||
self.blurHash = blurHash
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
self.id = try container.decode(String.self, forKey: .id)
|
self.id = try container.decode(String.self, forKey: .id)
|
||||||
|
|
|
@ -26,38 +26,6 @@ public struct Card: Codable, Sendable {
|
||||||
/// Only present when returned from the trending links endpoint
|
/// Only present when returned from the trending links endpoint
|
||||||
public let history: [History]?
|
public let history: [History]?
|
||||||
|
|
||||||
public init(
|
|
||||||
url: WebURL,
|
|
||||||
title: String,
|
|
||||||
description: String,
|
|
||||||
image: WebURL? = nil,
|
|
||||||
kind: Card.Kind,
|
|
||||||
authorName: String? = nil,
|
|
||||||
authorURL: WebURL? = nil,
|
|
||||||
providerName: String? = nil,
|
|
||||||
providerURL: WebURL? = nil,
|
|
||||||
html: String? = nil,
|
|
||||||
width: Int? = nil,
|
|
||||||
height: Int? = nil,
|
|
||||||
blurhash: String? = nil,
|
|
||||||
history: [History]? = nil
|
|
||||||
) {
|
|
||||||
self.url = url
|
|
||||||
self.title = title
|
|
||||||
self.description = description
|
|
||||||
self.image = image
|
|
||||||
self.kind = kind
|
|
||||||
self.authorName = authorName
|
|
||||||
self.authorURL = authorURL
|
|
||||||
self.providerName = providerName
|
|
||||||
self.providerURL = providerURL
|
|
||||||
self.html = html
|
|
||||||
self.width = width
|
|
||||||
self.height = height
|
|
||||||
self.blurhash = blurhash
|
|
||||||
self.history = history
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0427033922B31269000D31B6 /* AdvancedPrefsView.swift */; };
|
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0427033922B31269000D31B6 /* AdvancedPrefsView.swift */; };
|
||||||
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450531E22B0097E00100BA2 /* Timline+UI.swift */; };
|
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450531E22B0097E00100BA2 /* Timline+UI.swift */; };
|
||||||
04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04586B4022B2FFB10021BD04 /* PreferencesView.swift */; };
|
04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04586B4022B2FFB10021BD04 /* PreferencesView.swift */; };
|
||||||
|
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04586B4222B301470021BD04 /* AppearancePrefsView.swift */; };
|
||||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
|
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
||||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
||||||
|
@ -290,8 +291,6 @@
|
||||||
D6C3F4FB299035650009FCFF /* TrendsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F4FA299035650009FCFF /* TrendsViewController.swift */; };
|
D6C3F4FB299035650009FCFF /* TrendsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F4FA299035650009FCFF /* TrendsViewController.swift */; };
|
||||||
D6C3F5172991C1A00009FCFF /* View+AppListStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F5162991C1A00009FCFF /* View+AppListStyle.swift */; };
|
D6C3F5172991C1A00009FCFF /* View+AppListStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F5162991C1A00009FCFF /* View+AppListStyle.swift */; };
|
||||||
D6C3F5192991F5D60009FCFF /* TimelineJumpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F5182991F5D60009FCFF /* TimelineJumpButton.swift */; };
|
D6C3F5192991F5D60009FCFF /* TimelineJumpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F5182991F5D60009FCFF /* TimelineJumpButton.swift */; };
|
||||||
D6C4532D2BCB86AC00E26A0E /* AppearancePrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C4532C2BCB86AC00E26A0E /* AppearancePrefsView.swift */; };
|
|
||||||
D6C4532F2BCB873400E26A0E /* MockStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C4532E2BCB873400E26A0E /* MockStatusView.swift */; };
|
|
||||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; };
|
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; };
|
||||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */; };
|
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */; };
|
||||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */; };
|
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */; };
|
||||||
|
@ -436,6 +435,7 @@
|
||||||
0427033922B31269000D31B6 /* AdvancedPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedPrefsView.swift; sourceTree = "<group>"; };
|
0427033922B31269000D31B6 /* AdvancedPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedPrefsView.swift; sourceTree = "<group>"; };
|
||||||
0450531E22B0097E00100BA2 /* Timline+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timline+UI.swift"; sourceTree = "<group>"; };
|
0450531E22B0097E00100BA2 /* Timline+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timline+UI.swift"; sourceTree = "<group>"; };
|
||||||
04586B4022B2FFB10021BD04 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
04586B4022B2FFB10021BD04 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
||||||
|
04586B4222B301470021BD04 /* AppearancePrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePrefsView.swift; sourceTree = "<group>"; };
|
||||||
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
|
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
|
||||||
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
||||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -714,8 +714,6 @@
|
||||||
D6C3F5162991C1A00009FCFF /* View+AppListStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+AppListStyle.swift"; sourceTree = "<group>"; };
|
D6C3F5162991C1A00009FCFF /* View+AppListStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+AppListStyle.swift"; sourceTree = "<group>"; };
|
||||||
D6C3F5182991F5D60009FCFF /* TimelineJumpButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineJumpButton.swift; sourceTree = "<group>"; };
|
D6C3F5182991F5D60009FCFF /* TimelineJumpButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineJumpButton.swift; sourceTree = "<group>"; };
|
||||||
D6C4532A2BCAD7F900E26A0E /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
D6C4532A2BCAD7F900E26A0E /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||||
D6C4532C2BCB86AC00E26A0E /* AppearancePrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePrefsView.swift; sourceTree = "<group>"; };
|
|
||||||
D6C4532E2BCB873400E26A0E /* MockStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockStatusView.swift; sourceTree = "<group>"; };
|
|
||||||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = "<group>"; };
|
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = "<group>"; };
|
||||||
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
|
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
|
||||||
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; };
|
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -1172,6 +1170,7 @@
|
||||||
D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */,
|
D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */,
|
||||||
04586B4022B2FFB10021BD04 /* PreferencesView.swift */,
|
04586B4022B2FFB10021BD04 /* PreferencesView.swift */,
|
||||||
D64B96802BC3279D002C8990 /* PrefsAccountView.swift */,
|
D64B96802BC3279D002C8990 /* PrefsAccountView.swift */,
|
||||||
|
04586B4222B301470021BD04 /* AppearancePrefsView.swift */,
|
||||||
D61F75892932E1FC00C0B37F /* SwipeActionsPrefsView.swift */,
|
D61F75892932E1FC00C0B37F /* SwipeActionsPrefsView.swift */,
|
||||||
D6958F3C2AA383D90062FE52 /* WidescreenNavigationPrefsView.swift */,
|
D6958F3C2AA383D90062FE52 /* WidescreenNavigationPrefsView.swift */,
|
||||||
0427033722B30F5F000D31B6 /* BehaviorPrefsView.swift */,
|
0427033722B30F5F000D31B6 /* BehaviorPrefsView.swift */,
|
||||||
|
@ -1182,7 +1181,6 @@
|
||||||
0427033922B31269000D31B6 /* AdvancedPrefsView.swift */,
|
0427033922B31269000D31B6 /* AdvancedPrefsView.swift */,
|
||||||
D67895BF246870DE00D4CD9E /* LocalAccountAvatarView.swift */,
|
D67895BF246870DE00D4CD9E /* LocalAccountAvatarView.swift */,
|
||||||
D68A76ED295369C7001DA1B3 /* AcknowledgementsView.swift */,
|
D68A76ED295369C7001DA1B3 /* AcknowledgementsView.swift */,
|
||||||
D6C4532B2BCB86A100E26A0E /* Appearance */,
|
|
||||||
D64B96822BC3892B002C8990 /* Notifications */,
|
D64B96822BC3892B002C8990 /* Notifications */,
|
||||||
D60089172981FEA4005B4D00 /* Tip Jar */,
|
D60089172981FEA4005B4D00 /* Tip Jar */,
|
||||||
D68A76EF2953910A001DA1B3 /* About */,
|
D68A76EF2953910A001DA1B3 /* About */,
|
||||||
|
@ -1487,15 +1485,6 @@
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D6C4532B2BCB86A100E26A0E /* Appearance */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D6C4532C2BCB86AC00E26A0E /* AppearancePrefsView.swift */,
|
|
||||||
D6C4532E2BCB873400E26A0E /* MockStatusView.swift */,
|
|
||||||
);
|
|
||||||
path = Appearance;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D6C693FA2162FE5D007D6A6D /* Utilities */ = {
|
D6C693FA2162FE5D007D6A6D /* Utilities */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2113,7 +2102,6 @@
|
||||||
D61F759B29384F9C00C0B37F /* FilterMO.swift in Sources */,
|
D61F759B29384F9C00C0B37F /* FilterMO.swift in Sources */,
|
||||||
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */,
|
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */,
|
||||||
D6934F342BA8D65A002B1C8D /* ImageGalleryDataSource.swift in Sources */,
|
D6934F342BA8D65A002B1C8D /* ImageGalleryDataSource.swift in Sources */,
|
||||||
D6C4532D2BCB86AC00E26A0E /* AppearancePrefsView.swift in Sources */,
|
|
||||||
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */,
|
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */,
|
||||||
D6BC74862AFC4772000DD603 /* SuggestedProfileCardView.swift in Sources */,
|
D6BC74862AFC4772000DD603 /* SuggestedProfileCardView.swift in Sources */,
|
||||||
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */,
|
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */,
|
||||||
|
@ -2302,6 +2290,7 @@
|
||||||
D60089192981FEBA005B4D00 /* ConfettiView.swift in Sources */,
|
D60089192981FEBA005B4D00 /* ConfettiView.swift in Sources */,
|
||||||
D61F759929384D4D00C0B37F /* CustomizeTimelinesView.swift in Sources */,
|
D61F759929384D4D00C0B37F /* CustomizeTimelinesView.swift in Sources */,
|
||||||
D61F759F29385AD800C0B37F /* SemiCaseSensitiveComparator.swift in Sources */,
|
D61F759F29385AD800C0B37F /* SemiCaseSensitiveComparator.swift in Sources */,
|
||||||
|
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
|
||||||
D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */,
|
D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */,
|
||||||
D646DCD42A0729440059ECEB /* ActionNotificationGroupCollectionViewCell.swift in Sources */,
|
D646DCD42A0729440059ECEB /* ActionNotificationGroupCollectionViewCell.swift in Sources */,
|
||||||
D69261272BB3BA610023152C /* Box.swift in Sources */,
|
D69261272BB3BA610023152C /* Box.swift in Sources */,
|
||||||
|
@ -2370,7 +2359,6 @@
|
||||||
D65B4B562971F98300DABDFB /* ReportView.swift in Sources */,
|
D65B4B562971F98300DABDFB /* ReportView.swift in Sources */,
|
||||||
D6934F2E2BA7AEF9002B1C8D /* StatusAttachmentsGalleryDataSource.swift in Sources */,
|
D6934F2E2BA7AEF9002B1C8D /* StatusAttachmentsGalleryDataSource.swift in Sources */,
|
||||||
D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */,
|
D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */,
|
||||||
D6C4532F2BCB873400E26A0E /* MockStatusView.swift in Sources */,
|
|
||||||
D68A76E829527884001DA1B3 /* PinnedTimelinesView.swift in Sources */,
|
D68A76E829527884001DA1B3 /* PinnedTimelinesView.swift in Sources */,
|
||||||
D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */,
|
D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */,
|
||||||
D6D12B5A292D684600D528E1 /* AccountListViewController.swift in Sources */,
|
D6D12B5A292D684600D528E1 /* AccountListViewController.swift in Sources */,
|
||||||
|
|
|
@ -104,9 +104,7 @@ struct AboutView: View {
|
||||||
|
|
||||||
private var appIcon: some View {
|
private var appIcon: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Image("AboutIcon")
|
AppIconView()
|
||||||
.resizable()
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 256 / 6.4))
|
|
||||||
.shadow(radius: 6, y: 3)
|
.shadow(radius: 6, y: 3)
|
||||||
.frame(width: 256, height: 256)
|
.frame(width: 256, height: 256)
|
||||||
|
|
||||||
|
@ -127,6 +125,20 @@ struct AboutView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct AppIconView: UIViewRepresentable {
|
||||||
|
func makeUIView(context: Context) -> UIImageView {
|
||||||
|
let view = UIImageView(image: UIImage(named: "AboutIcon"))
|
||||||
|
view.contentMode = .scaleAspectFit
|
||||||
|
view.layer.cornerRadius = 256 / 6.4
|
||||||
|
view.layer.cornerCurve = .continuous
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UIImageView, context: Context) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private struct MailSheet: UIViewControllerRepresentable {
|
private struct MailSheet: UIViewControllerRepresentable {
|
||||||
typealias UIViewControllerType = MFMailComposeViewController
|
typealias UIViewControllerType = MFMailComposeViewController
|
||||||
|
|
||||||
|
|
|
@ -1,123 +0,0 @@
|
||||||
//
|
|
||||||
// AppearancePrefsView.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 6/13/19.
|
|
||||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import TuskerPreferences
|
|
||||||
|
|
||||||
struct AppearancePrefsView: View {
|
|
||||||
@ObservedObject private var preferences = Preferences.shared
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
List {
|
|
||||||
Section {
|
|
||||||
MockStatusView()
|
|
||||||
.padding(.top, 8)
|
|
||||||
}
|
|
||||||
.appGroupedListRowBackground()
|
|
||||||
|
|
||||||
accountsSection
|
|
||||||
postsSection
|
|
||||||
mediaSection
|
|
||||||
}
|
|
||||||
.listStyle(.insetGrouped)
|
|
||||||
.appGroupedListBackground(container: PreferencesNavigationController.self)
|
|
||||||
.navigationTitle("Appearance")
|
|
||||||
}
|
|
||||||
|
|
||||||
private var accountsSection: some View {
|
|
||||||
Section("Accounts") {
|
|
||||||
Toggle(isOn: Binding(get: {
|
|
||||||
preferences.avatarStyle == .circle
|
|
||||||
}, set: {
|
|
||||||
preferences.avatarStyle = $0 ? .circle : .roundRect
|
|
||||||
})) {
|
|
||||||
Text("Use Circular Avatars")
|
|
||||||
}
|
|
||||||
Toggle(isOn: $preferences.hideCustomEmojiInUsernames) {
|
|
||||||
Text("Hide Custom Emoji in Usernames")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.appGroupedListRowBackground()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var postsSection: some View {
|
|
||||||
Section("Posts") {
|
|
||||||
Toggle(isOn: $preferences.showIsStatusReplyIcon) {
|
|
||||||
Text("Show Status Reply Icons")
|
|
||||||
}
|
|
||||||
Toggle(isOn: $preferences.alwaysShowStatusVisibilityIcon) {
|
|
||||||
Text("Always Show Status Visibility Icons")
|
|
||||||
}
|
|
||||||
Toggle(isOn: $preferences.showLinkPreviews) {
|
|
||||||
Text("Show Link Previews")
|
|
||||||
}
|
|
||||||
Toggle(isOn: $preferences.showAttachmentsInTimeline) {
|
|
||||||
Text("Show Attachments on Timeline")
|
|
||||||
}
|
|
||||||
Toggle(isOn: $preferences.hideActionsInTimeline) {
|
|
||||||
Text("Hide Actions on Timeline")
|
|
||||||
}
|
|
||||||
Toggle(isOn: $preferences.underlineTextLinks) {
|
|
||||||
Text("Underline Links")
|
|
||||||
}
|
|
||||||
// NavigationLink("Leading Swipe Actions") {
|
|
||||||
// SwipeActionsPrefsView(selection: $preferences.leadingStatusSwipeActions)
|
|
||||||
// .edgesIgnoringSafeArea(.all)
|
|
||||||
// .navigationTitle("Leading Swipe Actions")
|
|
||||||
// }
|
|
||||||
// NavigationLink("Trailing Swipe Actions") {
|
|
||||||
// SwipeActionsPrefsView(selection: $preferences.trailingStatusSwipeActions)
|
|
||||||
// .edgesIgnoringSafeArea(.all)
|
|
||||||
// .navigationTitle("Trailing Swipe Actions")
|
|
||||||
}
|
|
||||||
.appGroupedListRowBackground()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var mediaSection: some View {
|
|
||||||
Section("Media") {
|
|
||||||
Picker(selection: $preferences.attachmentBlurMode) {
|
|
||||||
ForEach(AttachmentBlurMode.allCases, id: \.self) { mode in
|
|
||||||
Text(mode.displayName).tag(mode)
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Text("Blur Media")
|
|
||||||
}
|
|
||||||
|
|
||||||
Toggle(isOn: $preferences.blurMediaBehindContentWarning) {
|
|
||||||
Text("Blur Media Behind Content Warning")
|
|
||||||
}
|
|
||||||
.disabled(preferences.attachmentBlurMode != .useStatusSetting)
|
|
||||||
|
|
||||||
Toggle(isOn: $preferences.automaticallyPlayGifs) {
|
|
||||||
Text("Automatically Play GIFs")
|
|
||||||
}
|
|
||||||
|
|
||||||
Toggle(isOn: $preferences.showUncroppedMediaInline) {
|
|
||||||
Text("Show Uncropped Media Inline")
|
|
||||||
}
|
|
||||||
|
|
||||||
Toggle(isOn: $preferences.showAttachmentBadges) {
|
|
||||||
Text("Show GIF/\(Text("Alt").font(.body.lowercaseSmallCaps())) Badges")
|
|
||||||
}
|
|
||||||
|
|
||||||
Toggle(isOn: $preferences.attachmentAltBadgeInverted) {
|
|
||||||
Text("Show Badge when Missing \(Text("Alt").font(.body.lowercaseSmallCaps()))")
|
|
||||||
}
|
|
||||||
.disabled(!preferences.showAttachmentBadges)
|
|
||||||
}
|
|
||||||
.appGroupedListRowBackground()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
struct AppearancePrefsView_Previews : PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
AppearancePrefsView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,270 +0,0 @@
|
||||||
//
|
|
||||||
// MockStatusView.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 4/13/24.
|
|
||||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Pachyderm
|
|
||||||
import WebURL
|
|
||||||
|
|
||||||
struct MockStatusView: View {
|
|
||||||
@ObservedObject private var preferences = Preferences.shared
|
|
||||||
@ScaledMetric(relativeTo: .body) private var attachmentsLabelHeight = 17
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack(alignment: .top, spacing: 8) {
|
|
||||||
VStack(spacing: 4) {
|
|
||||||
Image("AboutIcon")
|
|
||||||
.resizable()
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: preferences.avatarStyle.cornerRadiusFraction * 50))
|
|
||||||
.frame(width: 50, height: 50)
|
|
||||||
|
|
||||||
MockMetaIndicatorsView()
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
HStack(spacing: 4) {
|
|
||||||
MockDisplayNameLabel()
|
|
||||||
Text(verbatim: "@tusker@example.com")
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.font(.body.weight(.light))
|
|
||||||
.lineLimit(1)
|
|
||||||
.truncationMode(.tail)
|
|
||||||
.layoutPriority(-100)
|
|
||||||
Spacer()
|
|
||||||
Text("1h")
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.font(.body.weight(.light))
|
|
||||||
}
|
|
||||||
|
|
||||||
MockStatusContentView()
|
|
||||||
|
|
||||||
if preferences.showLinkPreviews {
|
|
||||||
MockStatusCardView()
|
|
||||||
.frame(height: StatusContentContainer.cardViewHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
MockAttachmentsContainerView()
|
|
||||||
.aspectRatio(preferences.showAttachmentsInTimeline ? 16/9 : nil, contentMode: .fill)
|
|
||||||
.frame(height: preferences.showAttachmentsInTimeline ? nil : attachmentsLabelHeight)
|
|
||||||
.padding(.bottom, preferences.showAttachmentsInTimeline && preferences.hideActionsInTimeline ? 8 : 0)
|
|
||||||
|
|
||||||
if !preferences.hideActionsInTimeline {
|
|
||||||
MockStatusActionButtons()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.layoutPriority(100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct MockMetaIndicatorsView: UIViewRepresentable {
|
|
||||||
@ObservedObject private var preferences = Preferences.shared
|
|
||||||
|
|
||||||
func makeUIView(context: Context) -> StatusMetaIndicatorsView {
|
|
||||||
let view = StatusMetaIndicatorsView()
|
|
||||||
view.primaryAxis = .vertical
|
|
||||||
view.secondaryAxisAlignment = .trailing
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: StatusMetaIndicatorsView, context: Context) {
|
|
||||||
var indicators: StatusMetaIndicatorsView.Indicator = []
|
|
||||||
if preferences.showIsStatusReplyIcon {
|
|
||||||
indicators.insert(.reply)
|
|
||||||
}
|
|
||||||
if preferences.alwaysShowStatusVisibilityIcon {
|
|
||||||
indicators.insert(.visibility)
|
|
||||||
}
|
|
||||||
uiView.setIndicators(indicators, visibility: .public)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct MockDisplayNameLabel: View {
|
|
||||||
@ObservedObject private var preferences = Preferences.shared
|
|
||||||
@ScaledMetric(relativeTo: .body) private var emojiSize = 17
|
|
||||||
@State var textWithImage = Text("Tusker")
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
displayName
|
|
||||||
.font(.body.weight(.semibold))
|
|
||||||
// don't let the height change depending on whether emojis are present or not
|
|
||||||
.frame(height: emojiSize)
|
|
||||||
.task(id: emojiSize) {
|
|
||||||
let size = CGSize(width: emojiSize, height: emojiSize)
|
|
||||||
let renderer = UIGraphicsImageRenderer(size: size)
|
|
||||||
let image = renderer.image { ctx in
|
|
||||||
let bounds = CGRect(origin: .zero, size: size)
|
|
||||||
UIBezierPath(roundedRect: bounds, cornerRadius: 2).addClip()
|
|
||||||
UIImage(named: "AboutIcon")!.draw(in: bounds)
|
|
||||||
}
|
|
||||||
textWithImage = Text("Tusker \(Image(uiImage: image))")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var displayName: Text {
|
|
||||||
if preferences.hideCustomEmojiInUsernames {
|
|
||||||
Text("Tusker")
|
|
||||||
} else {
|
|
||||||
textWithImage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct MockStatusContentView: View {
|
|
||||||
@ObservedObject private var preferences = Preferences.shared
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Text("This is an example post so you can check out how things look.\n\nThanks for using \(link)!")
|
|
||||||
.lineLimit(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var link: Text {
|
|
||||||
Text("Tusker")
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
.underline(preferences.underlineTextLinks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct MockStatusCardView: UIViewRepresentable {
|
|
||||||
func makeUIView(context: Context) -> StatusCardView {
|
|
||||||
let view = StatusCardView()
|
|
||||||
view.isUserInteractionEnabled = false
|
|
||||||
let card = Card(
|
|
||||||
url: WebURL("https://vaccor.space/tusker")!,
|
|
||||||
title: "Tusker",
|
|
||||||
description: "Tusker is an iOS app for Mastodon",
|
|
||||||
image: WebURL("https://vaccor.space/tusker/img/icon.png")!,
|
|
||||||
kind: .link
|
|
||||||
)
|
|
||||||
view.updateUI(card: card, sensitive: false)
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: StatusCardView, context: Context) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private actor MockAttachmentsGenerator {
|
|
||||||
static let shared = MockAttachmentsGenerator()
|
|
||||||
|
|
||||||
private var attachmentURLs: [URL]?
|
|
||||||
|
|
||||||
func getAttachmentURLs(displayScale: CGFloat) -> [URL] {
|
|
||||||
if let attachmentURLs,
|
|
||||||
attachmentURLs.allSatisfy({ FileManager.default.fileExists(atPath: $0.path) }) {
|
|
||||||
return attachmentURLs
|
|
||||||
}
|
|
||||||
|
|
||||||
let size = CGSize(width: 100, height: 100)
|
|
||||||
let bounds = CGRect(origin: .zero, size: size)
|
|
||||||
let format = UIGraphicsImageRendererFormat()
|
|
||||||
format.scale = displayScale
|
|
||||||
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
|
||||||
|
|
||||||
let firstImage = renderer.image { ctx in
|
|
||||||
UIColor(red: 0x56 / 255, green: 0x03 / 255, blue: 0xad / 255, alpha: 1).setFill()
|
|
||||||
ctx.fill(bounds)
|
|
||||||
ctx.cgContext.concatenate(CGAffineTransform(1, 0, -0.5, 1, 0, 0))
|
|
||||||
for minX in stride(from: 0, through: 100, by: 30) {
|
|
||||||
UIColor(red: 0x83 / 255, green: 0x67 / 255, blue: 0xc7 / 255, alpha: 1).setFill()
|
|
||||||
ctx.fill(CGRect(x: minX + 20, y: 0, width: 15, height: 100))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let secondImage = renderer.image { ctx in
|
|
||||||
UIColor(red: 0x00 / 255, green: 0x43 / 255, blue: 0x85 / 255, alpha: 1).setFill()
|
|
||||||
ctx.fill(bounds)
|
|
||||||
UIColor(red: 0x05 / 255, green: 0xb2 / 255, blue: 0xdc / 255, alpha: 1).setFill()
|
|
||||||
for y in 0..<2 {
|
|
||||||
for x in 0..<4 {
|
|
||||||
let rect = CGRect(x: x * 45 - 5, y: y * 50 + 15, width: 20, height: 20)
|
|
||||||
ctx.cgContext.fillEllipse(in: rect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UIColor(red: 0x08 / 255, green: 0x7c / 255, blue: 0xa7 / 255, alpha: 1).setFill()
|
|
||||||
for y in 0..<3 {
|
|
||||||
for x in 0..<2 {
|
|
||||||
let rect = CGRect(x: CGFloat(x) * 45 + 22.5, y: CGFloat(y) * 50 - 5, width: 10, height: 10)
|
|
||||||
ctx.cgContext.fillEllipse(in: rect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tempDirectory = FileManager.default.temporaryDirectory
|
|
||||||
let firstURL = tempDirectory.appendingPathComponent("\(UUID().description)", conformingTo: .png)
|
|
||||||
let secondURL = tempDirectory.appendingPathComponent("\(UUID().description)", conformingTo: .png)
|
|
||||||
|
|
||||||
do {
|
|
||||||
try firstImage.pngData()!.write(to: firstURL)
|
|
||||||
try secondImage.pngData()!.write(to: secondURL)
|
|
||||||
attachmentURLs = [firstURL, secondURL]
|
|
||||||
return [firstURL, secondURL]
|
|
||||||
} catch {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct MockAttachmentsContainerView: View {
|
|
||||||
@State private var attachments: [Attachment] = []
|
|
||||||
@Environment(\.displayScale) private var displayScale
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
MockAttachmentsContainerRepresentable(attachments: attachments)
|
|
||||||
.task {
|
|
||||||
let attachmentURLs = await MockAttachmentsGenerator.shared.getAttachmentURLs(displayScale: displayScale)
|
|
||||||
self.attachments = [
|
|
||||||
.init(id: "1", kind: .image, url: attachmentURLs[0], description: "test"),
|
|
||||||
.init(id: "2", kind: .image, url: attachmentURLs[1], description: nil),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct MockAttachmentsContainerRepresentable: UIViewRepresentable {
|
|
||||||
let attachments: [Attachment]
|
|
||||||
@ObservedObject private var preferences = Preferences.shared
|
|
||||||
|
|
||||||
func makeUIView(context: Context) -> AttachmentsContainerView {
|
|
||||||
let view = AttachmentsContainerView()
|
|
||||||
view.isUserInteractionEnabled = false
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: AttachmentsContainerView, context: Context) {
|
|
||||||
uiView.updateUI(attachments: attachments, labelOnly: !preferences.showAttachmentsInTimeline)
|
|
||||||
uiView.contentHidden = preferences.attachmentBlurMode == .always
|
|
||||||
for attachmentView in uiView.attachmentViews.allObjects {
|
|
||||||
attachmentView.updateBadges()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct MockStatusActionButtons: View {
|
|
||||||
var body: some View {
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: "arrowshape.turn.up.left.fill")
|
|
||||||
.foregroundStyle(.tint)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "star.fill")
|
|
||||||
.foregroundStyle(.tint)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "repeat")
|
|
||||||
.foregroundStyle(.yellow)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "ellipsis")
|
|
||||||
.foregroundStyle(.tint)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
MockStatusView()
|
|
||||||
.frame(height: 300)
|
|
||||||
}
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
// AppearancePrefsView.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/13/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
import TuskerPreferences
|
||||||
|
|
||||||
|
struct AppearancePrefsView : View {
|
||||||
|
@ObservedObject var preferences = Preferences.shared
|
||||||
|
|
||||||
|
private var appearanceChangePublisher: some Publisher<Void, Never> {
|
||||||
|
preferences.$theme
|
||||||
|
.map { _ in () }
|
||||||
|
.merge(with: preferences.$pureBlackDarkMode.map { _ in () },
|
||||||
|
preferences.$accentColor.map { _ in () }
|
||||||
|
)
|
||||||
|
// the prefrence publishers are all willSet, but want to notify after the change, so wait one runloop iteration
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var useCircularAvatars: Binding<Bool> = Binding(get: {
|
||||||
|
Preferences.shared.avatarStyle == .circle
|
||||||
|
}) {
|
||||||
|
Preferences.shared.avatarStyle = $0 ? .circle : .roundRect
|
||||||
|
}
|
||||||
|
|
||||||
|
private let accentColorsAndImages: [(AccentColor, UIImage?)] = AccentColor.allCases.map { color in
|
||||||
|
var image: UIImage?
|
||||||
|
if let color = color.color {
|
||||||
|
if #available(iOS 16.0, *) {
|
||||||
|
image = UIImage(systemName: "circle.fill")!.withTintColor(color, renderingMode: .alwaysTemplate).withRenderingMode(.alwaysOriginal)
|
||||||
|
} else {
|
||||||
|
image = UIGraphicsImageRenderer(size: CGSize(width: 20, height: 20)).image { context in
|
||||||
|
color.setFill()
|
||||||
|
context.cgContext.fillEllipse(in: CGRect(x: 0, y: 0, width: 20, height: 20))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (color, image)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
List {
|
||||||
|
themeSection
|
||||||
|
interfaceSection
|
||||||
|
accountsSection
|
||||||
|
postsSection
|
||||||
|
}
|
||||||
|
.listStyle(.insetGrouped)
|
||||||
|
.appGroupedListBackground(container: PreferencesNavigationController.self)
|
||||||
|
.navigationBarTitle(Text("Appearance"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private var themeSection: some View {
|
||||||
|
Section {
|
||||||
|
#if !os(visionOS)
|
||||||
|
Picker(selection: $preferences.theme, label: Text("Theme")) {
|
||||||
|
Text("Use System Theme").tag(UIUserInterfaceStyle.unspecified)
|
||||||
|
Text("Light").tag(UIUserInterfaceStyle.light)
|
||||||
|
Text("Dark").tag(UIUserInterfaceStyle.dark)
|
||||||
|
}
|
||||||
|
|
||||||
|
// macOS system dark mode isn't pure black, so this isn't necessary
|
||||||
|
if !ProcessInfo.processInfo.isMacCatalystApp && !ProcessInfo.processInfo.isiOSAppOnMac {
|
||||||
|
Toggle(isOn: $preferences.pureBlackDarkMode) {
|
||||||
|
Text("Pure Black Dark Mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Picker(selection: $preferences.accentColor, label: Text("Accent Color")) {
|
||||||
|
ForEach(accentColorsAndImages, id: \.0.rawValue) { (color, image) in
|
||||||
|
HStack {
|
||||||
|
Text(color.name)
|
||||||
|
if let image {
|
||||||
|
Spacer()
|
||||||
|
Image(uiImage: image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tag(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onReceive(appearanceChangePublisher) { _ in
|
||||||
|
NotificationCenter.default.post(name: .themePreferenceChanged, object: nil)
|
||||||
|
}
|
||||||
|
.appGroupedListRowBackground()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var interfaceSection: some View {
|
||||||
|
let visionIdiom = UIUserInterfaceIdiom(rawValue: 6)
|
||||||
|
if [visionIdiom, .pad, .mac].contains(UIDevice.current.userInterfaceIdiom) {
|
||||||
|
Section(header: Text("Interface")) {
|
||||||
|
WidescreenNavigationPrefsView()
|
||||||
|
}
|
||||||
|
.appGroupedListRowBackground()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var accountsSection: some View {
|
||||||
|
Section(header: Text("Accounts")) {
|
||||||
|
Toggle(isOn: useCircularAvatars) {
|
||||||
|
Text("Use Circular Avatars")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $preferences.hideCustomEmojiInUsernames) {
|
||||||
|
Text("Hide Custom Emoji in Usernames")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.appGroupedListRowBackground()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var postsSection: some View {
|
||||||
|
Section(header: Text("Posts")) {
|
||||||
|
Toggle(isOn: $preferences.showIsStatusReplyIcon) {
|
||||||
|
Text("Show Status Reply Icons")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $preferences.alwaysShowStatusVisibilityIcon) {
|
||||||
|
Text("Always Show Status Visibility Icons")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $preferences.showLinkPreviews) {
|
||||||
|
Text("Show Link Previews")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $preferences.showAttachmentsInTimeline) {
|
||||||
|
Text("Show Attachments on Timeline")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $preferences.hideActionsInTimeline) {
|
||||||
|
Text("Hide Actions on Timeline")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $preferences.underlineTextLinks) {
|
||||||
|
Text("Underline Links")
|
||||||
|
}
|
||||||
|
NavigationLink("Leading Swipe Actions") {
|
||||||
|
SwipeActionsPrefsView(selection: $preferences.leadingStatusSwipeActions)
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
.navigationTitle("Leading Swipe Actions")
|
||||||
|
}
|
||||||
|
NavigationLink("Trailing Swipe Actions") {
|
||||||
|
SwipeActionsPrefsView(selection: $preferences.trailingStatusSwipeActions)
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
.navigationTitle("Trailing Swipe Actions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.appGroupedListRowBackground()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
struct AppearancePrefsView_Previews : PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
AppearancePrefsView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -111,10 +111,6 @@ class AttachmentView: GIFImageView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateBadges() {
|
|
||||||
createBadgesView(getBadges())
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func gifPlaybackModeChanged() {
|
@objc private func gifPlaybackModeChanged() {
|
||||||
// NSProcessInfoPowerStateDidChange is sometimes fired on a background thread
|
// NSProcessInfoPowerStateDidChange is sometimes fired on a background thread
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
@ -374,8 +370,6 @@ class AttachmentView: GIFImageView {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.badgeContainer?.removeFromSuperview()
|
|
||||||
|
|
||||||
let stack = UIStackView()
|
let stack = UIStackView()
|
||||||
self.badgeContainer = stack
|
self.badgeContainer = stack
|
||||||
stack.axis = .horizontal
|
stack.axis = .horizontal
|
||||||
|
|
|
@ -72,35 +72,21 @@ class AttachmentsContainerView: UIView {
|
||||||
func updateUI(attachments: [Attachment], labelOnly: Bool = false) {
|
func updateUI(attachments: [Attachment], labelOnly: Bool = false) {
|
||||||
let newTokens = attachments.map { AttachmentToken(attachment: $0) }
|
let newTokens = attachments.map { AttachmentToken(attachment: $0) }
|
||||||
|
|
||||||
guard labelOnly != (label != nil) || self.attachmentTokens != newTokens else {
|
guard !labelOnly else {
|
||||||
self.attachments = attachments
|
self.attachments = attachments
|
||||||
self.attachmentTokens = newTokens
|
self.attachmentTokens = newTokens
|
||||||
|
updateLabel(attachments: attachments)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard self.attachmentTokens != newTokens else {
|
||||||
self.isHidden = attachments.isEmpty
|
self.isHidden = attachments.isEmpty
|
||||||
if labelOnly && !attachments.isEmpty {
|
|
||||||
updateLabel(attachments: attachments)
|
|
||||||
} else {
|
|
||||||
label?.removeFromSuperview()
|
|
||||||
label = nil
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.attachments = attachments
|
self.attachments = attachments
|
||||||
self.attachmentTokens = newTokens
|
self.attachmentTokens = newTokens
|
||||||
|
|
||||||
if labelOnly {
|
|
||||||
if !attachments.isEmpty {
|
|
||||||
updateLabel(attachments: attachments)
|
|
||||||
} else {
|
|
||||||
label?.removeFromSuperview()
|
|
||||||
label = nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
label?.removeFromSuperview()
|
|
||||||
label = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
removeAttachmentViews()
|
removeAttachmentViews()
|
||||||
hideButtonView?.isHidden = false
|
hideButtonView?.isHidden = false
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@ class StatusCardView: UIView {
|
||||||
private var statusID: String?
|
private var statusID: String?
|
||||||
private(set) var card: Card?
|
private(set) var card: Card?
|
||||||
|
|
||||||
private static let activeBackgroundColor = UIColor.secondarySystemFill
|
private let activeBackgroundColor = UIColor.secondarySystemFill
|
||||||
private static let inactiveBackgroundColor = UIColor.secondarySystemBackground
|
private let inactiveBackgroundColor = UIColor.secondarySystemBackground
|
||||||
|
|
||||||
private var isGrayscale = false
|
private var isGrayscale = false
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ class StatusCardView: UIView {
|
||||||
hStack.clipsToBounds = true
|
hStack.clipsToBounds = true
|
||||||
hStack.layer.borderWidth = 0.5
|
hStack.layer.borderWidth = 0.5
|
||||||
hStack.layer.cornerCurve = .continuous
|
hStack.layer.cornerCurve = .continuous
|
||||||
hStack.backgroundColor = StatusCardView.inactiveBackgroundColor
|
hStack.backgroundColor = inactiveBackgroundColor
|
||||||
updateBorderColor()
|
updateBorderColor()
|
||||||
|
|
||||||
addSubview(hStack)
|
addSubview(hStack)
|
||||||
|
@ -173,12 +173,8 @@ class StatusCardView: UIView {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUI(card: card, sensitive: status.sensitive)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUI(card: Card, sensitive: Bool) {
|
|
||||||
if let image = card.image {
|
if let image = card.image {
|
||||||
if sensitive {
|
if status.sensitive {
|
||||||
if let blurhash = card.blurhash {
|
if let blurhash = card.blurhash {
|
||||||
imageView.blurImage = false
|
imageView.blurImage = false
|
||||||
imageView.showOnlyBlurHash(blurhash, for: URL(image)!)
|
imageView.showOnlyBlurHash(blurhash, for: URL(image)!)
|
||||||
|
@ -223,7 +219,7 @@ class StatusCardView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
hStack.backgroundColor = StatusCardView.activeBackgroundColor
|
hStack.backgroundColor = activeBackgroundColor
|
||||||
setNeedsDisplay()
|
setNeedsDisplay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +227,7 @@ class StatusCardView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
hStack.backgroundColor = StatusCardView.inactiveBackgroundColor
|
hStack.backgroundColor = inactiveBackgroundColor
|
||||||
setNeedsDisplay()
|
setNeedsDisplay()
|
||||||
|
|
||||||
if let card = card, let delegate = navigationDelegate {
|
if let card = card, let delegate = navigationDelegate {
|
||||||
|
@ -240,7 +236,7 @@ class StatusCardView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
hStack.backgroundColor = StatusCardView.inactiveBackgroundColor
|
hStack.backgroundColor = inactiveBackgroundColor
|
||||||
setNeedsDisplay()
|
setNeedsDisplay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,35 +80,20 @@ class StatusMetaIndicatorsView: UIView {
|
||||||
}
|
}
|
||||||
statusID = status.id
|
statusID = status.id
|
||||||
|
|
||||||
var indicators: Indicator = []
|
var images: [UIImage] = []
|
||||||
|
|
||||||
if allowedIndicators.contains(.reply) && Preferences.shared.showIsStatusReplyIcon && status.inReplyToID != nil {
|
if allowedIndicators.contains(.reply) && Preferences.shared.showIsStatusReplyIcon && status.inReplyToID != nil {
|
||||||
indicators.insert(.reply)
|
images.append(UIImage(systemName: "bubble.left.and.bubble.right")!)
|
||||||
}
|
}
|
||||||
|
|
||||||
if allowedIndicators.contains(.visibility) && Preferences.shared.alwaysShowStatusVisibilityIcon {
|
if allowedIndicators.contains(.visibility) && Preferences.shared.alwaysShowStatusVisibilityIcon {
|
||||||
indicators.insert(.visibility)
|
images.append(UIImage(systemName: status.visibility.unfilledImageName)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
if allowedIndicators.contains(.localOnly) && status.localOnly {
|
if allowedIndicators.contains(.localOnly) && status.localOnly {
|
||||||
indicators.insert(.localOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
setIndicators(indicators, visibility: status.visibility)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used by MockStatusView
|
|
||||||
func setIndicators(_ indicators: Indicator, visibility: Visibility) {
|
|
||||||
var images: [UIImage] = []
|
|
||||||
if indicators.contains(.reply) {
|
|
||||||
images.append(UIImage(systemName: "bubble.left.and.bubble.right")!)
|
|
||||||
}
|
|
||||||
if indicators.contains(.visibility) {
|
|
||||||
images.append(UIImage(systemName: visibility.unfilledImageName)!)
|
|
||||||
}
|
|
||||||
if indicators.contains(.localOnly) {
|
|
||||||
images.append(UIImage(named: "link.broken")!)
|
images.append(UIImage(named: "link.broken")!)
|
||||||
}
|
}
|
||||||
|
|
||||||
let views = images.map {
|
let views = images.map {
|
||||||
let v = UIImageView(image: $0)
|
let v = UIImageView(image: $0)
|
||||||
v.translatesAutoresizingMaskIntoConstraints = false
|
v.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
|
@ -47,4 +47,30 @@ class AttributedStringHelperTests: XCTestCase {
|
||||||
XCTAssertEqual(d, NSAttributedString(string: "abc"))
|
XCTAssertEqual(d, NSAttributedString(string: "abc"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCollapsingWhitespace() {
|
||||||
|
var str = NSAttributedString(string: "test 1\n")
|
||||||
|
XCTAssertEqual(str.collapsingWhitespace(), NSAttributedString(string: "test 1\n"))
|
||||||
|
|
||||||
|
str = NSAttributedString(string: "test 2 \n")
|
||||||
|
XCTAssertEqual(str.collapsingWhitespace(), NSAttributedString(string: "test 2\n"))
|
||||||
|
|
||||||
|
str = NSAttributedString(string: "test 3\n ")
|
||||||
|
XCTAssertEqual(str.collapsingWhitespace(), NSAttributedString(string: "test 3\n"))
|
||||||
|
|
||||||
|
str = NSAttributedString(string: "test 4 \n ")
|
||||||
|
XCTAssertEqual(str.collapsingWhitespace(), NSAttributedString(string: "test 4\n"))
|
||||||
|
|
||||||
|
str = NSAttributedString(string: "test 5 \n blah")
|
||||||
|
XCTAssertEqual(str.collapsingWhitespace(), NSAttributedString(string: "test 5\nblah"))
|
||||||
|
|
||||||
|
str = NSAttributedString(string: "\ntest 6")
|
||||||
|
XCTAssertEqual(str.collapsingWhitespace(), NSAttributedString(string: "\ntest 6"))
|
||||||
|
|
||||||
|
str = NSAttributedString(string: " \ntest 7")
|
||||||
|
XCTAssertEqual(str.collapsingWhitespace(), NSAttributedString(string: "\ntest 7"))
|
||||||
|
|
||||||
|
str = NSAttributedString(string: " \n test 8")
|
||||||
|
XCTAssertEqual(str.collapsingWhitespace(), NSAttributedString(string: "\ntest 8"))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue