Convert Theme to Observable

This commit is contained in:
Thomas Ricouard 2023-09-18 21:03:52 +02:00
parent 625b7f8137
commit f9c0355f1d
78 changed files with 356 additions and 148 deletions

View file

@ -115,7 +115,7 @@ extension View {
environment(CurrentAccount.shared)
.environmentObject(UserPreferences.shared)
.environment(CurrentInstance.shared)
.environmentObject(Theme.shared)
.environment(Theme.shared)
.environment(AppAccountsManager.shared)
.environment(PushNotificationsService.shared)
.environment(AppAccountsManager.shared.currentClient)

View file

@ -22,7 +22,7 @@ struct IceCubesApp: App {
@State private var pushNotificationsService = PushNotificationsService.shared
@State private var watcher = StreamWatcher()
@State private var quickLook = QuickLook()
@StateObject private var theme = Theme.shared
@State private var theme = Theme.shared
@State private var sidebarRouterPath = RouterPath()
@State private var selectedTab: Tab = .timeline
@ -49,7 +49,7 @@ struct IceCubesApp: App {
.environment(currentAccount)
.environment(currentInstance)
.environmentObject(userPreferences)
.environmentObject(theme)
.environment(theme)
.environment(watcher)
.environment(pushNotificationsService)
.environment(\.isSupporter, isSupporter)

View file

@ -8,7 +8,7 @@ import SwiftUI
public struct ReportView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(Client.self) private var client
let status: Status

View file

@ -12,7 +12,7 @@ extension View {
@MainActor
private struct SafariRouter: ViewModifier {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@EnvironmentObject private var preferences: UserPreferences
@Environment(RouterPath.self) private var routerPath

View file

@ -8,7 +8,7 @@ import SwiftUI
struct SideBarView<Content: View>: View {
@Environment(AppAccountsManager.self) private var appAccounts
@Environment(CurrentAccount.self) private var currentAccount
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(StreamWatcher.self) private var watcher
@EnvironmentObject private var userPreferences: UserPreferences
@Environment(RouterPath.self) private var routerPath
@ -155,7 +155,7 @@ struct SideBarView<Content: View>: View {
}
private struct SideBarIcon: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
let systemIconName: String
let isSelected: Bool

View file

@ -8,7 +8,7 @@ import Shimmer
import SwiftUI
struct ExploreTab: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@EnvironmentObject private var preferences: UserPreferences
@Environment(CurrentAccount.self) private var currentAccount
@Environment(Client.self) private var client

View file

@ -8,8 +8,9 @@ import Network
import Shimmer
import SwiftUI
@MainActor
struct MessagesTab: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(StreamWatcher.self) private var watcher
@Environment(Client.self) private var client
@Environment(CurrentAccount.self) private var currentAccount

View file

@ -11,7 +11,7 @@ struct NotificationsTab: View {
@Environment(\.isSecondaryColumn) private var isSecondaryColumn: Bool
@Environment(\.scenePhase) private var scenePhase
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(Client.self) private var client
@Environment(StreamWatcher.self) private var watcher
@Environment(AppAccountsManager.self) private var appAccount

View file

@ -8,9 +8,10 @@ import Network
import Shimmer
import SwiftUI
@MainActor
struct ProfileTab: View {
@Environment(AppAccountsManager.self) private var appAccount
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(Client.self) private var client
@Environment(CurrentAccount.self) private var currentAccount
@State private var routerPath = RouterPath()

View file

@ -2,9 +2,10 @@ import DesignSystem
import Env
import SwiftUI
@MainActor
struct AboutView: View {
@Environment(RouterPath.self) private var routerPath
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
let versionNumber: String
@ -101,6 +102,6 @@ struct AboutView: View {
struct AboutView_Previews: PreviewProvider {
static var previews: some View {
AboutView()
.environmentObject(Theme.shared)
.environment(Theme.shared)
}
}

View file

@ -14,7 +14,7 @@ struct AccountSettingsView: View {
@Environment(PushNotificationsService.self) private var pushNotifications
@Environment(CurrentAccount.self) private var currentAccount
@Environment(CurrentInstance.self) private var currentInstance
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(AppAccountsManager.self) private var appAccountsManager
@Environment(Client.self) private var client

View file

@ -9,6 +9,7 @@ import SafariServices
import Shimmer
import SwiftUI
@MainActor
struct AddAccountView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.scenePhase) private var scenePhase
@ -17,7 +18,7 @@ struct AddAccountView: View {
@Environment(CurrentAccount.self) private var currentAccount
@Environment(CurrentInstance.self) private var currentInstance
@Environment(PushNotificationsService.self) private var pushNotifications
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@State private var instanceName: String = ""
@State private var instance: Instance?

View file

@ -9,7 +9,7 @@ import UserNotifications
struct ContentSettingsView: View {
@EnvironmentObject private var userPreferences: UserPreferences
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
var body: some View {
Form {

View file

@ -15,15 +15,14 @@ import SwiftUI
var lineSpacing = Theme.shared.lineSpacing
var fontSizeScale = Theme.shared.fontSizeScale
init() { }
init() {}
}
struct DisplaySettingsView: View {
typealias FontState = Theme.FontState
@Environment(\.colorScheme) private var colorScheme
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@EnvironmentObject private var userPreferences: UserPreferences
@State private var localValues = DisplaySettingsLocalValues()
@ -51,27 +50,27 @@ struct DisplaySettingsView: View {
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.task(id: localValues.tintColor) {
do { try await Task.sleep(for: .microseconds(500)) } catch { }
do { try await Task.sleep(for: .microseconds(500)) } catch {}
Theme.shared.tintColor = localValues.tintColor
}
.task(id: localValues.primaryBackgroundColor) {
do { try await Task.sleep(for: .microseconds(500)) } catch { }
do { try await Task.sleep(for: .microseconds(500)) } catch {}
Theme.shared.primaryBackgroundColor = localValues.primaryBackgroundColor
}
.task(id: localValues.secondaryBackgroundColor) {
do { try await Task.sleep(for: .microseconds(500)) } catch { }
do { try await Task.sleep(for: .microseconds(500)) } catch {}
Theme.shared.secondaryBackgroundColor = localValues.secondaryBackgroundColor
}
.task(id: localValues.labelColor) {
do { try await Task.sleep(for: .microseconds(500)) } catch { }
do { try await Task.sleep(for: .microseconds(500)) } catch {}
Theme.shared.labelColor = localValues.labelColor
}
.task(id: localValues.lineSpacing) {
do { try await Task.sleep(for: .microseconds(500)) } catch { }
do { try await Task.sleep(for: .microseconds(500)) } catch {}
Theme.shared.lineSpacing = localValues.lineSpacing
}
.task(id: localValues.fontSizeScale) {
do { try await Task.sleep(for: .microseconds(500)) } catch { }
do { try await Task.sleep(for: .microseconds(500)) } catch {}
Theme.shared.fontSizeScale = localValues.fontSizeScale
}
examplePost
@ -96,7 +95,9 @@ struct DisplaySettingsView: View {
}
}
@ViewBuilder
private var themeSection: some View {
@Bindable var theme = theme
Section {
Toggle("settings.display.theme.systemColor", isOn: $theme.followSystemColorScheme)
themeSelectorButton
@ -176,7 +177,9 @@ struct DisplaySettingsView: View {
.listRowBackground(theme.primaryBackgroundColor)
}
@ViewBuilder
private var layoutSection: some View {
@Bindable var theme = theme
Section("settings.display.section.display") {
Picker("settings.display.avatar.position", selection: $theme.avatarPosition) {
ForEach(Theme.AvatarPosition.allCases, id: \.rawValue) { position in

View file

@ -5,7 +5,7 @@ import Status
import SwiftUI
struct HapticSettingsView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@EnvironmentObject private var userPreferences: UserPreferences
var body: some View {

View file

@ -1,6 +1,7 @@
import DesignSystem
import SwiftUI
@MainActor
struct IconSelectorView: View {
enum Icon: Int, CaseIterable, Identifiable {
var id: String {
@ -62,7 +63,7 @@ struct IconSelectorView: View {
]
}
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@State private var currentIcon = UIApplication.shared.alternateIconName ?? Icon.primary.appIconName
private let columns = [GridItem(.adaptive(minimum: 125, maximum: 1024))]

View file

@ -4,7 +4,7 @@ import NukeUI
import SwiftUI
struct InstanceInfoView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
let instance: Instance
@ -19,7 +19,7 @@ struct InstanceInfoView: View {
}
public struct InstanceInfoSection: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
let instance: Instance

View file

@ -7,8 +7,9 @@ import NukeUI
import SwiftUI
import UserNotifications
@MainActor
struct PushNotificationsView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(AppAccountsManager.self) private var appAccountsManager
@Environment(PushNotificationsService.self) private var pushNotifications

View file

@ -17,7 +17,7 @@ struct SettingsTabs: View {
@Environment(Client.self) private var client
@Environment(CurrentInstance.self) private var currentInstance
@Environment(AppAccountsManager.self) private var appAccountsManager
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@State private var routerPath = RouterPath()
@State private var addAccountSheetPresented = false

View file

@ -4,6 +4,7 @@ import RevenueCat
import Shimmer
import SwiftUI
@MainActor
struct SupportAppView: View {
enum Tip: String, CaseIterable {
case one, two, three, four, supporter
@ -47,7 +48,7 @@ struct SupportAppView: View {
}
}
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(\.openURL) private var openURL

View file

@ -3,7 +3,7 @@ import Env
import SwiftUI
struct SwipeActionsSettingsView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@EnvironmentObject private var userPreferences: UserPreferences
var body: some View {

View file

@ -4,7 +4,7 @@ import SwiftUI
struct TranslationSettingsView: View {
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@State private var apiKey: String = ""

View file

@ -11,7 +11,7 @@ struct AddRemoteTimelineView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@State private var instanceName: String = ""
@State private var instance: Instance?

View file

@ -11,7 +11,7 @@ struct EditTagGroupView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@State private var title: String = ""
@State private var sfSymbolName: String = ""

View file

@ -9,7 +9,7 @@ import Timeline
struct TimelineTab: View {
@Environment(AppAccountsManager.self) private var appAccount
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentAccount.self) private var currentAccount
@EnvironmentObject private var preferences: UserPreferences
@Environment(Client.self) private var client

View file

@ -31,7 +31,7 @@ class ShareViewController: UIViewController {
.environment(appAccountsManager)
.environment(client)
.environment(account)
.environmentObject(theme)
.environment(theme)
.environment(instance)
.tint(theme.tintColor)
.preferredColorScheme(colorScheme == .light ? .light : .dark)

View file

@ -6,12 +6,13 @@ import NukeUI
import Shimmer
import SwiftUI
@MainActor
struct AccountDetailHeaderView: View {
enum Constants {
static let headerHeight: CGFloat = 200
}
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(QuickLook.self) private var quickLook
@Environment(RouterPath.self) private var routerPath
@Environment(CurrentAccount.self) private var currentAccount

View file

@ -15,7 +15,7 @@ public struct AccountDetailView: View {
@Environment(CurrentAccount.self) private var currentAccount
@Environment(CurrentInstance.self) private var currentInstance
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(Client.self) private var client
@Environment(RouterPath.self) private var routerPath

View file

@ -20,8 +20,9 @@ import SwiftUI
}
}
@MainActor
public struct AccountsListRow: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentAccount.self) private var currentAccount
@Environment(RouterPath.self) private var routerPath
@Environment(Client.self) private var client
@ -117,7 +118,7 @@ public struct AccountsListRow: View {
.listStyle(.plain)
.scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor)
.environmentObject(theme)
.environment(theme)
.environment(currentAccount)
.environment(client)
}

View file

@ -5,8 +5,9 @@ import Network
import Shimmer
import SwiftUI
@MainActor
public struct AccountsListView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(Client.self) private var client
@Environment(CurrentAccount.self) private var currentAccount
@State private var viewModel: AccountsListViewModel

View file

@ -3,10 +3,11 @@ import Models
import Network
import SwiftUI
@MainActor
public struct EditAccountView: View {
@Environment(\.dismiss) private var dismiss
@Environment(Client.self) private var client
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@State private var viewModel = EditAccountViewModel()

View file

@ -2,9 +2,10 @@ import DesignSystem
import Network
import SwiftUI
@MainActor
public struct EditRelationshipNoteView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(Client.self) private var client
@State var accountDetailViewModel: AccountDetailViewModel

View file

@ -4,10 +4,11 @@ import Models
import Network
import SwiftUI
@MainActor
struct EditFilterView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentAccount.self) private var account
@Environment(Client.self) private var client

View file

@ -7,7 +7,7 @@ import SwiftUI
public struct FiltersListView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentAccount.self) private var account
@Environment(Client.self) private var client

View file

@ -4,7 +4,7 @@ import Env
import SwiftUI
public struct AppAccountView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(RouterPath.self) private var routerPath
@Environment(AppAccountsManager.self) private var appAccounts
@EnvironmentObject private var preferences: UserPreferences

View file

@ -6,7 +6,7 @@ public struct AppAccountsSelectorView: View {
@EnvironmentObject private var preferences: UserPreferences
@Environment(CurrentAccount.self) private var currentAccount
@Environment(AppAccountsManager.self) private var appAccounts
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
var routerPath: RouterPath

View file

@ -5,6 +5,7 @@ import Network
import NukeUI
import SwiftUI
@MainActor
public struct ConversationDetailView: View {
private enum Constants {
static let bottomAnchor = "bottom"
@ -14,7 +15,7 @@ public struct ConversationDetailView: View {
@Environment(RouterPath.self) private var routerPath
@Environment(CurrentAccount.self) private var currentAccount
@Environment(Client.self) private var client
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(StreamWatcher.self) private var watcher
@State private var viewModel: ConversationDetailViewModel

View file

@ -5,12 +5,13 @@ import Network
import NukeUI
import SwiftUI
@MainActor
struct ConversationMessageView: View {
@Environment(QuickLook.self) private var quickLook
@Environment(RouterPath.self) private var routerPath
@Environment(CurrentAccount.self) private var currentAccount
@Environment(Client.self) private var client
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
let message: Status
let conversation: Conversation

View file

@ -5,10 +5,11 @@ import Models
import Network
import SwiftUI
@MainActor
struct ConversationsListRow: View {
@Environment(Client.self) private var client
@Environment(RouterPath.self) private var routerPath
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentAccount.self) private var currentAccount
@Binding var conversation: Conversation

View file

@ -10,7 +10,7 @@ public struct ConversationsListView: View {
@Environment(RouterPath.self) private var routerPath
@Environment(StreamWatcher.self) private var watcher
@Environment(Client.self) private var client
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@State private var viewModel = ConversationsListViewModel()

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DesignSystem"
BuildableName = "DesignSystem"
BlueprintName = "DesignSystem"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DesignSystem"
BuildableName = "DesignSystem"
BlueprintName = "DesignSystem"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -1,14 +1,35 @@
import Combine
import SwiftUI
public class Theme: ObservableObject {
enum ThemeKey: String {
case colorScheme, tint, label, primaryBackground, secondaryBackground
case avatarPosition, avatarShape, statusActionsDisplay, statusDisplayStyle
case selectedSet, selectedScheme
case followSystemColorSchme
case displayFullUsernameTimeline
case lineSpacing
@Observable public class Theme {
class ThemeStorage {
enum ThemeKey: String {
case colorScheme, tint, label, primaryBackground, secondaryBackground
case avatarPosition, avatarShape, statusActionsDisplay, statusDisplayStyle
case selectedSet, selectedScheme
case followSystemColorSchme
case displayFullUsernameTimeline
case lineSpacing
}
@AppStorage("is_previously_set") public var isThemePreviouslySet: Bool = false
@AppStorage(ThemeKey.selectedScheme.rawValue) public var selectedScheme: ColorScheme = .dark
@AppStorage(ThemeKey.tint.rawValue) public var tintColor: Color = .black
@AppStorage(ThemeKey.primaryBackground.rawValue) public var primaryBackgroundColor: Color = .white
@AppStorage(ThemeKey.secondaryBackground.rawValue) public var secondaryBackgroundColor: Color = .gray
@AppStorage(ThemeKey.label.rawValue) public var labelColor: Color = .black
@AppStorage(ThemeKey.avatarPosition.rawValue) var rawAvatarPosition: String = AvatarPosition.top.rawValue
@AppStorage(ThemeKey.avatarShape.rawValue) var rawAvatarShape: String = AvatarShape.rounded.rawValue
@AppStorage(ThemeKey.selectedSet.rawValue) var storedSet: ColorSetName = .iceCubeDark
@AppStorage(ThemeKey.statusActionsDisplay.rawValue) public var statusActionsDisplay: StatusActionsDisplay = .full
@AppStorage(ThemeKey.statusDisplayStyle.rawValue) public var statusDisplayStyle: StatusDisplayStyle = .large
@AppStorage(ThemeKey.followSystemColorSchme.rawValue) public var followSystemColorScheme: Bool = true
@AppStorage(ThemeKey.displayFullUsernameTimeline.rawValue) public var displayFullUsername: Bool = true
@AppStorage(ThemeKey.lineSpacing.rawValue) public var lineSpacing: Double = 0.8
@AppStorage("font_size_scale") public var fontSizeScale: Double = 1
@AppStorage("chosen_font") public var chosenFontData: Data?
init() {}
}
public enum FontState: Int, CaseIterable {
@ -18,7 +39,6 @@ public class Theme: ObservableObject {
case SFRounded
case custom
@MainActor
public var title: LocalizedStringKey {
switch self {
case .system:
@ -115,60 +135,144 @@ public class Theme: ObservableObject {
}
}
@AppStorage("is_previously_set") public var isThemePreviouslySet: Bool = false
@AppStorage(ThemeKey.selectedScheme.rawValue) public var selectedScheme: ColorScheme = .dark
@AppStorage(ThemeKey.tint.rawValue) public var tintColor: Color = .black
@AppStorage(ThemeKey.primaryBackground.rawValue) public var primaryBackgroundColor: Color = .white
@AppStorage(ThemeKey.secondaryBackground.rawValue) public var secondaryBackgroundColor: Color = .gray
@AppStorage(ThemeKey.label.rawValue) public var labelColor: Color = .black
@AppStorage(ThemeKey.avatarPosition.rawValue) var rawAvatarPosition: String = AvatarPosition.top.rawValue
@AppStorage(ThemeKey.avatarShape.rawValue) var rawAvatarShape: String = AvatarShape.rounded.rawValue
@AppStorage(ThemeKey.selectedSet.rawValue) var storedSet: ColorSetName = .iceCubeDark
@AppStorage(ThemeKey.statusActionsDisplay.rawValue) public var statusActionsDisplay: StatusActionsDisplay = .full
@AppStorage(ThemeKey.statusDisplayStyle.rawValue) public var statusDisplayStyle: StatusDisplayStyle = .large
@AppStorage(ThemeKey.followSystemColorSchme.rawValue) public var followSystemColorScheme: Bool = true
@AppStorage(ThemeKey.displayFullUsernameTimeline.rawValue) public var displayFullUsername: Bool = true
@AppStorage(ThemeKey.lineSpacing.rawValue) public var lineSpacing: Double = 0.8
@AppStorage("font_size_scale") public var fontSizeScale: Double = 1
@AppStorage("chosen_font") public private(set) var chosenFontData: Data?
let themeStorage = ThemeStorage()
@Published public var avatarPosition: AvatarPosition = .top
@Published public var avatarShape: AvatarShape = .rounded
@Published public var selectedSet: ColorSetName = .iceCubeDark
public var isThemePreviouslySet: Bool {
didSet {
themeStorage.isThemePreviouslySet = isThemePreviouslySet
}
}
private var cancellables = Set<AnyCancellable>()
public var selectedScheme: ColorScheme {
didSet {
themeStorage.selectedScheme = selectedScheme
}
}
public var tintColor: Color {
didSet {
themeStorage.tintColor = tintColor
}
}
public var primaryBackgroundColor: Color {
didSet {
themeStorage.primaryBackgroundColor = primaryBackgroundColor
}
}
public var secondaryBackgroundColor: Color {
didSet {
themeStorage.secondaryBackgroundColor = secondaryBackgroundColor
}
}
public var labelColor: Color {
didSet {
themeStorage.labelColor = labelColor
}
}
private var rawAvatarPosition: String {
didSet {
themeStorage.rawAvatarPosition = rawAvatarPosition
}
}
private var rawAvatarShape: String {
didSet {
themeStorage.rawAvatarShape = rawAvatarShape
}
}
private var storedSet: ColorSetName {
didSet {
themeStorage.storedSet = storedSet
}
}
public var statusActionsDisplay: StatusActionsDisplay {
didSet {
themeStorage.statusActionsDisplay = statusActionsDisplay
}
}
public var statusDisplayStyle: StatusDisplayStyle {
didSet {
themeStorage.statusDisplayStyle = statusDisplayStyle
}
}
public var followSystemColorScheme: Bool {
didSet {
themeStorage.followSystemColorScheme = followSystemColorScheme
}
}
public var displayFullUsername: Bool {
didSet {
themeStorage.displayFullUsername = displayFullUsername
}
}
public var lineSpacing: Double {
didSet {
themeStorage.lineSpacing = lineSpacing
}
}
public var fontSizeScale: Double {
didSet {
themeStorage.fontSizeScale = fontSizeScale
}
}
public private(set) var chosenFontData: Data? {
didSet {
themeStorage.chosenFontData = chosenFontData
}
}
public var avatarPosition: AvatarPosition = .top {
didSet {
rawAvatarPosition = avatarShape.rawValue
}
}
public var avatarShape: AvatarShape = .rounded {
didSet {
rawAvatarShape = avatarShape.rawValue
}
}
public var selectedSet: ColorSetName = .iceCubeDark {
didSet {
setColor(withName: selectedSet)
}
}
public static let shared = Theme()
private init() {
isThemePreviouslySet = themeStorage.isThemePreviouslySet
selectedScheme = themeStorage.selectedScheme
tintColor = themeStorage.tintColor
primaryBackgroundColor = themeStorage.primaryBackgroundColor
secondaryBackgroundColor = themeStorage.secondaryBackgroundColor
labelColor = themeStorage.labelColor
rawAvatarPosition = themeStorage.rawAvatarPosition
rawAvatarShape = themeStorage.rawAvatarShape
storedSet = themeStorage.storedSet
statusActionsDisplay = themeStorage.statusActionsDisplay
statusDisplayStyle = themeStorage.statusDisplayStyle
followSystemColorScheme = themeStorage.followSystemColorScheme
displayFullUsername = themeStorage.displayFullUsername
lineSpacing = themeStorage.lineSpacing
fontSizeScale = themeStorage.fontSizeScale
chosenFontData = themeStorage.chosenFontData
selectedSet = storedSet
avatarPosition = AvatarPosition(rawValue: rawAvatarPosition) ?? .top
avatarShape = AvatarShape(rawValue: rawAvatarShape) ?? .rounded
$avatarPosition
.dropFirst()
.map(\.rawValue)
.sink { [weak self] position in
self?.rawAvatarPosition = position
}
.store(in: &cancellables)
$avatarShape
.dropFirst()
.map(\.rawValue)
.sink { [weak self] shape in
self?.rawAvatarShape = shape
}
.store(in: &cancellables)
// Workaround, since @AppStorage can't be directly observed
$selectedSet
.dropFirst()
.sink { [weak self] colorSetName in
self?.setColor(withName: colorSetName)
}
.store(in: &cancellables)
}
public static var allColorSet: [ColorSet] {

View file

@ -9,10 +9,11 @@ public extension View {
}
}
@MainActor
struct ThemeApplier: ViewModifier {
@Environment(\EnvironmentValues.colorScheme) var colorScheme
@ObservedObject var theme: Theme
var theme: Theme
var actualColorScheme: SwiftUI.ColorScheme? {
if theme.followSystemColorScheme {

View file

@ -0,0 +1 @@
import SwiftUI

View file

@ -6,7 +6,7 @@ import SwiftUI
@MainActor
public struct AvatarView: View {
@Environment(\.redactionReasons) private var reasons
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
public enum Size {
case account, status, embed, badge, list, boost

View file

@ -3,7 +3,7 @@ import SwiftUI
public struct ThemePreviewView: View {
private let gutterSpace: Double = 8
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(\.dismiss) var dismiss
public init() {}
@ -25,7 +25,7 @@ public struct ThemePreviewView: View {
}
struct ThemeBoxView: View {
@EnvironmentObject var theme: Theme
@Environment(Theme.self) private var theme
private let gutterSpace = 8.0
@State private var isSelected = false

View file

@ -59,7 +59,7 @@ public extension EnvironmentValues {
get { self[IsStatusFocused.self] }
set { self[IsStatusFocused.self] = newValue }
}
var isStatusReplyToPrevious: Bool {
get { self[IsStatusReplyToPrevious.self] }
set { self[IsStatusReplyToPrevious.self] = newValue }

View file

@ -7,8 +7,9 @@ import Shimmer
import Status
import SwiftUI
@MainActor
public struct ExploreView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(Client.self) private var client
@Environment(RouterPath.self) private var routerPath

View file

@ -3,7 +3,7 @@ import Models
import SwiftUI
public struct TagsListView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
let tags: [Tag]

View file

@ -4,10 +4,11 @@ import Models
import Network
import SwiftUI
@MainActor
public struct ListAddAccountView: View {
@Environment(\.dismiss) private var dismiss
@Environment(Client.self) private var client
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentAccount.self) private var currentAccount
@State private var viewModel: ListAddAccountViewModel

View file

@ -4,9 +4,10 @@ import Models
import Network
import SwiftUI
@MainActor
public struct ListEditView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(Client.self) private var client
@State private var viewModel: ListEditViewModel

View file

@ -6,8 +6,9 @@ import Network
import Status
import SwiftUI
@MainActor
struct NotificationRowView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(\.redactionReasons) private var reasons
let notification: ConsolidatedNotification

View file

@ -5,9 +5,10 @@ import Network
import Shimmer
import SwiftUI
@MainActor
public struct NotificationsListView: View {
@Environment(\.scenePhase) private var scenePhase
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(StreamWatcher.self) private var watcher
@Environment(Client.self) private var client
@Environment(RouterPath.self) private var routerPath

View file

@ -5,8 +5,9 @@ import Network
import Shimmer
import SwiftUI
@MainActor
public struct StatusDetailView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentAccount.self) private var currentAccount
@Environment(StreamWatcher.self) private var watcher
@Environment(Client.self) private var client

View file

@ -19,7 +19,7 @@ import SwiftUI
var state: State = .loading
var title: LocalizedStringKey = ""
var scrollToId: String?
@ObservationIgnored
var isReplyToPreviousCache: [String: Bool] = [:]
@ -127,7 +127,7 @@ import SwiftUI
isReplyToPreviousCache[status.id] = isReplyToPrevious
}
}
func handleEvent(event: any StreamEvent, currentAccount: Account?) {
Task {
if let event = event as? StreamEventUpdate,

View file

@ -7,7 +7,7 @@ import SwiftUI
struct StatusEditorAccessoryView: View {
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentInstance.self) private var currentInstance
@Environment(\.colorScheme) private var colorScheme

View file

@ -3,8 +3,9 @@ import EmojiText
import Foundation
import SwiftUI
@MainActor
struct StatusEditorAutoCompleteView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
var viewModel: StatusEditorViewModel
var body: some View {

View file

@ -6,7 +6,7 @@ import SwiftUI
struct StatusEditorMediaEditView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentInstance.self) private var currentInstance
var viewModel: StatusEditorViewModel
let container: StatusEditorMediaContainer

View file

@ -5,8 +5,9 @@ import Models
import NukeUI
import SwiftUI
@MainActor
struct StatusEditorMediaView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentInstance.self) private var currentInstance
var viewModel: StatusEditorViewModel
@State private var editingContainer: StatusEditorMediaContainer?

View file

@ -2,6 +2,7 @@ import DesignSystem
import Env
import SwiftUI
@MainActor
struct StatusEditorPollView: View {
enum FocusField: Hashable {
case option(Int)
@ -11,7 +12,7 @@ struct StatusEditorPollView: View {
@State private var currentFocusIndex: Int = 0
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentInstance.self) private var currentInstance
var viewModel: StatusEditorViewModel

View file

@ -11,10 +11,11 @@ import StoreKit
import SwiftUI
import UIKit
@MainActor
public struct StatusEditorView: View {
@Environment(AppAccountsManager.self) private var appAccounts
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(Client.self) private var client
@Environment(CurrentAccount.self) private var currentAccount
@Environment(RouterPath.self) private var routerPath
@ -238,7 +239,7 @@ public struct StatusEditorView: View {
avatarSize: .status)
} else {
AvatarView(url: account.avatar, size: .status)
.environmentObject(theme)
.environment(theme)
.accessibilityHidden(true)
}
VStack(alignment: .leading, spacing: 4) {

View file

@ -7,7 +7,7 @@ import SwiftUI
@MainActor
public struct StatusEmbeddedView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
public let status: Status
public let client: Client

View file

@ -7,7 +7,7 @@ public struct StatusEditHistoryView: View {
@Environment(\.dismiss) private var dismiss
@Environment(Client.self) private var client
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
private let statusId: String

View file

@ -5,8 +5,9 @@ import Network
import Shimmer
import SwiftUI
@MainActor
public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@State private var fetcher: Fetcher
// Whether this status is on a remote local timeline (many actions are unavailable if so)

View file

@ -52,7 +52,7 @@ struct VideoPlayerView: View {
@Environment(\.scenePhase) private var scenePhase
@Environment(\.isCompact) private var isCompact
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@State var viewModel: VideoPlayerViewModel

View file

@ -4,8 +4,9 @@ import Models
import Network
import SwiftUI
@MainActor
public struct StatusPollView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(Client.self) private var client
@Environment(CurrentInstance.self) private var currentInstance
@Environment(CurrentAccount.self) private var currentAccount

View file

@ -7,6 +7,7 @@ import Network
import Shimmer
import SwiftUI
@MainActor
public struct StatusRowView: View {
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
@Environment(\.redactionReasons) private var reasons
@ -16,7 +17,7 @@ public struct StatusRowView: View {
@Environment(\.isStatusReplyToPrevious) private var isStatusReplyToPrevious
@Environment(QuickLook.self) private var quickLook
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@State private var viewModel: StatusRowViewModel
@ -95,7 +96,7 @@ public struct StatusRowView: View {
viewModel.navigateToDetail()
}
}
if isFocused {
StatusRowDetailView(viewModel: viewModel)
}

View file

@ -15,10 +15,10 @@ import SwiftUI
let showActions: Bool
let textDisabled: Bool
let finalStatus: AnyStatus
let client: Client
let routerPath: RouterPath
private let theme = Theme.shared
private let userMentionned: Bool

View file

@ -5,7 +5,7 @@ import Network
import SwiftUI
struct StatusRowActionsView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentAccount.self) private var currentAccount
@Environment(StatusDataController.self) private var statusDataController
@EnvironmentObject private var userPreferences: UserPreferences
@ -13,7 +13,7 @@ struct StatusRowActionsView: View {
@Environment(\.isStatusFocused) private var isFocused
var viewModel: StatusRowViewModel
func privateBoost() -> Bool {
viewModel.status.visibility == .priv && viewModel.status.account.id == currentAccount.account?.id
}

View file

@ -9,7 +9,7 @@ public struct StatusRowCardView: View {
@Environment(\.openURL) private var openURL
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
let card: Card

View file

@ -8,7 +8,7 @@ struct StatusRowContentView: View {
@Environment(\.isCompact) private var isCompact
@Environment(\.isStatusFocused) private var isFocused
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
var viewModel: StatusRowViewModel

View file

@ -4,6 +4,7 @@ import Foundation
import Network
import SwiftUI
@MainActor
struct StatusRowContextMenu: View {
@Environment(\.displayScale) var displayScale
@ -85,7 +86,7 @@ struct StatusRowContextMenu: View {
.padding(16)
}
.environment(\.isInCaptureMode, true)
.environmentObject(Theme.shared)
.environment(Theme.shared)
.environmentObject(preferences)
.environment(account)
.environment(currentInstance)

View file

@ -75,7 +75,7 @@ struct StatusRowDetailView: View {
.buttonStyle(.borderless)
.transition(.move(edge: .leading))
}
if viewModel.actionsAccountsFetched, statusDataController.reblogsCount > 0 {
Divider()
Button {

View file

@ -3,11 +3,12 @@ import Env
import Models
import SwiftUI
@MainActor
struct StatusRowHeaderView: View {
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
@Environment(\.isStatusFocused) private var isFocused
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
let viewModel: StatusRowViewModel

View file

@ -14,7 +14,7 @@ public struct StatusRowMediaPreviewView: View {
@Environment(SceneDelegate.self) private var sceneDelegate
@EnvironmentObject private var preferences: UserPreferences
@Environment(QuickLook.self) private var quickLook
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
public let attachments: [MediaAttachment]
public let sensitive: Bool

View file

@ -4,7 +4,7 @@ import Models
import SwiftUI
struct StatusRowSwipeView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@EnvironmentObject private var preferences: UserPreferences
@Environment(CurrentAccount.self) private var currentAccount
@Environment(StatusDataController.self) private var statusDataController

View file

@ -3,8 +3,9 @@ import Env
import Models
import SwiftUI
@MainActor
struct StatusRowTextView: View {
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(\.isStatusFocused) private var isFocused
var viewModel: StatusRowViewModel

View file

@ -7,13 +7,14 @@ import Status
import SwiftUI
import SwiftUIIntrospect
@MainActor
public struct TimelineView: View {
private enum Constants {
static let scrollToTop = "top"
}
@Environment(\.scenePhase) private var scenePhase
@EnvironmentObject private var theme: Theme
@Environment(Theme.self) private var theme
@Environment(CurrentAccount.self) private var account
@Environment(StreamWatcher.self) private var watcher
@Environment(Client.self) private var client