Reorganize appearance prefs, add mock status preview
This commit is contained in:
parent
39251b9aa2
commit
c7a56a9f61
|
@ -25,6 +25,17 @@ 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,6 +26,38 @@ 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,7 +11,6 @@
|
||||||
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 */; };
|
||||||
|
@ -291,6 +290,8 @@
|
||||||
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 */; };
|
||||||
|
@ -435,7 +436,6 @@
|
||||||
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,6 +714,8 @@
|
||||||
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>"; };
|
||||||
|
@ -1170,7 +1172,6 @@
|
||||||
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 */,
|
||||||
|
@ -1181,6 +1182,7 @@
|
||||||
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 */,
|
||||||
|
@ -1485,6 +1487,15 @@
|
||||||
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 = (
|
||||||
|
@ -2102,6 +2113,7 @@
|
||||||
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 */,
|
||||||
|
@ -2290,7 +2302,6 @@
|
||||||
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 */,
|
||||||
|
@ -2359,6 +2370,7 @@
|
||||||
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,7 +104,9 @@ struct AboutView: View {
|
||||||
|
|
||||||
private var appIcon: some View {
|
private var appIcon: some View {
|
||||||
VStack {
|
VStack {
|
||||||
AppIconView()
|
Image("AboutIcon")
|
||||||
|
.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)
|
||||||
|
|
||||||
|
@ -125,20 +127,6 @@ 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
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
//
|
||||||
|
// 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
|
|
@ -0,0 +1,270 @@
|
||||||
|
//
|
||||||
|
// 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)
|
||||||
|
}
|
|
@ -1,158 +0,0 @@
|
||||||
// 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,6 +111,10 @@ 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 {
|
||||||
|
@ -370,6 +374,8 @@ 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,21 +72,35 @@ 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 else {
|
guard labelOnly != (label != nil) || self.attachmentTokens != newTokens 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 let activeBackgroundColor = UIColor.secondarySystemFill
|
private static let activeBackgroundColor = UIColor.secondarySystemFill
|
||||||
private let inactiveBackgroundColor = UIColor.secondarySystemBackground
|
private static 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 = inactiveBackgroundColor
|
hStack.backgroundColor = StatusCardView.inactiveBackgroundColor
|
||||||
updateBorderColor()
|
updateBorderColor()
|
||||||
|
|
||||||
addSubview(hStack)
|
addSubview(hStack)
|
||||||
|
@ -173,8 +173,12 @@ 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 status.sensitive {
|
if 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)!)
|
||||||
|
@ -219,7 +223,7 @@ class StatusCardView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
hStack.backgroundColor = activeBackgroundColor
|
hStack.backgroundColor = StatusCardView.activeBackgroundColor
|
||||||
setNeedsDisplay()
|
setNeedsDisplay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +231,7 @@ class StatusCardView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
hStack.backgroundColor = inactiveBackgroundColor
|
hStack.backgroundColor = StatusCardView.inactiveBackgroundColor
|
||||||
setNeedsDisplay()
|
setNeedsDisplay()
|
||||||
|
|
||||||
if let card = card, let delegate = navigationDelegate {
|
if let card = card, let delegate = navigationDelegate {
|
||||||
|
@ -236,7 +240,7 @@ class StatusCardView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
hStack.backgroundColor = inactiveBackgroundColor
|
hStack.backgroundColor = StatusCardView.inactiveBackgroundColor
|
||||||
setNeedsDisplay()
|
setNeedsDisplay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,20 +80,35 @@ class StatusMetaIndicatorsView: UIView {
|
||||||
}
|
}
|
||||||
statusID = status.id
|
statusID = status.id
|
||||||
|
|
||||||
var images: [UIImage] = []
|
var indicators: Indicator = []
|
||||||
|
|
||||||
if allowedIndicators.contains(.reply) && Preferences.shared.showIsStatusReplyIcon && status.inReplyToID != nil {
|
if allowedIndicators.contains(.reply) && Preferences.shared.showIsStatusReplyIcon && status.inReplyToID != nil {
|
||||||
images.append(UIImage(systemName: "bubble.left.and.bubble.right")!)
|
indicators.insert(.reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
if allowedIndicators.contains(.visibility) && Preferences.shared.alwaysShowStatusVisibilityIcon {
|
if allowedIndicators.contains(.visibility) && Preferences.shared.alwaysShowStatusVisibilityIcon {
|
||||||
images.append(UIImage(systemName: status.visibility.unfilledImageName)!)
|
indicators.insert(.visibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
if allowedIndicators.contains(.localOnly) && status.localOnly {
|
if allowedIndicators.contains(.localOnly) && status.localOnly {
|
||||||
images.append(UIImage(named: "link.broken")!)
|
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")!)
|
||||||
|
}
|
||||||
let views = images.map {
|
let views = images.map {
|
||||||
let v = UIImageView(image: $0)
|
let v = UIImageView(image: $0)
|
||||||
v.translatesAutoresizingMaskIntoConstraints = false
|
v.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
Loading…
Reference in New Issue