Save last used account instead of having to set default

This commit is contained in:
Arkadiusz Fal 2021-10-19 23:27:04 +02:00
parent 00d706766c
commit 2d075e7b3a
23 changed files with 169 additions and 273 deletions

View File

@ -3,7 +3,7 @@ import Defaults
import Foundation import Foundation
final class AccountsModel: ObservableObject { final class AccountsModel: ObservableObject {
@Published private(set) var account: Instance.Account! @Published private(set) var current: Instance.Account!
@Published private(set) var invidious = InvidiousAPI() @Published private(set) var invidious = InvidiousAPI()
@Published private(set) var piped = PipedAPI() @Published private(set) var piped = PipedAPI()
@ -11,15 +11,23 @@ final class AccountsModel: ObservableObject {
private var cancellables = [AnyCancellable]() private var cancellables = [AnyCancellable]()
var all: [Instance.Account] { var all: [Instance.Account] {
Defaults[.instances].map(\.anonymousAccount) + Defaults[.accounts] Defaults[.accounts]
}
var lastUsed: Instance.Account? {
guard let id = Defaults[.lastAccountID] else {
return nil
}
return AccountsModel.find(id)
} }
var isEmpty: Bool { var isEmpty: Bool {
account.isNil current.isNil
} }
var signedIn: Bool { var signedIn: Bool {
!isEmpty && !account.anonymous !isEmpty && !current.anonymous
} }
init() { init() {
@ -32,12 +40,12 @@ final class AccountsModel: ObservableObject {
) )
} }
func setAccount(_ account: Instance.Account! = nil) { func setCurrent(_ account: Instance.Account! = nil) {
guard account != self.account else { guard account != current else {
return return
} }
self.account = account current = account
guard !account.isNil else { guard !account.isNil else {
return return
@ -49,5 +57,25 @@ final class AccountsModel: ObservableObject {
case .piped: case .piped:
piped.setAccount(account) piped.setAccount(account)
} }
Defaults[.lastAccountID] = account.anonymous ? nil : account.id
Defaults[.lastInstanceID] = account.instanceID
}
static func find(_ id: Instance.Account.ID) -> Instance.Account? {
Defaults[.accounts].first { $0.id == id }
}
static func add(instance: Instance, name: String, sid: String) -> Instance.Account {
let account = Instance.Account(instanceID: instance.id, name: name, url: instance.url, sid: sid)
Defaults[.accounts].append(account)
return account
}
static func remove(_ account: Instance.Account) {
if let accountIndex = Defaults[.accounts].firstIndex(where: { $0.id == account.id }) {
Defaults[.accounts].remove(at: accountIndex)
}
} }
} }

View File

@ -14,17 +14,17 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
static var bridge = AccountsBridge() static var bridge = AccountsBridge()
let id: String let id: String
let instanceID: UUID let instanceID: String
var name: String? var name: String?
let url: String let url: String
let sid: String let sid: String
let anonymous: Bool let anonymous: Bool
init(id: String? = nil, instanceID: UUID? = nil, name: String? = nil, url: String? = nil, sid: String? = nil, anonymous: Bool = false) { init(id: String? = nil, instanceID: String? = nil, name: String? = nil, url: String? = nil, sid: String? = nil, anonymous: Bool = false) {
self.anonymous = anonymous self.anonymous = anonymous
self.id = id ?? (anonymous ? "anonymous-\(instanceID!)" : UUID().uuidString) self.id = id ?? (anonymous ? "anonymous-\(instanceID!)" : UUID().uuidString)
self.instanceID = instanceID ?? UUID() self.instanceID = instanceID ?? UUID().uuidString
self.name = name self.name = name
self.url = url ?? "" self.url = url ?? ""
self.sid = sid ?? "" self.sid = sid ?? ""
@ -62,7 +62,7 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
return [ return [
"id": value.id, "id": value.id,
"instanceID": value.instanceID.uuidString, "instanceID": value.instanceID,
"name": value.name ?? "", "name": value.name ?? "",
"url": value.url, "url": value.url,
"sid": value.sid "sid": value.sid
@ -80,10 +80,9 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
return nil return nil
} }
let instanceUUID = UUID(uuidString: instanceID)!
let name = object["name"] ?? "" let name = object["name"] ?? ""
return Account(id: id, instanceID: instanceUUID, name: name, url: url, sid: sid) return Account(id: id, instanceID: instanceID, name: name, url: url, sid: sid)
} }
} }
} }
@ -91,13 +90,13 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
static var bridge = InstancesBridge() static var bridge = InstancesBridge()
let app: App let app: App
let id: UUID let id: String
let name: String let name: String
let url: String let url: String
init(app: App, id: UUID? = nil, name: String, url: String) { init(app: App, id: String? = nil, name: String, url: String) {
self.app = app self.app = app
self.id = id ?? UUID() self.id = id ?? UUID().uuidString
self.name = name self.name = name
self.url = url self.url = url
} }
@ -133,7 +132,7 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
return [ return [
"app": value.app.rawValue, "app": value.app.rawValue,
"id": value.id.uuidString, "id": value.id,
"name": value.name, "name": value.name,
"url": value.url "url": value.url
] ]
@ -149,10 +148,9 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
return nil return nil
} }
let uuid = UUID(uuidString: id)
let name = object["name"] ?? "" let name = object["name"] ?? ""
return Instance(app: app, id: uuid, name: name, url: url) return Instance(app: app, id: id, name: name, url: url)
} }
} }

View File

@ -2,21 +2,19 @@ import Defaults
import Foundation import Foundation
final class InstancesModel: ObservableObject { final class InstancesModel: ObservableObject {
@Published var defaultAccount: Instance.Account?
var all: [Instance] { var all: [Instance] {
Defaults[.instances] Defaults[.instances]
} }
init() { var lastUsed: Instance? {
guard let id = Defaults[.defaultAccountID] else { guard let id = Defaults[.lastInstanceID] else {
return return nil
} }
defaultAccount = findAccount(id) return InstancesModel.find(id)
} }
func find(_ id: Instance.ID?) -> Instance? { static func find(_ id: Instance.ID?) -> Instance? {
guard id != nil else { guard id != nil else {
return nil return nil
} }
@ -24,48 +22,26 @@ final class InstancesModel: ObservableObject {
return Defaults[.instances].first { $0.id == id } return Defaults[.instances].first { $0.id == id }
} }
func accounts(_ id: Instance.ID?) -> [Instance.Account] { static func accounts(_ id: Instance.ID?) -> [Instance.Account] {
Defaults[.accounts].filter { $0.instanceID == id } Defaults[.accounts].filter { $0.instanceID == id }
} }
func add(app: Instance.App, name: String, url: String) -> Instance { static func add(app: Instance.App, name: String, url: String) -> Instance {
let instance = Instance(app: app, name: name, url: url) let instance = Instance(app: app, id: UUID().uuidString, name: name, url: url)
Defaults[.instances].append(instance) Defaults[.instances].append(instance)
return instance return instance
} }
func remove(_ instance: Instance) { static func remove(_ instance: Instance) {
let accounts = accounts(instance.id) let accounts = InstancesModel.accounts(instance.id)
if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) { if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) {
Defaults[.instances].remove(at: index) Defaults[.instances].remove(at: index)
accounts.forEach { removeAccount($0) } accounts.forEach { AccountsModel.remove($0) }
} }
} }
func findAccount(_ id: Instance.Account.ID) -> Instance.Account? { static func setLastAccount(_ account: Instance.Account?) {
Defaults[.accounts].first { $0.id == id } Defaults[.lastAccountID] = account?.id
}
func addAccount(instance: Instance, name: String, sid: String) -> Instance.Account {
let account = Instance.Account(instanceID: instance.id, name: name, url: instance.url, sid: sid)
Defaults[.accounts].append(account)
return account
}
func removeAccount(_ account: Instance.Account) {
if let accountIndex = Defaults[.accounts].firstIndex(where: { $0.id == account.id }) {
Defaults[.accounts].remove(at: accountIndex)
}
}
func setDefaultAccount(_ account: Instance.Account?) {
Defaults[.defaultAccountID] = account?.id
defaultAccount = account
}
func resetDefaultAccount() {
setDefaultAccount(nil)
} }
} }

View File

@ -101,7 +101,6 @@
37484C1B26FC837400287258 /* PlaybackSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlaybackSettingsView.swift */; }; 37484C1B26FC837400287258 /* PlaybackSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlaybackSettingsView.swift */; };
37484C1D26FC83A400287258 /* InstancesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1C26FC83A400287258 /* InstancesSettingsView.swift */; }; 37484C1D26FC83A400287258 /* InstancesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1C26FC83A400287258 /* InstancesSettingsView.swift */; };
37484C1F26FC83A400287258 /* InstancesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1C26FC83A400287258 /* InstancesSettingsView.swift */; }; 37484C1F26FC83A400287258 /* InstancesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1C26FC83A400287258 /* InstancesSettingsView.swift */; };
37484C2226FC83C400287258 /* AccountSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2026FC83C400287258 /* AccountSettingsView.swift */; };
37484C2526FC83E000287258 /* InstanceFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceFormView.swift */; }; 37484C2526FC83E000287258 /* InstanceFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceFormView.swift */; };
37484C2626FC83E000287258 /* InstanceFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceFormView.swift */; }; 37484C2626FC83E000287258 /* InstanceFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceFormView.swift */; };
37484C2726FC83E000287258 /* InstanceFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceFormView.swift */; }; 37484C2726FC83E000287258 /* InstanceFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceFormView.swift */; };
@ -325,9 +324,6 @@
37F64FE526FE70A60081B69E /* RedrawOnViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F64FE326FE70A60081B69E /* RedrawOnViewModifier.swift */; }; 37F64FE526FE70A60081B69E /* RedrawOnViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F64FE326FE70A60081B69E /* RedrawOnViewModifier.swift */; };
37F64FE626FE70A60081B69E /* RedrawOnViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F64FE326FE70A60081B69E /* RedrawOnViewModifier.swift */; }; 37F64FE626FE70A60081B69E /* RedrawOnViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F64FE326FE70A60081B69E /* RedrawOnViewModifier.swift */; };
37FD43DC270470B70073EE42 /* InstancesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43DB270470B70073EE42 /* InstancesSettingsView.swift */; }; 37FD43DC270470B70073EE42 /* InstancesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43DB270470B70073EE42 /* InstancesSettingsView.swift */; };
37FD43DE2704717F0073EE42 /* DefaultAccountHint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43DD2704717F0073EE42 /* DefaultAccountHint.swift */; };
37FD43DF2704717F0073EE42 /* DefaultAccountHint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43DD2704717F0073EE42 /* DefaultAccountHint.swift */; };
37FD43E02704717F0073EE42 /* DefaultAccountHint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43DD2704717F0073EE42 /* DefaultAccountHint.swift */; };
37FD43E32704847C0073EE42 /* View+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43E22704847C0073EE42 /* View+Fixtures.swift */; }; 37FD43E32704847C0073EE42 /* View+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43E22704847C0073EE42 /* View+Fixtures.swift */; };
37FD43E42704847C0073EE42 /* View+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43E22704847C0073EE42 /* View+Fixtures.swift */; }; 37FD43E42704847C0073EE42 /* View+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43E22704847C0073EE42 /* View+Fixtures.swift */; };
37FD43E52704847C0073EE42 /* View+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43E22704847C0073EE42 /* View+Fixtures.swift */; }; 37FD43E52704847C0073EE42 /* View+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43E22704847C0073EE42 /* View+Fixtures.swift */; };
@ -390,7 +386,6 @@
3748186D26A769D60084E870 /* DetailBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailBadge.swift; sourceTree = "<group>"; }; 3748186D26A769D60084E870 /* DetailBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailBadge.swift; sourceTree = "<group>"; };
37484C1826FC837400287258 /* PlaybackSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSettingsView.swift; sourceTree = "<group>"; }; 37484C1826FC837400287258 /* PlaybackSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSettingsView.swift; sourceTree = "<group>"; };
37484C1C26FC83A400287258 /* InstancesSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesSettingsView.swift; sourceTree = "<group>"; }; 37484C1C26FC83A400287258 /* InstancesSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesSettingsView.swift; sourceTree = "<group>"; };
37484C2026FC83C400287258 /* AccountSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingsView.swift; sourceTree = "<group>"; };
37484C2426FC83E000287258 /* InstanceFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceFormView.swift; sourceTree = "<group>"; }; 37484C2426FC83E000287258 /* InstanceFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceFormView.swift; sourceTree = "<group>"; };
37484C2826FC83FF00287258 /* AccountFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFormView.swift; sourceTree = "<group>"; }; 37484C2826FC83FF00287258 /* AccountFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFormView.swift; sourceTree = "<group>"; };
37484C2C26FC844700287258 /* AccountsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsView.swift; sourceTree = "<group>"; }; 37484C2C26FC844700287258 /* AccountsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsView.swift; sourceTree = "<group>"; };
@ -481,7 +476,6 @@
37F4AE7126828F0900BD60EA /* VideosCellsVertical.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosCellsVertical.swift; sourceTree = "<group>"; }; 37F4AE7126828F0900BD60EA /* VideosCellsVertical.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosCellsVertical.swift; sourceTree = "<group>"; };
37F64FE326FE70A60081B69E /* RedrawOnViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedrawOnViewModifier.swift; sourceTree = "<group>"; }; 37F64FE326FE70A60081B69E /* RedrawOnViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedrawOnViewModifier.swift; sourceTree = "<group>"; };
37FD43DB270470B70073EE42 /* InstancesSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesSettingsView.swift; sourceTree = "<group>"; }; 37FD43DB270470B70073EE42 /* InstancesSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesSettingsView.swift; sourceTree = "<group>"; };
37FD43DD2704717F0073EE42 /* DefaultAccountHint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAccountHint.swift; sourceTree = "<group>"; };
37FD43E22704847C0073EE42 /* View+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Fixtures.swift"; sourceTree = "<group>"; }; 37FD43E22704847C0073EE42 /* View+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Fixtures.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -653,7 +647,6 @@
children = ( children = (
37484C2826FC83FF00287258 /* AccountFormView.swift */, 37484C2826FC83FF00287258 /* AccountFormView.swift */,
37484C2C26FC844700287258 /* AccountsSettingsView.swift */, 37484C2C26FC844700287258 /* AccountsSettingsView.swift */,
37FD43DD2704717F0073EE42 /* DefaultAccountHint.swift */,
37484C2426FC83E000287258 /* InstanceFormView.swift */, 37484C2426FC83E000287258 /* InstanceFormView.swift */,
37484C1C26FC83A400287258 /* InstancesSettingsView.swift */, 37484C1C26FC83A400287258 /* InstancesSettingsView.swift */,
37484C1826FC837400287258 /* PlaybackSettingsView.swift */, 37484C1826FC837400287258 /* PlaybackSettingsView.swift */,
@ -865,7 +858,6 @@
37FD43E1270472060073EE42 /* Settings */ = { 37FD43E1270472060073EE42 /* Settings */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
37484C2026FC83C400287258 /* AccountSettingsView.swift */,
37FD43DB270470B70073EE42 /* InstancesSettingsView.swift */, 37FD43DB270470B70073EE42 /* InstancesSettingsView.swift */,
); );
path = Settings; path = Settings;
@ -1291,7 +1283,6 @@
3748186626A7627F0084E870 /* Video+Fixtures.swift in Sources */, 3748186626A7627F0084E870 /* Video+Fixtures.swift in Sources */,
37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */, 37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
377FC7DC267A081800A6BBAF /* PopularView.swift in Sources */, 377FC7DC267A081800A6BBAF /* PopularView.swift in Sources */,
37FD43DE2704717F0073EE42 /* DefaultAccountHint.swift in Sources */,
37CC3F4C270CFE1700608308 /* PlayerQueueView.swift in Sources */, 37CC3F4C270CFE1700608308 /* PlayerQueueView.swift in Sources */,
3705B182267B4E4900704544 /* TrendingCategory.swift in Sources */, 3705B182267B4E4900704544 /* TrendingCategory.swift in Sources */,
3700155F271B12DD0049C794 /* SiestaConfiguration.swift in Sources */, 3700155F271B12DD0049C794 /* SiestaConfiguration.swift in Sources */,
@ -1372,7 +1363,6 @@
37CEE4C22677B697005A1EFE /* Stream.swift in Sources */, 37CEE4C22677B697005A1EFE /* Stream.swift in Sources */,
371F2F1B269B43D300E4A7AB /* NavigationModel.swift in Sources */, 371F2F1B269B43D300E4A7AB /* NavigationModel.swift in Sources */,
37001564271B1F250049C794 /* AccountsModel.swift in Sources */, 37001564271B1F250049C794 /* AccountsModel.swift in Sources */,
37FD43DF2704717F0073EE42 /* DefaultAccountHint.swift in Sources */,
3761ABFE26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */, 3761ABFE26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */,
37BA795026DC3E0E002A0235 /* Int+Format.swift in Sources */, 37BA795026DC3E0E002A0235 /* Int+Format.swift in Sources */,
3743CA4F270EFE3400E4D32B /* PlayerQueueRow.swift in Sources */, 3743CA4F270EFE3400E4D32B /* PlayerQueueRow.swift in Sources */,
@ -1443,7 +1433,6 @@
37BA794026DB8F97002A0235 /* ChannelVideosView.swift in Sources */, 37BA794026DB8F97002A0235 /* ChannelVideosView.swift in Sources */,
3711404026B206A6005B3555 /* SearchModel.swift in Sources */, 3711404026B206A6005B3555 /* SearchModel.swift in Sources */,
37484C2A26FC83FF00287258 /* AccountFormView.swift in Sources */, 37484C2A26FC83FF00287258 /* AccountFormView.swift in Sources */,
37484C2226FC83C400287258 /* AccountSettingsView.swift in Sources */,
37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */, 37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
373CFAEC26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */, 373CFAEC26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
37977584268922F600DD52A8 /* InvidiousAPI.swift in Sources */, 37977584268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
@ -1477,7 +1466,6 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
37FD43E02704717F0073EE42 /* DefaultAccountHint.swift in Sources */,
37AAF28026737550007FC770 /* SearchView.swift in Sources */, 37AAF28026737550007FC770 /* SearchView.swift in Sources */,
3788AC2D26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */, 3788AC2D26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */,
37EAD871267B9ED100D9E01B /* Segment.swift in Sources */, 37EAD871267B9ED100D9E01B /* Segment.swift in Sources */,

View File

@ -1,12 +1,22 @@
import Defaults import Defaults
import Foundation
extension Defaults.Keys { extension Defaults.Keys {
static let invidiousInstanceID = "default-invidious-instance"
static let pipedInstanceID = "default-piped-instance"
static let instances = Key<[Instance]>("instances", default: [ static let instances = Key<[Instance]>("instances", default: [
.init(app: .piped, name: "Public", url: "https://pipedapi.kavin.rocks"), .init(app: .piped, id: pipedInstanceID, name: "Public", url: "https://pipedapi.kavin.rocks"),
.init(app: .invidious, name: "Private", url: "https://invidious.home.arekf.net") .init(app: .invidious, id: invidiousInstanceID, name: "Private", url: "https://invidious.home.arekf.net")
]) ])
static let accounts = Key<[Instance.Account]>("accounts", default: []) static let accounts = Key<[Instance.Account]>("accounts", default: [
static let defaultAccountID = Key<String?>("defaultAccountID") .init(instanceID: invidiousInstanceID,
name: "arekf",
url: "https://invidious.home.arekf.net",
sid: "ki55SJbaQmm0bOxUWctGAQLYPQRgk-CXDPw5Dp4oBmI=")
])
static let lastAccountID = Key<Instance.Account.ID?>("lastAccountID")
static let lastInstanceID = Key<Instance.ID?>("lastInstanceID")
static let quality = Key<Stream.ResolutionSetting>("quality", default: .hd720pFirstThenBest) static let quality = Key<Stream.ResolutionSetting>("quality", default: .hd720pFirstThenBest)

View File

@ -4,24 +4,29 @@ import SwiftUI
struct AccountsMenuView: View { struct AccountsMenuView: View {
@EnvironmentObject<AccountsModel> private var model @EnvironmentObject<AccountsModel> private var model
@Default(.accounts) private var accounts
@Default(.instances) private var instances @Default(.instances) private var instances
var body: some View { var body: some View {
Menu { Menu {
ForEach(model.all, id: \.id) { account in ForEach(allAccounts, id: \.id) { account in
Button(accountButtonTitle(account: account)) { Button(accountButtonTitle(account: account)) {
model.setAccount(account) model.setCurrent(account)
} }
} }
} label: { } label: {
Label(model.account?.name ?? "Select Account", systemImage: "person.crop.circle") Label(model.current?.name ?? "Select Account", systemImage: "person.crop.circle")
.labelStyle(.titleAndIcon) .labelStyle(.titleAndIcon)
} }
.disabled(instances.isEmpty) .disabled(instances.isEmpty)
.transaction { t in t.animation = .none } .transaction { t in t.animation = .none }
} }
func accountButtonTitle(account: Instance.Account) -> String { private var allAccounts: [Instance.Account] {
accounts + instances.map(\.anonymousAccount)
}
private func accountButtonTitle(account: Instance.Account) -> String {
instances.count > 1 ? "\(account.description)\(account.instance.description)" : account.description instances.count > 1 ? "\(account.description)\(account.instance.description)" : account.description
} }
} }

View File

@ -62,8 +62,8 @@ struct AppSidebarNavigation: View {
.help( .help(
"Switch Instances and Accounts\n" + "Switch Instances and Accounts\n" +
"Current Instance: \n" + "Current Instance: \n" +
"\(accounts.account?.url ?? "Not Set")\n" + "\(accounts.current?.url ?? "Not Set")\n" +
"Current User: \(accounts.account?.description ?? "Not set")" "Current User: \(accounts.current?.description ?? "Not set")"
) )
} }
} }

View File

@ -85,15 +85,17 @@ struct ContentView: View {
SiestaLog.Category.enabled = .common SiestaLog.Category.enabled = .common
// TODO: Remove when piped supports videos information // TODO: Remove when piped supports videos information
if let account = instances.defaultAccount ?? if let account = accounts.lastUsed ??
accounts.all.first(where: { $0.instance.app == .invidious }) instances.lastUsed?.anonymousAccount ??
instances.all.first?.anonymousAccount
{ {
accounts.setAccount(account) accounts.setCurrent(account)
} }
if accounts.account.isNil { if accounts.current.isNil {
navigation.presentingWelcomeScreen = true navigation.presentingWelcomeScreen = true
} }
player.accounts = accounts player.accounts = accounts
playlists.accounts = accounts playlists.accounts = accounts
search.accounts = accounts search.accounts = accounts
@ -101,7 +103,7 @@ struct ContentView: View {
} }
func openWelcomeScreenIfAccountEmpty() { func openWelcomeScreenIfAccountEmpty() {
guard accounts.isEmpty else { guard Defaults[.instances].isEmpty else {
return return
} }

View File

@ -16,7 +16,6 @@ struct PearvidiousApp: App {
#if os(macOS) #if os(macOS)
Settings { Settings {
SettingsView() SettingsView()
.environmentObject(InvidiousAPI())
.environmentObject(InstancesModel()) .environmentObject(InstancesModel())
} }
#endif #endif

View File

@ -122,8 +122,7 @@ struct VideoPlayerView: View {
VStack(spacing: 10) { VStack(spacing: 10) {
#if !os(tvOS) #if !os(tvOS)
Image(systemName: "ticket") Image(systemName: "ticket")
.font(.system(size: 80)) .font(.system(size: 120))
Text("What are we watching next?")
#endif #endif
} }
Spacer() Spacer()

View File

@ -124,7 +124,7 @@ struct AccountFormView: View {
return return
} }
let account = instances.addAccount(instance: instance, name: name, sid: sid) let account = AccountsModel.add(instance: instance, name: name, sid: sid)
selectedAccount?.wrappedValue = account selectedAccount?.wrappedValue = account
dismiss() dismiss()

View File

@ -6,10 +6,11 @@ struct AccountsSettingsView: View {
@State private var accountsChanged = false @State private var accountsChanged = false
@State private var presentingAccountForm = false @State private var presentingAccountForm = false
@EnvironmentObject<AccountsModel> private var model
@EnvironmentObject<InstancesModel> private var instances @EnvironmentObject<InstancesModel> private var instances
var instance: Instance! { var instance: Instance! {
instances.find(instanceID) InstancesModel.find(instanceID)
} }
var body: some View { var body: some View {
@ -27,41 +28,18 @@ struct AccountsSettingsView: View {
var accounts: some View { var accounts: some View {
List { List {
Section(header: Text("Accounts"), footer: sectionFooter) { Section(header: Text("Accounts"), footer: sectionFooter) {
ForEach(instances.accounts(instanceID), id: \.self) { account in ForEach(InstancesModel.accounts(instanceID), id: \.self) { account in
#if os(iOS) #if os(tvOS)
HStack(spacing: 2) { Button(account.description) {}
Text(account.description) .contextMenu {
if instances.defaultAccount == account { Button("Remove", role: .destructive) { removeAccount(account) }
Text("— default") Button("Cancel", role: .cancel) {}
.foregroundColor(.secondary)
} }
}
.swipeActions(edge: .leading, allowsFullSwipe: true) {
if instances.defaultAccount != account {
Button("Make Default") { makeDefault(account) }
} else {
Button("Reset Default", action: resetDefaultAccount)
}
}
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button("Remove", role: .destructive) { removeAccount(account) }
}
#else #else
Button(action: { toggleDefault(account) }) { Text(account.description)
HStack(spacing: 2) { .swipeActions(edge: .trailing, allowsFullSwipe: false) {
Text(account.description) Button("Remove", role: .destructive) { removeAccount(account) }
if instances.defaultAccount == account {
Text("— default")
.foregroundColor(.secondary)
}
} }
}
.contextMenu {
Button("Toggle Default") { toggleDefault(account) }
Button("Remove", role: .destructive) { removeAccount(account) }
Button("Cancel", role: .cancel) {}
}
#endif #endif
} }
.redrawOn(change: accountsChanged) .redrawOn(change: accountsChanged)
@ -83,33 +61,15 @@ struct AccountsSettingsView: View {
private var sectionFooter: some View { private var sectionFooter: some View {
#if os(iOS) #if os(iOS)
Text("Swipe right to toggle default account, swipe left to remove") Text("Swipe to remove account")
#else #else
Text("Tap to toggle default account, tap and hold to remove") Text("Tap and hold to remove account")
.foregroundColor(.secondary) .foregroundColor(.secondary)
#endif #endif
} }
private func makeDefault(_ account: Instance.Account) {
instances.setDefaultAccount(account)
accountsChanged.toggle()
}
private func toggleDefault(_ account: Instance.Account) {
if account == instances.defaultAccount {
resetDefaultAccount()
} else {
makeDefault(account)
}
}
private func resetDefaultAccount() {
instances.resetDefaultAccount()
accountsChanged.toggle()
}
private func removeAccount(_ account: Instance.Account) { private func removeAccount(_ account: Instance.Account) {
instances.removeAccount(account) AccountsModel.remove(account)
accountsChanged.toggle() accountsChanged.toggle()
} }
} }

View File

@ -1,38 +0,0 @@
import Foundation
import SwiftUI
struct DefaultAccountHint: View {
@EnvironmentObject<InstancesModel> private var instancesModel
var body: some View {
Group {
if !instancesModel.defaultAccount.isNil {
VStack {
HStack(spacing: 2) {
hintText
.truncationMode(.middle)
.lineLimit(1)
}
}
} else {
Text("You have no default account set")
}
}
#if os(tvOS)
.foregroundColor(.gray)
#elseif os(macOS)
.font(.caption2)
.foregroundColor(.secondary)
#endif
}
var hintText: some View {
Group {
if let account = instancesModel.defaultAccount {
Text(
"**\(account.description)** account on instance **\(account.instance.shortDescription)** is your default."
)
}
}
}
}

View File

@ -146,7 +146,7 @@ struct InstanceFormView: View {
return return
} }
savedInstanceID = instancesModel.add(app: app, name: name, url: url).id savedInstanceID = InstancesModel.add(app: app, name: name, url: url).id
dismiss() dismiss()
} }

View File

@ -17,7 +17,7 @@ struct InstancesSettingsView: View {
var body: some View { var body: some View {
Group { Group {
Section(header: Text("Instances"), footer: DefaultAccountHint()) { Section(header: Text("Instances")) {
ForEach(instances) { instance in ForEach(instances) { instance in
Group { Group {
NavigationLink(instance.longDescription) { NavigationLink(instance.longDescription) {
@ -55,16 +55,12 @@ struct InstancesSettingsView: View {
private func removeInstanceButton(_ instance: Instance) -> some View { private func removeInstanceButton(_ instance: Instance) -> some View {
Button("Remove", role: .destructive) { Button("Remove", role: .destructive) {
if accounts.account?.instance == instance { if accounts.current?.instance == instance {
accounts.setAccount(nil) accounts.setCurrent(nil)
} }
instancesModel.remove(instance) InstancesModel.remove(instance)
} }
} }
private func resetDefaultAccount() {
instancesModel.resetDefaultAccount()
}
} }
struct InstancesSettingsView_Previews: PreviewProvider { struct InstancesSettingsView_Previews: PreviewProvider {

View File

@ -21,7 +21,7 @@ struct SubscriptionsView: View {
.onAppear { .onAppear {
loadResources() loadResources()
} }
.onChange(of: accounts.account) { _ in .onChange(of: accounts.current) { _ in
loadResources(force: true) loadResources(force: true)
} }
} }

View File

@ -39,6 +39,10 @@ struct VideoContextMenuView: View {
removeFromPlaylistButton(playlistID: id) removeFromPlaylistButton(playlistID: id)
} }
} }
#if os(tvOS)
Button("Cancel", role: .cancel) {}
#endif
} }
var playNowButton: some View { var playNowButton: some View {

View File

@ -1,3 +1,4 @@
import Defaults
import SwiftUI import SwiftUI
struct WelcomeScreen: View { struct WelcomeScreen: View {
@ -6,6 +7,8 @@ struct WelcomeScreen: View {
@EnvironmentObject<AccountsModel> private var accounts @EnvironmentObject<AccountsModel> private var accounts
@EnvironmentObject<NavigationModel> private var navigation @EnvironmentObject<NavigationModel> private var navigation
@Default(.accounts) private var allAccounts
var body: some View { var body: some View {
VStack { VStack {
Spacer() Spacer()
@ -14,7 +17,7 @@ struct WelcomeScreen: View {
.font(.largeTitle) .font(.largeTitle)
.padding(.bottom, 10) .padding(.bottom, 10)
if accounts.all.isEmpty { if allAccounts.isEmpty {
Text("To start, configure your Instances in Settings") Text("To start, configure your Instances in Settings")
.foregroundColor(.secondary) .foregroundColor(.secondary)
} else { } else {
@ -28,12 +31,12 @@ struct WelcomeScreen: View {
} label: { } label: {
Text("Start") Text("Start")
} }
.opacity(accounts.account.isNil ? 0 : 1) .opacity(accounts.current.isNil ? 0 : 1)
.disabled(accounts.account.isNil) .disabled(accounts.current.isNil)
#else #else
AccountsMenuView() AccountsMenuView()
.onChange(of: accounts.account) { _ in .onChange(of: accounts.current) { _ in
dismiss() dismiss()
} }
#if os(macOS) #if os(macOS)

View File

@ -21,7 +21,7 @@ struct WatchNowSection: View {
resource.addObserver(store) resource.addObserver(store)
resource.loadIfNeeded() resource.loadIfNeeded()
} }
.onChange(of: accounts.account) { _ in .onChange(of: accounts.current) { _ in
resource.load() resource.load()
} }
} }

View File

@ -12,7 +12,7 @@ struct WatchNowView: View {
var body: some View { var body: some View {
PlayerControlsView { PlayerControlsView {
ScrollView(.vertical, showsIndicators: false) { ScrollView(.vertical, showsIndicators: false) {
if !accounts.account.isNil { if !accounts.current.isNil {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
if api.signedIn { if api.signedIn {
WatchNowSection(resource: api.feed, label: "Subscriptions") WatchNowSection(resource: api.feed, label: "Subscriptions")

View File

@ -1,54 +0,0 @@
import Defaults
import SwiftUI
struct AccountSettingsView: View {
let account: Instance.Account
@Binding var selectedAccount: Instance.Account?
@State private var presentingRemovalConfirmationDialog = false
@EnvironmentObject<InstancesModel> private var instances
var body: some View {
HStack {
HStack(spacing: 2) {
Text(account.description)
if instances.defaultAccount == account {
Text("— default")
.foregroundColor(.secondary)
}
}
Spacer()
HStack {
if instances.defaultAccount != account {
Button("Make default", action: makeDefault)
} else {
Button("Reset default", action: resetDefault)
}
Button("Remove", role: .destructive) {
presentingRemovalConfirmationDialog = true
}
.confirmationDialog(
"Are you sure you want to remove \(account.description) account?",
isPresented: $presentingRemovalConfirmationDialog
) {
Button("Remove", role: .destructive) {
instances.removeAccount(account)
}
}
.foregroundColor(.red)
}
.opacity(account == selectedAccount ? 1 : 0)
}
}
private func makeDefault() {
instances.setDefaultAccount(account)
}
private func resetDefault() {
instances.resetDefaultAccount()
}
}

View File

@ -9,7 +9,8 @@ struct InstancesSettingsView: View {
@State private var presentingInstanceForm = false @State private var presentingInstanceForm = false
@State private var savedFormInstanceID: Instance.ID? @State private var savedFormInstanceID: Instance.ID?
@State private var presentingConfirmationDialog = false @State private var presentingAccountRemovalConfirmation = false
@State private var presentingInstanceRemovalConfirmation = false
@EnvironmentObject<AccountsModel> private var accounts @EnvironmentObject<AccountsModel> private var accounts
@EnvironmentObject<InstancesModel> private var model @EnvironmentObject<InstancesModel> private var model
@ -41,8 +42,26 @@ struct InstancesSettingsView: View {
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
ForEach(selectedInstanceAccounts) { account in ForEach(selectedInstanceAccounts) { account in
AccountSettingsView(account: account, selectedAccount: $selectedAccount) HStack {
.tag(account) Text(account.description)
Spacer()
Button("Remove", role: .destructive) {
presentingAccountRemovalConfirmation = true
}
.foregroundColor(.red)
.opacity(account == selectedAccount ? 1 : 0)
}
.tag(account)
}
}
.confirmationDialog(
"Are you sure you want to remove \(selectedAccount?.description ?? "") account?",
isPresented: $presentingAccountRemovalConfirmation
) {
Button("Remove", role: .destructive) {
AccountsModel.remove(selectedAccount!)
} }
} }
.listStyle(.inset(alternatesRowBackgrounds: true)) .listStyle(.inset(alternatesRowBackgrounds: true))
@ -58,28 +77,27 @@ struct InstancesSettingsView: View {
if selectedInstance != nil { if selectedInstance != nil {
HStack { HStack {
if selectedInstance.supportsAccounts { Button("Add Account...") {
Button("Add Account...") { selectedAccount = nil
selectedAccount = nil presentingAccountForm = true
presentingAccountForm = true
}
} }
.disabled(!selectedInstance.supportsAccounts)
Spacer() Spacer()
Button("Remove Instance", role: .destructive) { Button("Remove Instance", role: .destructive) {
presentingConfirmationDialog = true presentingInstanceRemovalConfirmation = true
} }
.confirmationDialog( .confirmationDialog(
"Are you sure you want to remove \(selectedInstance!.longDescription) instance?", "Are you sure you want to remove \(selectedInstance!.longDescription) instance?",
isPresented: $presentingConfirmationDialog isPresented: $presentingInstanceRemovalConfirmation
) { ) {
Button("Remove Instance", role: .destructive) { Button("Remove Instance", role: .destructive) {
if accounts.account?.instance == selectedInstance { if accounts.current?.instance == selectedInstance {
accounts.setAccount(nil) accounts.setCurrent(nil)
} }
model.remove(selectedInstance!) InstancesModel.remove(selectedInstance!)
selectedInstanceID = instances.last?.id selectedInstanceID = instances.last?.id
} }
} }
@ -91,9 +109,6 @@ struct InstancesSettingsView: View {
Button("Add Instance...") { Button("Add Instance...") {
presentingInstanceForm = true presentingInstanceForm = true
} }
DefaultAccountHint()
.padding(.top, 10)
} }
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
@ -116,7 +131,7 @@ struct InstancesSettingsView: View {
} }
var selectedInstance: Instance! { var selectedInstance: Instance! {
model.find(selectedInstanceID) InstancesModel.find(selectedInstanceID)
} }
private var selectedInstanceAccounts: [Instance.Account] { private var selectedInstanceAccounts: [Instance.Account] {
@ -124,7 +139,7 @@ struct InstancesSettingsView: View {
return [] return []
} }
return model.accounts(selectedInstanceID) return InstancesModel.accounts(selectedInstanceID)
} }
} }

View File

@ -6,22 +6,23 @@ struct AccountSelectionView: View {
var showHeader = true var showHeader = true
@EnvironmentObject<InstancesModel> private var instancesModel @EnvironmentObject<InstancesModel> private var instancesModel
@EnvironmentObject<AccountsModel> private var accounts @EnvironmentObject<AccountsModel> private var accountsModel
@Default(.accounts) private var accounts
@Default(.instances) private var instances @Default(.instances) private var instances
var body: some View { var body: some View {
Section(header: Text(showHeader ? "Current Account" : "")) { Section(header: Text(showHeader ? "Current Account" : "")) {
Button(accountButtonTitle(account: accounts.account)) { Button(accountButtonTitle(account: accountsModel.current)) {
if let account = nextAccount { if let account = nextAccount {
accounts.setAccount(account) accountsModel.setCurrent(account)
} }
} }
.disabled(instances.isEmpty) .disabled(instances.isEmpty)
.contextMenu { .contextMenu {
ForEach(accounts.all) { account in ForEach(allAccounts) { account in
Button(accountButtonTitle(account: account)) { Button(accountButtonTitle(account: account)) {
accounts.setAccount(account) accountsModel.setCurrent(account)
} }
} }
@ -31,8 +32,12 @@ struct AccountSelectionView: View {
.id(UUID()) .id(UUID())
} }
var allAccounts: [Instance.Account] {
accounts + instances.map(\.anonymousAccount)
}
private var nextAccount: Instance.Account? { private var nextAccount: Instance.Account? {
accounts.all.next(after: accounts.account) allAccounts.next(after: accountsModel.current)
} }
func accountButtonTitle(account: Instance.Account! = nil) -> String { func accountButtonTitle(account: Instance.Account! = nil) -> String {