Implement theme switcher

This commit is contained in:
Marcin Czachursk 2023-01-13 13:37:01 +01:00
parent 2e95d2b862
commit c1fb80e2bb
13 changed files with 373 additions and 87 deletions

View File

@ -81,6 +81,10 @@
F89992CE296D92E7005994BF /* AttachmentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89992CD296D92E7005994BF /* AttachmentViewModel.swift */; };
F89A46DC296EAACE0062125F /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89A46DB296EAACE0062125F /* SettingsView.swift */; };
F89A46DE296EABA20062125F /* StatusPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89A46DD296EABA20062125F /* StatusPlaceholder.swift */; };
F89D6C3F29716E41001DA3D4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C3E29716E41001DA3D4 /* Theme.swift */; };
F89D6C4229717FDC001DA3D4 /* AccountsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4129717FDC001DA3D4 /* AccountsSection.swift */; };
F89D6C4429718092001DA3D4 /* AccentsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4329718092001DA3D4 /* AccentsSection.swift */; };
F89D6C4629718193001DA3D4 /* ThemeSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4529718193001DA3D4 /* ThemeSection.swift */; };
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7D2965FD89001D8331 /* UserProfileView.swift */; };
F8A93D802965FED4001D8331 /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7F2965FED4001D8331 /* AccountService.swift */; };
F8C14392296AF0B3001FE31D /* String+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C14391296AF0B3001FE31D /* String+Exif.swift */; };
@ -163,6 +167,11 @@
F89992CD296D92E7005994BF /* AttachmentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentViewModel.swift; sourceTree = "<group>"; };
F89A46DB296EAACE0062125F /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
F89A46DD296EABA20062125F /* StatusPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusPlaceholder.swift; sourceTree = "<group>"; };
F89D6C3B29716DBC001DA3D4 /* Vernissage20230113-001.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage20230113-001.xcdatamodel"; sourceTree = "<group>"; };
F89D6C3E29716E41001DA3D4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
F89D6C4129717FDC001DA3D4 /* AccountsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSection.swift; sourceTree = "<group>"; };
F89D6C4329718092001DA3D4 /* AccentsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentsSection.swift; sourceTree = "<group>"; };
F89D6C4529718193001DA3D4 /* ThemeSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSection.swift; sourceTree = "<group>"; };
F8A93D7D2965FD89001D8331 /* UserProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = "<group>"; };
F8A93D7F2965FED4001D8331 /* AccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountService.swift; sourceTree = "<group>"; };
F8AF2A61297073FE00D2DA3F /* Vernissage20230112-001.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage20230112-001.xcdatamodel"; sourceTree = "<group>"; };
@ -234,6 +243,7 @@
F88FAD2C295F4AD7009B20C9 /* ApplicationState.swift */,
F866F6AD29606367002E8F88 /* ApplicationViewMode.swift */,
F8CC95CD2970761D00C9C2AC /* TintColor.swift */,
F89D6C3E29716E41001DA3D4 /* Theme.swift */,
);
path = Models;
sourceTree = "<group>";
@ -273,6 +283,7 @@
F83901A2295D863B00456AE2 /* Widgets */ = {
isa = PBXGroup;
children = (
F89D6C4029717FC0001DA3D4 /* SettingsView */,
F83901A5295D8EC000456AE2 /* LabelIcon.swift */,
F85D4972296406E700751DF7 /* BottomRight.swift */,
F85D497629640A5200751DF7 /* ImageRow.swift */,
@ -399,6 +410,16 @@
path = ViewModels;
sourceTree = "<group>";
};
F89D6C4029717FC0001DA3D4 /* SettingsView */ = {
isa = PBXGroup;
children = (
F89D6C4129717FDC001DA3D4 /* AccountsSection.swift */,
F89D6C4329718092001DA3D4 /* AccentsSection.swift */,
F89D6C4529718193001DA3D4 /* ThemeSection.swift */,
);
path = SettingsView;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -480,6 +501,7 @@
files = (
F85D497729640A5200751DF7 /* ImageRow.swift in Sources */,
F8210DDF2966CFC7001D9973 /* AttachmentData+Attachment.swift in Sources */,
F89D6C4229717FDC001DA3D4 /* AccountsSection.swift in Sources */,
F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */,
F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */,
F8210DEA2966E4F9001D9973 /* AnimatePlaceholderModifier.swift in Sources */,
@ -502,7 +524,9 @@
F8210DCF2966B600001D9973 /* ImageRowAsync.swift in Sources */,
F85D498329642FAC00751DF7 /* AttachmentData+Comperable.swift in Sources */,
F85D497B29640C8200751DF7 /* UsernameRow.swift in Sources */,
F89D6C4429718092001DA3D4 /* AccentsSection.swift in Sources */,
F85D497929640B9D00751DF7 /* ImagesCarousel.swift in Sources */,
F89D6C3F29716E41001DA3D4 /* Theme.swift in Sources */,
F8CC95CE2970761D00C9C2AC /* TintColor.swift in Sources */,
F89992CC296D9231005994BF /* StatusViewModel.swift in Sources */,
F80048052961850500E6868A /* StatusData+CoreDataClass.swift in Sources */,
@ -523,6 +547,7 @@
F89A46DE296EABA20062125F /* StatusPlaceholder.swift in Sources */,
F88C2482295C3A4F0006098B /* StatusView.swift in Sources */,
F866F6A329604161002E8F88 /* AccountDataHandler.swift in Sources */,
F89D6C4629718193001DA3D4 /* ThemeSection.swift in Sources */,
F85D497F296416C800751DF7 /* CommentsSection.swift in Sources */,
F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */,
F866F6A529604194002E8F88 /* ApplicationSettingsHandler.swift in Sources */,
@ -799,10 +824,11 @@
F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
F89D6C3B29716DBC001DA3D4 /* Vernissage20230113-001.xcdatamodel */,
F8AF2A61297073FE00D2DA3F /* Vernissage20230112-001.xcdatamodel */,
F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */,
);
currentVersion = F8AF2A61297073FE00D2DA3F /* Vernissage20230112-001.xcdatamodel */;
currentVersion = F89D6C3B29716DBC001DA3D4 /* Vernissage20230113-001.xcdatamodel */;
path = Vernissage.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;

View File

@ -3,13 +3,10 @@
// Copyright © 2023 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
//
import Foundation
import CoreData
extension ApplicationSettings {
@nonobjc public class func fetchRequest() -> NSFetchRequest<ApplicationSettings> {
@ -17,6 +14,11 @@ extension ApplicationSettings {
}
@NSManaged public var currentAccount: String?
@NSManaged public var theme: Int32
@NSManaged public var tintColor: Int32
}
extension ApplicationSettings : Identifiable {
}

View File

@ -43,6 +43,12 @@ class ApplicationSettingsHandler {
defaultSettings.tintColor = Int32(tintColor.rawValue)
CoreDataHandler.shared.save()
}
func setDefaultTheme(theme: Theme) {
let defaultSettings = self.getDefaultSettings()
defaultSettings.theme = Int32(theme.rawValue)
CoreDataHandler.shared.save()
}
private func createApplicationSettingsEntity() -> ApplicationSettings {
let context = CoreDataHandler.shared.container.viewContext

View File

@ -14,6 +14,7 @@ public class ApplicationState: ObservableObject {
@Published var accountData: AccountData?
@Published var tintColor = TintColor.accentColor2
@Published var theme = Theme.system
@Published var showInteractionStatusId = ""
}

View File

@ -0,0 +1,22 @@
//
// https://mczachurski.dev
// Copyright © 2023 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import SwiftUI
public enum Theme: Int {
case system, light, dark
public func colorScheme() -> ColorScheme? {
switch self {
case .system:
return nil
case .light:
return ColorScheme.light
case .dark:
return ColorScheme.dark
}
}
}

View File

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Vernissage20230112-001.xcdatamodel</string>
<string>Vernissage20230113-001.xcdatamodel</string>
</dict>
</plist>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22C65" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="AccountData" representedClassName="AccountData" syncable="YES">
<attribute name="accessToken" optional="YES" attributeType="String"/>
<attribute name="acct" attributeType="String"/>
<attribute name="avatar" optional="YES" attributeType="URI"/>
<attribute name="avatarData" optional="YES" attributeType="Binary"/>
<attribute name="clientId" attributeType="String"/>
<attribute name="clientSecret" attributeType="String"/>
<attribute name="clientVapidKey" attributeType="String"/>
<attribute name="createdAt" attributeType="String"/>
<attribute name="displayName" optional="YES" attributeType="String"/>
<attribute name="followersCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="followingCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="header" optional="YES" attributeType="URI"/>
<attribute name="id" attributeType="String"/>
<attribute name="locked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="note" optional="YES" attributeType="String"/>
<attribute name="serverUrl" attributeType="URI"/>
<attribute name="statusesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="url" optional="YES" attributeType="URI"/>
<attribute name="username" attributeType="String"/>
<relationship name="statuses" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="StatusData" inverseName="pixelfedAccount" inverseEntity="StatusData"/>
</entity>
<entity name="ApplicationSettings" representedClassName="ApplicationSettings" syncable="YES">
<attribute name="currentAccount" optional="YES" attributeType="String"/>
<attribute name="theme" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="tintColor" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
</entity>
<entity name="AttachmentData" representedClassName="AttachmentData" syncable="YES">
<attribute name="blurhash" optional="YES" attributeType="String"/>
<attribute name="data" attributeType="Binary" allowsExternalBinaryDataStorage="YES"/>
<attribute name="exifCamera" optional="YES" attributeType="String"/>
<attribute name="exifCreatedDate" optional="YES" attributeType="String"/>
<attribute name="exifExposure" optional="YES" attributeType="String"/>
<attribute name="exifLens" optional="YES" attributeType="String"/>
<attribute name="id" attributeType="String"/>
<attribute name="metaImageHeight" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="metaImageWidth" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="previewUrl" optional="YES" attributeType="URI"/>
<attribute name="remoteUrl" optional="YES" attributeType="URI"/>
<attribute name="statusId" attributeType="String"/>
<attribute name="text" optional="YES" attributeType="String"/>
<attribute name="type" attributeType="String"/>
<attribute name="url" attributeType="URI"/>
<relationship name="statusRelation" maxCount="1" deletionRule="Cascade" destinationEntity="StatusData" inverseName="attachmentRelation" inverseEntity="StatusData"/>
</entity>
<entity name="StatusData" representedClassName="StatusData" syncable="YES">
<attribute name="accountAvatar" optional="YES" attributeType="URI"/>
<attribute name="accountDisplayName" optional="YES" attributeType="String"/>
<attribute name="accountId" attributeType="String"/>
<attribute name="accountUsername" optional="YES" attributeType="String"/>
<attribute name="applicationName" optional="YES" attributeType="String"/>
<attribute name="applicationWebsite" optional="YES" attributeType="URI"/>
<attribute name="bookmarked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="content" attributeType="String"/>
<attribute name="createdAt" attributeType="String"/>
<attribute name="favourited" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="favouritesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="String"/>
<attribute name="inReplyToAccount" optional="YES" attributeType="String"/>
<attribute name="inReplyToId" optional="YES" attributeType="String"/>
<attribute name="muted" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="pinned" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="reblogged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="reblogsCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="repliesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sensitive" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="spoilerText" optional="YES" attributeType="String"/>
<attribute name="uri" attributeType="String"/>
<attribute name="url" optional="YES" attributeType="URI"/>
<attribute name="visibility" attributeType="String"/>
<relationship name="attachmentRelation" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="AttachmentData" inverseName="statusRelation" inverseEntity="AttachmentData"/>
<relationship name="pixelfedAccount" maxCount="1" deletionRule="No Action" destinationEntity="AccountData" inverseName="statuses" inverseEntity="AccountData"/>
</entity>
</model>

View File

@ -15,6 +15,7 @@ struct VernissageApp: App {
@State var applicationViewMode: ApplicationViewMode = .loading
@State var tintColor = ApplicationState.shared.tintColor.color()
@State var theme = ApplicationState.shared.theme.colorScheme()
var body: some Scene {
WindowGroup {
@ -31,19 +32,28 @@ struct VernissageApp: App {
case .mainView:
MainView { color in
self.tintColor = color.color()
} onThemeChange: { theme in
self.theme = theme.colorScheme()
}
.environment(\.managedObjectContext, coreDataHandler.container.viewContext)
.environmentObject(applicationState)
}
}
.tint(self.tintColor)
.preferredColorScheme(self.theme)
.task {
let defaultSettings = ApplicationSettingsHandler.shared.getDefaultSettings()
if let tintColor = TintColor(rawValue: Int(defaultSettings.tintColor)) {
self.applicationState.tintColor = tintColor
self.tintColor = tintColor.color()
}
if let theme = Theme(rawValue: Int(defaultSettings.theme)) {
self.applicationState.theme = theme
self.theme = theme.colorScheme()
}
await AuthorizationService.shared.verifyAccount({ accountData in
guard let accountData = accountData else {
self.applicationViewMode = .signIn

View File

@ -19,6 +19,7 @@ struct MainView: View {
@EnvironmentObject var applicationState: ApplicationState
var onTintChange: ((TintColor) -> Void)?
var onThemeChange: ((Theme) -> Void)?
@State private var showSettings = false
@State private var sheet: Sheet?
@ -45,6 +46,8 @@ struct MainView: View {
case .settings:
SettingsView { color in
self.onTintChange?(color)
} onThemeChange: { theme in
self.onThemeChange?(theme)
}
case .compose:
ComposeView(statusViewModel: .constant(nil))

View File

@ -8,105 +8,46 @@ import SwiftUI
struct SettingsView: View {
@EnvironmentObject var applicationState: ApplicationState
@Environment(\.colorScheme) var colorScheme
@Environment(\.dismiss) private var dismiss
@State var accounts: [AccountData] = []
@State private var matchSystemTheme = true
@State private var theme: ColorScheme?
@State private var appVersion: String?
var onTintChange: ((TintColor) -> Void)?
let accentColors1: [TintColor] = [.accentColor1, .accentColor2, .accentColor3, .accentColor4, .accentColor5]
let accentColors2: [TintColor] = [.accentColor6, .accentColor7, .accentColor8, .accentColor9, .accentColor10]
var onThemeChange: ((Theme) -> Void)?
var body: some View {
NavigationView {
List {
Section("Accounts") {
ForEach(self.accounts) { account in
UsernameRow(accountAvatar: account.avatar,
accountDisplayName: account.displayName,
accountUsername: account.username,
cachedAvatar: CacheAvatarService.shared.getImage(for: account.id))
}
NavigationLink(destination: SignInView()) {
HStack {
Text("New account")
Spacer()
Image(systemName: "person.crop.circle.badge.plus")
}
}
}
Section("Theme") {
Toggle("Match system", isOn: $matchSystemTheme)
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("Light")
Text("Dark")
// Accounts.
AccountsSection()
// Themes.
ThemeSection { theme in
changeTheme(theme: theme)
}
Section("Accent") {
VStack(alignment: .leading) {
HStack(alignment: .center) {
ForEach(accentColors1, id: \.self) { color in
ZStack {
Circle()
.fill(color.color())
.frame(width: 36, height: 36)
.onTapGesture {
self.applicationState.tintColor = color
ApplicationSettingsHandler.shared.setDefaultTintColor(tintColor: color)
self.onTintChange?(color)
}
if color == self.applicationState.tintColor {
Image(systemName: "checkmark")
.tint(Color.mainTextColor)
.fontWeight(.bold)
}
}
if color != accentColors1.last {
Spacer()
}
}
}
.padding(.vertical, 8)
HStack(alignment: .center) {
ForEach(accentColors2, id: \.self) { color in
ZStack {
Circle()
.fill(color.color())
.frame(width: 36, height: 36)
.onTapGesture {
self.applicationState.tintColor = color
ApplicationSettingsHandler.shared.setDefaultTintColor(tintColor: color)
self.onTintChange?(color)
}
if color == self.applicationState.tintColor {
Image(systemName: "checkmark")
.tint(Color.mainTextColor)
.fontWeight(.bold)
}
}
if color != accentColors2.last {
Spacer()
}
}
}
.padding(.vertical, 8)
}
// Accents.
AccentsSection { color in
self.onTintChange?(color)
}
// Other.
Section("Other") {
Text("Third party") // Link to dependeinces
Text("Report a bug")
Text("Follow me on Mastodon")
}
// Version.
Section() {
Text("Version") // Link to dependeinces
HStack {
Text("Version")
Spacer()
Text(appVersion ?? "")
.foregroundColor(.accentColor)
}
}
}
.frame(alignment: .topLeading)
@ -118,11 +59,28 @@ struct SettingsView: View {
}
}
.task {
self.accounts = AccountDataHandler.shared.getAccountsData()
self.appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification), perform: { _ in
self.theme = applicationState.theme.colorScheme() ?? self.getSystemColorScheme()
})
.navigationBarTitle(Text("Settings"), displayMode: .inline)
.preferredColorScheme(self.theme)
}
}
private func changeTheme(theme: Theme) {
// Change theme of current modal screen (unformtunatelly it's not changed autmatically_
self.theme = theme.colorScheme() ?? self.getSystemColorScheme()
self.applicationState.theme = theme
ApplicationSettingsHandler.shared.setDefaultTheme(theme: theme)
onThemeChange?(theme)
}
func getSystemColorScheme() -> ColorScheme {
return UITraitCollection.current.userInterfaceStyle == .light ? .light : .dark
}
}
struct SettingsView_Previews: PreviewProvider {

View File

@ -0,0 +1,79 @@
//
// https://mczachurski.dev
// Copyright © 2023 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import SwiftUI
struct AccentsSection: View {
@EnvironmentObject var applicationState: ApplicationState
var onTintChange: ((TintColor) -> Void)?
private let accentColors1: [TintColor] = [.accentColor1, .accentColor2, .accentColor3, .accentColor4, .accentColor5]
private let accentColors2: [TintColor] = [.accentColor6, .accentColor7, .accentColor8, .accentColor9, .accentColor10]
var body: some View {
Section("Accent") {
VStack(alignment: .leading) {
HStack(alignment: .center) {
ForEach(accentColors1, id: \.self) { color in
ZStack {
Circle()
.fill(color.color())
.frame(width: 36, height: 36)
.onTapGesture {
self.applicationState.tintColor = color
ApplicationSettingsHandler.shared.setDefaultTintColor(tintColor: color)
self.onTintChange?(color)
}
if color == self.applicationState.tintColor {
Image(systemName: "checkmark")
.foregroundColor(Color.white)
.fontWeight(.bold)
}
}
if color != accentColors1.last {
Spacer()
}
}
}
.padding(.vertical, 8)
HStack(alignment: .center) {
ForEach(accentColors2, id: \.self) { color in
ZStack {
Circle()
.fill(color.color())
.frame(width: 36, height: 36)
.onTapGesture {
self.applicationState.tintColor = color
ApplicationSettingsHandler.shared.setDefaultTintColor(tintColor: color)
self.onTintChange?(color)
}
if color == self.applicationState.tintColor {
Image(systemName: "checkmark")
.foregroundColor(Color.white)
.fontWeight(.bold)
}
}
if color != accentColors2.last {
Spacer()
}
}
}
.padding(.vertical, 8)
}
}
}
}
struct AccentsSection_Previews: PreviewProvider {
static var previews: some View {
AccentsSection()
}
}

View File

@ -0,0 +1,40 @@
//
// https://mczachurski.dev
// Copyright © 2023 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import SwiftUI
struct AccountsSection: View {
@State private var accounts: [AccountData] = []
var body: some View {
Section("Accounts") {
ForEach(self.accounts) { account in
UsernameRow(accountAvatar: account.avatar,
accountDisplayName: account.displayName,
accountUsername: account.username,
cachedAvatar: CacheAvatarService.shared.getImage(for: account.id))
}
NavigationLink(destination: SignInView()) {
HStack {
Text("New account")
Spacer()
Image(systemName: "person.crop.circle.badge.plus")
}
}
}
.task {
self.accounts = AccountDataHandler.shared.getAccountsData()
}
}
}
struct AccountsSection_Previews: PreviewProvider {
static var previews: some View {
AccountsSection()
}
}

View File

@ -0,0 +1,63 @@
//
// https://mczachurski.dev
// Copyright © 2023 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import SwiftUI
struct ThemeSection: View {
@EnvironmentObject var applicationState: ApplicationState
@Environment(\.colorScheme) var colorScheme
var onThemeChange: ((Theme) -> Void)?
var body: some View {
Section("Theme") {
Button {
onThemeChange?(.system)
} label: {
HStack {
Text("System")
.foregroundColor(.label)
Spacer()
if self.applicationState.theme == .system {
Image(systemName: "checkmark")
}
}
}
Button {
onThemeChange?(.light)
} label: {
HStack {
Text("Light")
.foregroundColor(.label)
Spacer()
if self.applicationState.theme == .light {
Image(systemName: "checkmark")
}
}
}
Button {
onThemeChange?(.dark)
} label: {
HStack {
Text("Dark")
.foregroundColor(.label)
Spacer()
if self.applicationState.theme == .dark {
Image(systemName: "checkmark")
}
}
}
}
}
}
struct ThemeSection_Previews: PreviewProvider {
static var previews: some View {
ThemeSection()
}
}