Use separate defaults keys for instances and accounts

This commit is contained in:
Arkadiusz Fal 2021-09-26 22:39:27 +02:00
parent a0f74a5899
commit 3d35110c67
11 changed files with 46 additions and 71 deletions

View File

@ -1,11 +0,0 @@
import Foundation
extension String {
var serializationSafe: String {
let serializationUnsafe = ":;"
let forbidden = CharacterSet(charactersIn: serializationUnsafe)
let result = unicodeScalars.filter { !forbidden.contains($0) }
return String(String.UnicodeScalarView(result))
}
}

View File

@ -2,9 +2,6 @@ import Foundation
extension Instance { extension Instance {
static var fixture: Instance { static var fixture: Instance {
Instance(name: "Home", url: "https://invidious.home.net", accounts: [ Instance(name: "Home", url: "https://invidious.home.net")
.init(id: UUID(), name: "Evelyn", url: "https://invidious.home.net", sid: "abc"),
.init(id: UUID(), name: "Jake", url: "https://invidious.home.net", sid: "xyz")
])
} }
} }

View File

@ -5,13 +5,15 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
struct Account: Defaults.Serializable, Hashable, Identifiable { struct Account: Defaults.Serializable, Hashable, Identifiable {
static var bridge = AccountsBridge() static var bridge = AccountsBridge()
let id: UUID? let id: UUID
let instanceID: UUID
var name: String? var name: String?
let url: String let url: String
let sid: String let sid: String
init(id: UUID? = nil, name: String? = nil, url: String, sid: String) { init(id: UUID? = nil, instanceID: UUID, name: String? = nil, url: String, sid: String) {
self.id = id ?? UUID() self.id = id ?? UUID()
self.instanceID = instanceID
self.name = name self.name = name
self.url = url self.url = url
self.sid = sid self.sid = sid
@ -45,7 +47,8 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
} }
return [ return [
"id": value.id?.uuidString ?? "", "id": value.id.uuidString,
"instanceID": value.instanceID.uuidString,
"name": value.name ?? "", "name": value.name ?? "",
"url": value.url, "url": value.url,
"sid": value.sid "sid": value.sid
@ -55,30 +58,32 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
func deserialize(_ object: Serializable?) -> Value? { func deserialize(_ object: Serializable?) -> Value? {
guard guard
let object = object, let object = object,
let id = object["id"],
let instanceID = object["instanceID"],
let url = object["url"], let url = object["url"],
let sid = object["sid"] let sid = object["sid"]
else { else {
return nil return nil
} }
let uuid = UUID(uuidString: id)
let instanceUUID = UUID(uuidString: instanceID)!
let name = object["name"] ?? "" let name = object["name"] ?? ""
return Account(name: name, url: url, sid: sid) return Account(id: uuid, instanceID: instanceUUID, name: name, url: url, sid: sid)
} }
} }
static var bridge = InstancesBridge() static var bridge = InstancesBridge()
let id: UUID? let id: UUID
let name: String let name: String
let url: String let url: String
var accounts = [Account]()
init(id: UUID? = nil, name: String, url: String, accounts: [Account] = []) { init(id: UUID? = nil, name: String, url: String) {
self.id = id ?? UUID() self.id = id ?? UUID()
self.name = name self.name = name
self.url = url self.url = url
self.accounts = accounts
} }
var description: String { var description: String {
@ -90,7 +95,7 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
} }
var anonymousAccount: Account { var anonymousAccount: Account {
Account(name: "Anonymous", url: url, sid: "") Account(instanceID: id, name: "Anonymous", url: url, sid: "")
} }
struct InstancesBridge: Defaults.Bridge { struct InstancesBridge: Defaults.Bridge {
@ -103,10 +108,9 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
} }
return [ return [
"id": value.id?.uuidString ?? "", "id": value.id.uuidString,
"name": value.name, "name": value.name,
"url": value.url, "url": value.url
"accounts": value.accounts.map { "\($0.id!):\($0.name ?? ""):\($0.sid)" }.joined(separator: ";")
] ]
} }
@ -119,24 +123,10 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
return nil return nil
} }
let name = object["name"] ?? ""
let accounts = object["accounts"] ?? ""
let uuid = UUID(uuidString: id) let uuid = UUID(uuidString: id)
let name = object["name"] ?? ""
var instance = Instance(id: uuid, name: name, url: url) return Instance(id: uuid, name: name, url: url)
accounts.split(separator: ";").forEach { sid in
let components = sid.components(separatedBy: ":")
let id = components[0]
let name = components[1]
let sid = components[2]
let uuid = UUID(uuidString: id)
instance.accounts.append(Account(id: uuid, name: name, url: instance.url, sid: sid))
}
return instance
} }
} }

View File

@ -3,7 +3,7 @@ import Foundation
final class InstancesModel: ObservableObject { final class InstancesModel: ObservableObject {
var defaultAccount: Instance.Account! { var defaultAccount: Instance.Account! {
Defaults[.instances].first?.accounts.first Defaults[.accounts].first
} }
func find(_ id: Instance.ID?) -> Instance? { func find(_ id: Instance.ID?) -> Instance? {
@ -15,7 +15,7 @@ final class InstancesModel: ObservableObject {
} }
func accounts(_ id: Instance.ID?) -> [Instance.Account] { func accounts(_ id: Instance.ID?) -> [Instance.Account] {
find(id)?.accounts ?? [] Defaults[.accounts].filter { $0.instanceID == id }
} }
func add(name: String, url: String) -> Instance { func add(name: String, url: String) -> Instance {
@ -32,20 +32,15 @@ final class InstancesModel: ObservableObject {
} }
func addAccount(instance: Instance, name: String, sid: String) -> Instance.Account { func addAccount(instance: Instance, name: String, sid: String) -> Instance.Account {
let account = Instance.Account(name: name, url: instance.url, sid: sid) let account = Instance.Account(instanceID: instance.id, name: name, url: instance.url, sid: sid)
Defaults[.accounts].append(account)
if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) {
Defaults[.instances][index].accounts.append(account)
}
return account return account
} }
func removeAccount(instance: Instance, account: Instance.Account) { func removeAccount(_ account: Instance.Account) {
if let instanceIndex = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) { if let accountIndex = Defaults[.accounts].firstIndex(where: { $0.id == account.id }) {
if let accountIndex = Defaults[.instances][instanceIndex].accounts.firstIndex(where: { $0.id == account.id }) { Defaults[.accounts].remove(at: accountIndex)
Defaults[.instances][instanceIndex].accounts.remove(at: accountIndex)
}
} }
} }
} }

View File

@ -80,9 +80,6 @@
375168D62700FAFF008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; }; 375168D62700FAFF008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
375168D72700FDB8008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; }; 375168D72700FDB8008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
375168D82700FDB9008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; }; 375168D82700FDB9008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
375168DB27010806008F96A6 /* String+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D92701070E008F96A6 /* String+Format.swift */; };
375168DC27010807008F96A6 /* String+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D92701070E008F96A6 /* String+Format.swift */; };
375168DD27010808008F96A6 /* String+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D92701070E008F96A6 /* String+Format.swift */; };
375DFB5826F9DA010013F468 /* InstancesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375DFB5726F9DA010013F468 /* InstancesModel.swift */; }; 375DFB5826F9DA010013F468 /* InstancesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375DFB5726F9DA010013F468 /* InstancesModel.swift */; };
375DFB5926F9DA010013F468 /* InstancesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375DFB5726F9DA010013F468 /* InstancesModel.swift */; }; 375DFB5926F9DA010013F468 /* InstancesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375DFB5726F9DA010013F468 /* InstancesModel.swift */; };
375DFB5A26F9DA010013F468 /* InstancesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375DFB5726F9DA010013F468 /* InstancesModel.swift */; }; 375DFB5A26F9DA010013F468 /* InstancesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375DFB5726F9DA010013F468 /* InstancesModel.swift */; };
@ -332,7 +329,6 @@
37484C2C26FC844700287258 /* InstanceDetailsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceDetailsSettingsView.swift; sourceTree = "<group>"; }; 37484C2C26FC844700287258 /* InstanceDetailsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceDetailsSettingsView.swift; sourceTree = "<group>"; };
37484C3026FCB8F900287258 /* AccountValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountValidator.swift; sourceTree = "<group>"; }; 37484C3026FCB8F900287258 /* AccountValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountValidator.swift; sourceTree = "<group>"; };
375168D52700FAFF008F96A6 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = "<group>"; }; 375168D52700FAFF008F96A6 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = "<group>"; };
375168D92701070E008F96A6 /* String+Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Format.swift"; sourceTree = "<group>"; };
375DFB5726F9DA010013F468 /* InstancesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesModel.swift; sourceTree = "<group>"; }; 375DFB5726F9DA010013F468 /* InstancesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesModel.swift; sourceTree = "<group>"; };
3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = "<group>"; }; 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = "<group>"; };
3761AC0E26F0F9A600AA496F /* UnsubscribeAlertModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnsubscribeAlertModifier.swift; sourceTree = "<group>"; }; 3761AC0E26F0F9A600AA496F /* UnsubscribeAlertModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnsubscribeAlertModifier.swift; sourceTree = "<group>"; };
@ -648,7 +644,6 @@
379775922689365600DD52A8 /* Array+Next.swift */, 379775922689365600DD52A8 /* Array+Next.swift */,
376578842685429C00D4EA09 /* CaseIterable+Next.swift */, 376578842685429C00D4EA09 /* CaseIterable+Next.swift */,
37BA794E26DC3E0E002A0235 /* Int+Format.swift */, 37BA794E26DC3E0E002A0235 /* Int+Format.swift */,
375168D92701070E008F96A6 /* String+Format.swift */,
377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */, 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */,
); );
path = Extensions; path = Extensions;
@ -1079,7 +1074,6 @@
37BA793F26DB8F97002A0235 /* ChannelVideosView.swift in Sources */, 37BA793F26DB8F97002A0235 /* ChannelVideosView.swift in Sources */,
37754C9D26B7500000DBD602 /* VideosView.swift in Sources */, 37754C9D26B7500000DBD602 /* VideosView.swift in Sources */,
37C194C726F6A9C8005D3B96 /* RecentsModel.swift in Sources */, 37C194C726F6A9C8005D3B96 /* RecentsModel.swift in Sources */,
375168DD27010808008F96A6 /* String+Format.swift in Sources */,
37484C1926FC837400287258 /* PlaybackSettingsView.swift in Sources */, 37484C1926FC837400287258 /* PlaybackSettingsView.swift in Sources */,
3711403F26B206A6005B3555 /* SearchModel.swift in Sources */, 3711403F26B206A6005B3555 /* SearchModel.swift in Sources */,
37F64FE426FE70A60081B69E /* RedrawOnViewModifier.swift in Sources */, 37F64FE426FE70A60081B69E /* RedrawOnViewModifier.swift in Sources */,
@ -1214,7 +1208,6 @@
37754C9E26B7500000DBD602 /* VideosView.swift in Sources */, 37754C9E26B7500000DBD602 /* VideosView.swift in Sources */,
3797758C2689345500DD52A8 /* Store.swift in Sources */, 3797758C2689345500DD52A8 /* Store.swift in Sources */,
37141674267A8E10006CA35D /* Country.swift in Sources */, 37141674267A8E10006CA35D /* Country.swift in Sources */,
375168DC27010807008F96A6 /* String+Format.swift in Sources */,
37AAF2A126741C97007FC770 /* SubscriptionsView.swift in Sources */, 37AAF2A126741C97007FC770 /* SubscriptionsView.swift in Sources */,
37BA794C26DC30EC002A0235 /* AppSidebarPlaylists.swift in Sources */, 37BA794C26DC30EC002A0235 /* AppSidebarPlaylists.swift in Sources */,
37D4B19826717E1500C925CA /* Video.swift in Sources */, 37D4B19826717E1500C925CA /* Video.swift in Sources */,
@ -1295,7 +1288,6 @@
37AAF27E26737323007FC770 /* PopularView.swift in Sources */, 37AAF27E26737323007FC770 /* PopularView.swift in Sources */,
37A9966026D6F9B9006E3224 /* WatchNowView.swift in Sources */, 37A9966026D6F9B9006E3224 /* WatchNowView.swift in Sources */,
37484C1F26FC83A400287258 /* InstancesSettingsView.swift in Sources */, 37484C1F26FC83A400287258 /* InstancesSettingsView.swift in Sources */,
375168DB27010806008F96A6 /* String+Format.swift in Sources */,
37AAF29A26740A01007FC770 /* VideosListView.swift in Sources */, 37AAF29A26740A01007FC770 /* VideosListView.swift in Sources */,
37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */, 37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
376578932685490700D4EA09 /* PlaylistsView.swift in Sources */, 376578932685490700D4EA09 /* PlaylistsView.swift in Sources */,

View File

@ -6,6 +6,7 @@ extension Defaults.Keys {
#endif #endif
static let instances = Key<[Instance]>("instances", default: []) static let instances = Key<[Instance]>("instances", default: [])
static let accounts = Key<[Instance.Account]>("accounts", default: [])
static let selectedPlaylistID = Key<String?>("selectedPlaylistID") static let selectedPlaylistID = Key<String?>("selectedPlaylistID")
static let showingAddToPlaylist = Key<Bool>("showingAddToPlaylist", default: false) static let showingAddToPlaylist = Key<Bool>("showingAddToPlaylist", default: false)

View File

@ -2,6 +2,7 @@ import Defaults
import SwiftUI import SwiftUI
struct AccountsMenuView: View { struct AccountsMenuView: View {
@EnvironmentObject<InstancesModel> private var instancesModel
@EnvironmentObject<InvidiousAPI> private var api @EnvironmentObject<InvidiousAPI> private var api
@Default(.instances) private var instances @Default(.instances) private var instances
@ -13,7 +14,7 @@ struct AccountsMenuView: View {
api.setAccount(instance.anonymousAccount) api.setAccount(instance.anonymousAccount)
} }
ForEach(instance.accounts) { account in ForEach(instancesModel.accounts(instance.id)) { account in
Button(accountButtonTitle(instance: instance, account: account)) { Button(accountButtonTitle(instance: instance, account: account)) {
api.setAccount(account) api.setAccount(account)
} }

View File

@ -111,7 +111,7 @@ struct AccountFormView: View {
return return
} }
let account = instances.addAccount(instance: instance, name: name.serializationSafe, sid: sid) let account = instances.addAccount(instance: instance, name: name, sid: sid)
selectedAccount?.wrappedValue = account selectedAccount?.wrappedValue = account
dismiss() dismiss()
@ -120,7 +120,7 @@ struct AccountFormView: View {
private var validator: AccountValidator { private var validator: AccountValidator {
AccountValidator( AccountValidator(
url: instance.url, url: instance.url,
account: Instance.Account(url: instance.url, sid: sid), account: Instance.Account(instanceID: instance.id, url: instance.url, sid: sid),
id: $sid, id: $sid,
valid: $valid, valid: $valid,
validated: $validated validated: $validated

View File

@ -12,6 +12,7 @@ struct AccountSettingsView: View {
var body: some View { var body: some View {
HStack { HStack {
Text(account.description) Text(account.description)
Spacer() Spacer()
HStack { HStack {
@ -23,7 +24,7 @@ struct AccountSettingsView: View {
isPresented: $presentingRemovalConfirmationDialog isPresented: $presentingRemovalConfirmationDialog
) { ) {
Button("Remove", role: .destructive) { Button("Remove", role: .destructive) {
instances.removeAccount(instance: instance, account: account) instances.removeAccount(account)
} }
} }
#if os(macOS) #if os(macOS)

View File

@ -15,12 +15,12 @@ struct InstanceDetailsSettingsView: View {
var body: some View { var body: some View {
List { List {
Section(header: Text("Accounts")) { Section(header: Text("Accounts")) {
ForEach(instance.accounts) { account in ForEach(instances.accounts(instanceID)) { account in
Text(account.description) Text(account.description)
#if !os(tvOS) #if !os(tvOS)
.swipeActions(edge: .trailing, allowsFullSwipe: false) { .swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button("Remove", role: .destructive) { Button("Remove", role: .destructive) {
instances.removeAccount(instance: instance, account: account) instances.removeAccount(account)
accountsChanged.toggle() accountsChanged.toggle()
} }
} }

View File

@ -23,6 +23,14 @@ struct InstancesSettingsView: View {
instancesModel.find(selectedInstanceID) instancesModel.find(selectedInstanceID)
} }
var accounts: [Instance.Account] {
guard selectedInstance != nil else {
return []
}
return instancesModel.accounts(selectedInstanceID)
}
var body: some View { var body: some View {
Group { Group {
#if os(iOS) #if os(iOS)
@ -74,16 +82,17 @@ struct InstancesSettingsView: View {
} }
if let instance = selectedInstance { if let instance = selectedInstance {
if instance.accounts.isEmpty { if accounts.isEmpty {
Text("You have no accounts for this instance") Text("You have no accounts for this instance")
.font(.caption) .font(.caption)
.foregroundColor(.secondary) .foregroundColor(.secondary)
} else { } else {
Text("Accounts") Text("Accounts")
List(selection: $selectedAccount) { List(selection: $selectedAccount) {
ForEach(instance.accounts) { account in ForEach(accounts) { account in
AccountSettingsView(instance: instance, account: account, AccountSettingsView(instance: instance, account: account,
selectedAccount: $selectedAccount) selectedAccount: $selectedAccount)
.tag(account)
} }
} }
#if os(macOS) #if os(macOS)