mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 21:43:41 +00:00
parent
08ed810b9e
commit
2f2fd67860
@ -35,7 +35,6 @@ struct FixtureEnvironmentObjectsModifier: ViewModifier {
|
|||||||
let api = InvidiousAPI()
|
let api = InvidiousAPI()
|
||||||
|
|
||||||
api.validInstance = true
|
api.validInstance = true
|
||||||
api.signedIn = true
|
|
||||||
|
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,12 @@ struct Account: Defaults.Serializable, Hashable, Identifiable {
|
|||||||
static var bridge = AccountsBridge()
|
static var bridge = AccountsBridge()
|
||||||
|
|
||||||
let id: String
|
let id: String
|
||||||
let app: VideosApp
|
var app: VideosApp?
|
||||||
let instanceID: String?
|
let instanceID: String?
|
||||||
var name: String?
|
var name: String?
|
||||||
let url: String
|
let url: String
|
||||||
let username: String
|
var username: String
|
||||||
let password: String?
|
var password: String?
|
||||||
var token: String?
|
|
||||||
let anonymous: Bool
|
let anonymous: Bool
|
||||||
let country: String?
|
let country: String?
|
||||||
let region: String?
|
let region: String?
|
||||||
@ -24,7 +23,6 @@ struct Account: Defaults.Serializable, Hashable, Identifiable {
|
|||||||
url: String? = nil,
|
url: String? = nil,
|
||||||
username: String? = nil,
|
username: String? = nil,
|
||||||
password: String? = nil,
|
password: String? = nil,
|
||||||
token: String? = nil,
|
|
||||||
anonymous: Bool = false,
|
anonymous: Bool = false,
|
||||||
country: String? = nil,
|
country: String? = nil,
|
||||||
region: String? = nil
|
region: String? = nil
|
||||||
@ -32,19 +30,26 @@ struct Account: Defaults.Serializable, Hashable, Identifiable {
|
|||||||
self.anonymous = anonymous
|
self.anonymous = anonymous
|
||||||
|
|
||||||
self.id = id ?? (anonymous ? "anonymous-\(instanceID ?? url ?? UUID().uuidString)" : UUID().uuidString)
|
self.id = id ?? (anonymous ? "anonymous-\(instanceID ?? url ?? UUID().uuidString)" : UUID().uuidString)
|
||||||
self.app = app ?? .invidious
|
|
||||||
self.instanceID = instanceID
|
self.instanceID = instanceID
|
||||||
self.name = name
|
self.name = name
|
||||||
self.url = url ?? ""
|
self.url = url ?? ""
|
||||||
self.username = username ?? ""
|
self.username = username ?? ""
|
||||||
self.token = token
|
|
||||||
self.password = password ?? ""
|
self.password = password ?? ""
|
||||||
self.country = country
|
self.country = country
|
||||||
self.region = region
|
self.region = region
|
||||||
|
self.app = app ?? instance.app
|
||||||
|
}
|
||||||
|
|
||||||
|
var token: String? {
|
||||||
|
KeychainModel.shared.getAccountKey(self, "token")
|
||||||
|
}
|
||||||
|
|
||||||
|
var credentials: (String?, String?) {
|
||||||
|
AccountsModel.getCredentials(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
var instance: Instance! {
|
var instance: Instance! {
|
||||||
Defaults[.instances].first { $0.id == instanceID } ?? Instance(app: app, name: url, apiURL: url)
|
Defaults[.instances].first { $0.id == instanceID } ?? Instance(app: app ?? .invidious, name: url, apiURL: url)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isPublic: Bool {
|
var isPublic: Bool {
|
||||||
@ -52,8 +57,12 @@ struct Account: Defaults.Serializable, Hashable, Identifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var shortUsername: String {
|
var shortUsername: String {
|
||||||
guard username.count > 10 else {
|
let (username, _) = credentials
|
||||||
return username
|
|
||||||
|
guard let username = username,
|
||||||
|
username.count > 10
|
||||||
|
else {
|
||||||
|
return username ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = username.index(username.startIndex, offsetBy: 11)
|
let index = username.index(username.startIndex, offsetBy: 11)
|
||||||
@ -61,7 +70,11 @@ struct Account: Defaults.Serializable, Hashable, Identifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
(name != nil && name!.isEmpty) ? shortUsername : name!
|
guard let name = name, !name.isEmpty else {
|
||||||
|
return shortUsername
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
func hash(into hasher: inout Hasher) {
|
||||||
|
@ -43,14 +43,6 @@ final class AccountValidator: Service {
|
|||||||
$0.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"])
|
$0.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"])
|
||||||
}
|
}
|
||||||
|
|
||||||
configure("/api/v1/auth/feed", requestMethods: [.get]) {
|
|
||||||
guard self.account != nil else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
$0.headers["Cookie"] = self.invidiousCookieHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
configure("/login", requestMethods: [.post]) {
|
configure("/login", requestMethods: [.post]) {
|
||||||
$0.headers["Content-Type"] = "application/json"
|
$0.headers["Content-Type"] = "application/json"
|
||||||
}
|
}
|
||||||
@ -167,7 +159,8 @@ final class AccountValidator: Service {
|
|||||||
var accountRequest: Request? {
|
var accountRequest: Request? {
|
||||||
switch app.wrappedValue {
|
switch app.wrappedValue {
|
||||||
case .invidious:
|
case .invidious:
|
||||||
return feed.load()
|
guard let password = account.password else { return nil }
|
||||||
|
return login.request(.post, urlEncoded: ["email": account.username, "password": password])
|
||||||
case .piped:
|
case .piped:
|
||||||
return login.request(.post, json: ["username": account.username, "password": account.password])
|
return login.request(.post, json: ["username": account.username, "password": account.password])
|
||||||
default:
|
default:
|
||||||
@ -184,18 +177,10 @@ final class AccountValidator: Service {
|
|||||||
error?.wrappedValue = nil
|
error?.wrappedValue = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var invidiousCookieHeader: String {
|
|
||||||
"SID=\(account.username)"
|
|
||||||
}
|
|
||||||
|
|
||||||
var login: Resource {
|
var login: Resource {
|
||||||
resource("/login")
|
resource("/login")
|
||||||
}
|
}
|
||||||
|
|
||||||
var feed: Resource {
|
|
||||||
resource("/api/v1/auth/feed")
|
|
||||||
}
|
|
||||||
|
|
||||||
var videoResourceBasePath: String {
|
var videoResourceBasePath: String {
|
||||||
app.wrappedValue == .invidious ? "/api/v1/videos" : "/streams"
|
app.wrappedValue == .invidious ? "/api/v1/videos" : "/streams"
|
||||||
}
|
}
|
||||||
|
@ -93,22 +93,47 @@ final class AccountsModel: ObservableObject {
|
|||||||
Defaults[.accounts].first { $0.id == id }
|
Defaults[.accounts].first { $0.id == id }
|
||||||
}
|
}
|
||||||
|
|
||||||
static func add(instance: Instance, name: String, username: String, password: String? = nil) -> Account {
|
static func add(instance: Instance, name: String, username: String, password: String) -> Account {
|
||||||
let account = Account(
|
let account = Account(instanceID: instance.id, name: name, url: instance.apiURL)
|
||||||
instanceID: instance.id,
|
|
||||||
name: name,
|
|
||||||
url: instance.apiURL,
|
|
||||||
username: username,
|
|
||||||
password: password
|
|
||||||
)
|
|
||||||
Defaults[.accounts].append(account)
|
Defaults[.accounts].append(account)
|
||||||
|
|
||||||
|
setCredentials(account, username: username, password: password)
|
||||||
|
|
||||||
return account
|
return account
|
||||||
}
|
}
|
||||||
|
|
||||||
static func remove(_ account: Account) {
|
static func remove(_ account: Account) {
|
||||||
if let accountIndex = Defaults[.accounts].firstIndex(where: { $0.id == account.id }) {
|
if let accountIndex = Defaults[.accounts].firstIndex(where: { $0.id == account.id }) {
|
||||||
|
let account = Defaults[.accounts][accountIndex]
|
||||||
|
KeychainModel.shared.removeAccountKeys(account)
|
||||||
Defaults[.accounts].remove(at: accountIndex)
|
Defaults[.accounts].remove(at: accountIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func setToken(_ account: Account, _ token: String) {
|
||||||
|
KeychainModel.shared.updateAccountKey(account, "token", token)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func setCredentials(_ account: Account, username: String, password: String) {
|
||||||
|
KeychainModel.shared.updateAccountKey(account, "username", username)
|
||||||
|
KeychainModel.shared.updateAccountKey(account, "password", password)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func getCredentials(_ account: Account) -> (String?, String?) {
|
||||||
|
(
|
||||||
|
KeychainModel.shared.getAccountKey(account, "username"),
|
||||||
|
KeychainModel.shared.getAccountKey(account, "password")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func removeDefaultsCredentials(_ account: Account) {
|
||||||
|
if let accountIndex = Defaults[.accounts].firstIndex(where: { $0.id == account.id }) {
|
||||||
|
var account = Defaults[.accounts][accountIndex]
|
||||||
|
account.name = ""
|
||||||
|
account.username = ""
|
||||||
|
account.password = nil
|
||||||
|
|
||||||
|
Defaults[.accounts][accountIndex] = account
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import Alamofire
|
||||||
import AVKit
|
import AVKit
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
@ -10,7 +11,12 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
@Published var account: Account!
|
@Published var account: Account!
|
||||||
|
|
||||||
@Published var validInstance = true
|
@Published var validInstance = true
|
||||||
@Published var signedIn = false
|
|
||||||
|
var signedIn: Bool {
|
||||||
|
guard let account = account else { return false }
|
||||||
|
|
||||||
|
return !account.anonymous && !(account.token?.isEmpty ?? true)
|
||||||
|
}
|
||||||
|
|
||||||
init(account: Account? = nil) {
|
init(account: Account? = nil) {
|
||||||
super.init()
|
super.init()
|
||||||
@ -25,7 +31,6 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
|
|
||||||
func setAccount(_ account: Account) {
|
func setAccount(_ account: Account) {
|
||||||
self.account = account
|
self.account = account
|
||||||
signedIn = false
|
|
||||||
|
|
||||||
validInstance = account.anonymous
|
validInstance = account.anonymous
|
||||||
|
|
||||||
@ -57,28 +62,23 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateSID() {
|
func validateSID() {
|
||||||
guard !signedIn else {
|
guard signedIn, !(account.token?.isEmpty ?? true) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
feed?
|
feed?
|
||||||
.load()
|
.load()
|
||||||
.onSuccess { _ in
|
.onFailure { _ in
|
||||||
self.signedIn = true
|
self.updateToken(force: true)
|
||||||
}
|
|
||||||
.onFailure { requestError in
|
|
||||||
self.signedIn = false
|
|
||||||
NavigationModel.shared.presentAlert(
|
|
||||||
title: "Could not connect with your account",
|
|
||||||
message: "\(requestError.httpStatusCode ?? -1) - \(requestError.userMessage)\nIf this issue persists, try removing and adding your account again in Settings."
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func configure() {
|
func configure() {
|
||||||
|
invalidateConfiguration()
|
||||||
|
|
||||||
configure {
|
configure {
|
||||||
if !self.account.username.isEmpty {
|
if let cookie = self.cookieHeader {
|
||||||
$0.headers["Cookie"] = self.cookieHeader
|
$0.headers["Cookie"] = cookie
|
||||||
}
|
}
|
||||||
$0.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"])
|
$0.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"])
|
||||||
}
|
}
|
||||||
@ -170,6 +170,71 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
|
|
||||||
return CommentsPage(comments: comments, nextPage: nextPage, disabled: disabled)
|
return CommentsPage(comments: comments, nextPage: nextPage, disabled: disabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateToken(force: Bool = false) {
|
||||||
|
let (username, password) = AccountsModel.getCredentials(account)
|
||||||
|
guard !account.anonymous,
|
||||||
|
(account.token?.isEmpty ?? true) || force
|
||||||
|
else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let username = username,
|
||||||
|
let password = password,
|
||||||
|
!username.isEmpty,
|
||||||
|
!password.isEmpty
|
||||||
|
else {
|
||||||
|
NavigationModel.shared.presentAlert(
|
||||||
|
title: "Account Error",
|
||||||
|
message: "Remove and add your account again in Settings."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentTokenUpdateFailedAlert: (AFDataResponse<Data?>?, String?) -> Void = { response, message in
|
||||||
|
NavigationModel.shared.presentAlert(
|
||||||
|
title: "Account Error",
|
||||||
|
message: message ?? "\(response?.response?.statusCode ?? -1) - \(response?.error?.errorDescription ?? "unknown")\nIf this issue persists, try removing and adding your account again in Settings."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AF
|
||||||
|
.request(login.url, method: .post, parameters: ["email": username, "password": password], encoding: URLEncoding.default)
|
||||||
|
.redirect(using: .doNotFollow)
|
||||||
|
.response { response in
|
||||||
|
guard let headers = response.response?.headers,
|
||||||
|
let cookies = headers["Set-Cookie"]
|
||||||
|
else {
|
||||||
|
presentTokenUpdateFailedAlert(response, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let sidRegex = #"SID=(?<sid>[^;]*);"#
|
||||||
|
guard let sidRegex = try? NSRegularExpression(pattern: sidRegex),
|
||||||
|
let match = sidRegex.matches(in: cookies, range: NSRange(cookies.startIndex..., in: cookies)).first
|
||||||
|
else {
|
||||||
|
presentTokenUpdateFailedAlert(nil, "Could not extract SID from received cookies: \(cookies)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let matchRange = match.range(withName: "sid")
|
||||||
|
|
||||||
|
if let substringRange = Range(matchRange, in: cookies) {
|
||||||
|
print("updating invidious token")
|
||||||
|
let sid = String(cookies[substringRange])
|
||||||
|
AccountsModel.setToken(self.account, sid)
|
||||||
|
self.configure()
|
||||||
|
} else {
|
||||||
|
presentTokenUpdateFailedAlert(nil, "Could not extract SID from received cookies: \(cookies)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var login: Resource {
|
||||||
|
resource(baseURL: account.url, path: "login")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func pathPattern(_ path: String) -> String {
|
private func pathPattern(_ path: String) -> String {
|
||||||
@ -180,8 +245,9 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
"\(Self.basePath)/\(path)"
|
"\(Self.basePath)/\(path)"
|
||||||
}
|
}
|
||||||
|
|
||||||
private var cookieHeader: String {
|
private var cookieHeader: String? {
|
||||||
"SID=\(account.username)"
|
guard let token = account?.token, !token.isEmpty else { return nil }
|
||||||
|
return "SID=\(token)"
|
||||||
}
|
}
|
||||||
|
|
||||||
var popular: Resource? {
|
var popular: Resource? {
|
||||||
|
@ -109,23 +109,33 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateToken() {
|
func updateToken() {
|
||||||
guard !account.anonymous else {
|
let (username, password) = AccountsModel.getCredentials(account)
|
||||||
|
|
||||||
|
guard !account.anonymous,
|
||||||
|
let username = username,
|
||||||
|
let password = password
|
||||||
|
else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
account.token = nil
|
|
||||||
|
|
||||||
login.request(
|
login.request(
|
||||||
.post,
|
.post,
|
||||||
json: ["username": account.username, "password": account.password]
|
json: ["username": username, "password": password]
|
||||||
)
|
)
|
||||||
.onSuccess { response in
|
.onSuccess { response in
|
||||||
self.account.token = response.json.dictionaryValue["token"]?.string ?? ""
|
let token = response.json.dictionaryValue["token"]?.string ?? ""
|
||||||
if let error = response.json.dictionaryValue["error"]?.string {
|
if let error = response.json.dictionaryValue["error"]?.string {
|
||||||
NavigationModel.shared.presentAlert(
|
NavigationModel.shared.presentAlert(
|
||||||
title: "Could not connect with your account",
|
title: "Account Error",
|
||||||
message: error
|
message: error
|
||||||
)
|
)
|
||||||
|
} else if !token.isEmpty {
|
||||||
|
AccountsModel.setToken(self.account, token)
|
||||||
|
} else {
|
||||||
|
NavigationModel.shared.presentAlert(
|
||||||
|
title: "Account Error",
|
||||||
|
message: "Could not update your token."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.configure()
|
self.configure()
|
||||||
|
@ -11,10 +11,6 @@ enum VideosApp: String, CaseIterable {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
var accountsUsePassword: Bool {
|
|
||||||
self == .piped
|
|
||||||
}
|
|
||||||
|
|
||||||
var supportsPopular: Bool {
|
var supportsPopular: Bool {
|
||||||
self == .invidious
|
self == .invidious
|
||||||
}
|
}
|
||||||
|
26
Model/KeychainModel.swift
Normal file
26
Model/KeychainModel.swift
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import Foundation
|
||||||
|
import KeychainAccess
|
||||||
|
|
||||||
|
struct KeychainModel {
|
||||||
|
static var shared = KeychainModel()
|
||||||
|
|
||||||
|
var keychain = Keychain(service: "stream.yattee.app")
|
||||||
|
|
||||||
|
func updateAccountKey(_ account: Account, _ key: String, _ value: String) {
|
||||||
|
keychain[accountKey(account, key)] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAccountKey(_ account: Account, _ key: String) -> String? {
|
||||||
|
keychain[accountKey(account, key)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func accountKey(_ account: Account, _ key: String) -> String {
|
||||||
|
"\(account.id)-\(key)"
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeAccountKeys(_ account: Account) {
|
||||||
|
try? keychain.remove(accountKey(account, "token"))
|
||||||
|
try? keychain.remove(accountKey(account, "username"))
|
||||||
|
try? keychain.remove(accountKey(account, "password"))
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ extension Defaults.Keys {
|
|||||||
#endif
|
#endif
|
||||||
static let accountPickerDisplaysUsername = Key<Bool>("accountPickerDisplaysUsername", default: accountPickerDisplaysUsernameDefault)
|
static let accountPickerDisplaysUsername = Key<Bool>("accountPickerDisplaysUsername", default: accountPickerDisplaysUsernameDefault)
|
||||||
#endif
|
#endif
|
||||||
|
static let accountPickerDisplaysAnonymousAccounts = Key<Bool>("accountPickerDisplaysAnonymousAccounts", default: true)
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
static let lockPortraitWhenBrowsing = Key<Bool>("lockPortraitWhenBrowsing", default: UIDevice.current.userInterfaceIdiom == .phone)
|
static let lockPortraitWhenBrowsing = Key<Bool>("lockPortraitWhenBrowsing", default: UIDevice.current.userInterfaceIdiom == .phone)
|
||||||
#endif
|
#endif
|
||||||
|
@ -7,6 +7,7 @@ struct AccountsMenuView: View {
|
|||||||
@Default(.accounts) private var accounts
|
@Default(.accounts) private var accounts
|
||||||
@Default(.instances) private var instances
|
@Default(.instances) private var instances
|
||||||
@Default(.accountPickerDisplaysUsername) private var accountPickerDisplaysUsername
|
@Default(.accountPickerDisplaysUsername) private var accountPickerDisplaysUsername
|
||||||
|
@Default(.accountPickerDisplaysAnonymousAccounts) private var accountPickerDisplaysAnonymousAccounts
|
||||||
|
|
||||||
@ViewBuilder var body: some View {
|
@ViewBuilder var body: some View {
|
||||||
if !instances.isEmpty {
|
if !instances.isEmpty {
|
||||||
@ -48,7 +49,8 @@ struct AccountsMenuView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var allAccounts: [Account] {
|
private var allAccounts: [Account] {
|
||||||
accounts + instances.map(\.anonymousAccount) + [model.publicAccount].compactMap { $0 }
|
let anonymousAccounts = accountPickerDisplaysAnonymousAccounts ? instances.map(\.anonymousAccount) : []
|
||||||
|
return accounts + anonymousAccounts + [model.publicAccount].compactMap { $0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func accountButtonTitle(account: Account) -> String {
|
private func accountButtonTitle(account: Account) -> String {
|
||||||
|
@ -46,11 +46,14 @@ struct ContentView: View {
|
|||||||
.environmentObject(settings)
|
.environmentObject(settings)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
.onChange(of: accounts.current) { _ in
|
||||||
|
subscriptions.load(force: true)
|
||||||
|
playlists.load(force: true)
|
||||||
|
}
|
||||||
.onChange(of: accounts.signedIn) { _ in
|
.onChange(of: accounts.signedIn) { _ in
|
||||||
subscriptions.load(force: true)
|
subscriptions.load(force: true)
|
||||||
playlists.load(force: true)
|
playlists.load(force: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
.environmentObject(accounts)
|
.environmentObject(accounts)
|
||||||
.environmentObject(comments)
|
.environmentObject(comments)
|
||||||
.environmentObject(instances)
|
.environmentObject(instances)
|
||||||
|
@ -63,10 +63,6 @@ struct AccountForm: View {
|
|||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
helpButton
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
formFields
|
formFields
|
||||||
@ -76,33 +72,10 @@ struct AccountForm: View {
|
|||||||
.onChange(of: password) { _ in validate() }
|
.onChange(of: password) { _ in validate() }
|
||||||
}
|
}
|
||||||
|
|
||||||
var helpButton: some View {
|
|
||||||
Group {
|
|
||||||
if instance.app == .invidious {
|
|
||||||
Button {
|
|
||||||
openURL(URL(string: "https://github.com/yattee/yattee/wiki/Adding-Invidious-instance-and-account")!)
|
|
||||||
} label: {
|
|
||||||
Label("How to add Invidious account?", systemImage: "questionmark.circle")
|
|
||||||
#if os(macOS)
|
|
||||||
.help("How to add Invidious account?")
|
|
||||||
.labelStyle(.iconOnly)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var formFields: some View {
|
var formFields: some View {
|
||||||
Group {
|
Group {
|
||||||
if !instance.app.accountsUsePassword {
|
TextField("Username", text: $username)
|
||||||
TextField("Name", text: $name)
|
SecureField("Password", text: $password)
|
||||||
}
|
|
||||||
|
|
||||||
TextField(usernamePrompt, text: $username)
|
|
||||||
|
|
||||||
if instance.app.accountsUsePassword {
|
|
||||||
SecureField("Password", text: $password)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,10 +100,6 @@ struct AccountForm: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
#if os(macOS)
|
|
||||||
helpButton
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Button("Save", action: submitForm)
|
Button("Save", action: submitForm)
|
||||||
.disabled(!isValid)
|
.disabled(!isValid)
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
@ -148,9 +117,7 @@ struct AccountForm: View {
|
|||||||
isValid = false
|
isValid = false
|
||||||
validationDebounce.invalidate()
|
validationDebounce.invalidate()
|
||||||
|
|
||||||
let passwordIsValid = instance.app.accountsUsePassword ? !password.isEmpty : true
|
guard !username.isEmpty, !password.isEmpty else {
|
||||||
|
|
||||||
guard !username.isEmpty, passwordIsValid else {
|
|
||||||
validator.reset()
|
validator.reset()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ struct BrowsingSettings: View {
|
|||||||
@Default(.accountPickerDisplaysUsername) private var accountPickerDisplaysUsername
|
@Default(.accountPickerDisplaysUsername) private var accountPickerDisplaysUsername
|
||||||
@Default(.roundedThumbnails) private var roundedThumbnails
|
@Default(.roundedThumbnails) private var roundedThumbnails
|
||||||
#endif
|
#endif
|
||||||
|
@Default(.accountPickerDisplaysAnonymousAccounts) private var accountPickerDisplaysAnonymousAccounts
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@Default(.lockPortraitWhenBrowsing) private var lockPortraitWhenBrowsing
|
@Default(.lockPortraitWhenBrowsing) private var lockPortraitWhenBrowsing
|
||||||
#endif
|
#endif
|
||||||
@ -37,9 +38,7 @@ struct BrowsingSettings: View {
|
|||||||
|
|
||||||
private var sections: some View {
|
private var sections: some View {
|
||||||
Group {
|
Group {
|
||||||
#if !os(tvOS)
|
interfaceSettings
|
||||||
interfaceSettings
|
|
||||||
#endif
|
|
||||||
thumbnailsSettings
|
thumbnailsSettings
|
||||||
visibleSectionsSettings
|
visibleSectionsSettings
|
||||||
}
|
}
|
||||||
@ -61,6 +60,8 @@ struct BrowsingSettings: View {
|
|||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
Toggle("Show account username", isOn: $accountPickerDisplaysUsername)
|
Toggle("Show account username", isOn: $accountPickerDisplaysUsername)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Toggle("Show anonymous accounts", isOn: $accountPickerDisplaysAnonymousAccounts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,11 +225,11 @@ struct SettingsView: View {
|
|||||||
private var windowHeight: Double {
|
private var windowHeight: Double {
|
||||||
switch selection {
|
switch selection {
|
||||||
case .browsing:
|
case .browsing:
|
||||||
return 390
|
return 400
|
||||||
case .player:
|
case .player:
|
||||||
return 420
|
return 420
|
||||||
case .quality:
|
case .quality:
|
||||||
return 400
|
return 420
|
||||||
case .history:
|
case .history:
|
||||||
return 480
|
return 480
|
||||||
case .sponsorBlock:
|
case .sponsorBlock:
|
||||||
|
@ -19,6 +19,7 @@ struct OpenSettingsButton: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label("Open Settings", systemImage: "gearshape.2")
|
Label("Open Settings", systemImage: "gearshape.2")
|
||||||
}
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
|
||||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||||
button
|
button
|
||||||
|
@ -170,6 +170,8 @@ struct YatteeApp: App {
|
|||||||
SDImageCodersManager.shared.addCoder(SDImageWebPCoder.shared)
|
SDImageCodersManager.shared.addCoder(SDImageWebPCoder.shared)
|
||||||
SDWebImageManager.defaultImageCache = PINCache(name: "stream.yattee.app")
|
SDWebImageManager.defaultImageCache = PINCache(name: "stream.yattee.app")
|
||||||
|
|
||||||
|
migrateAccounts()
|
||||||
|
|
||||||
if !Defaults[.lastAccountIsPublic] {
|
if !Defaults[.lastAccountIsPublic] {
|
||||||
accounts.configureAccount()
|
accounts.configureAccount()
|
||||||
}
|
}
|
||||||
@ -246,4 +248,28 @@ struct YatteeApp: App {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateAccounts() {
|
||||||
|
Defaults[.accounts].forEach { account in
|
||||||
|
if !account.username.isEmpty || !(account.password?.isEmpty ?? true) || !(account.name?.isEmpty ?? true) {
|
||||||
|
print("Account needs migration: \(account.description)")
|
||||||
|
if account.app == .invidious {
|
||||||
|
if let name = account.name, !name.isEmpty {
|
||||||
|
AccountsModel.setCredentials(account, username: name, password: "")
|
||||||
|
}
|
||||||
|
if !account.username.isEmpty {
|
||||||
|
AccountsModel.setToken(account, account.username)
|
||||||
|
}
|
||||||
|
} else if account.app == .piped,
|
||||||
|
!account.username.isEmpty,
|
||||||
|
let password = account.password,
|
||||||
|
!password.isEmpty
|
||||||
|
{
|
||||||
|
AccountsModel.setCredentials(account, username: account.username, password: password)
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountsModel.removeDefaultsCredentials(account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,6 +211,7 @@
|
|||||||
37319F0527103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
|
37319F0527103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
|
||||||
37319F0627103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
|
37319F0627103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
|
||||||
37319F0727103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
|
37319F0727103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
|
||||||
|
3732BFD028B83763009F3F4D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 3732BFCF28B83763009F3F4D /* KeychainAccess */; };
|
||||||
3736A1FE286BB72300C9E5EE /* libavdevice.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3736A1EF286BB72300C9E5EE /* libavdevice.xcframework */; };
|
3736A1FE286BB72300C9E5EE /* libavdevice.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3736A1EF286BB72300C9E5EE /* libavdevice.xcframework */; };
|
||||||
3736A1FF286BB72300C9E5EE /* libavdevice.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3736A1EF286BB72300C9E5EE /* libavdevice.xcframework */; };
|
3736A1FF286BB72300C9E5EE /* libavdevice.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3736A1EF286BB72300C9E5EE /* libavdevice.xcframework */; };
|
||||||
3736A200286BB72300C9E5EE /* libuchardet.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3736A1F0286BB72300C9E5EE /* libuchardet.xcframework */; };
|
3736A200286BB72300C9E5EE /* libuchardet.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3736A1F0286BB72300C9E5EE /* libuchardet.xcframework */; };
|
||||||
@ -340,6 +341,11 @@
|
|||||||
37599F38272B4D740087F250 /* FavoriteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F37272B4D740087F250 /* FavoriteButton.swift */; };
|
37599F38272B4D740087F250 /* FavoriteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F37272B4D740087F250 /* FavoriteButton.swift */; };
|
||||||
37599F39272B4D740087F250 /* FavoriteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F37272B4D740087F250 /* FavoriteButton.swift */; };
|
37599F39272B4D740087F250 /* FavoriteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F37272B4D740087F250 /* FavoriteButton.swift */; };
|
||||||
37599F3A272B4D740087F250 /* FavoriteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F37272B4D740087F250 /* FavoriteButton.swift */; };
|
37599F3A272B4D740087F250 /* FavoriteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F37272B4D740087F250 /* FavoriteButton.swift */; };
|
||||||
|
375B8AB128B57F4200397B31 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 375B8AB028B57F4200397B31 /* KeychainAccess */; };
|
||||||
|
375B8AB328B580D300397B31 /* KeychainModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375B8AB228B580D300397B31 /* KeychainModel.swift */; };
|
||||||
|
375B8AB428B580D300397B31 /* KeychainModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375B8AB228B580D300397B31 /* KeychainModel.swift */; };
|
||||||
|
375B8AB528B580D300397B31 /* KeychainModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375B8AB228B580D300397B31 /* KeychainModel.swift */; };
|
||||||
|
375B8AB728B583BD00397B31 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 375B8AB628B583BD00397B31 /* KeychainAccess */; };
|
||||||
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 */; };
|
||||||
@ -1073,6 +1079,7 @@
|
|||||||
37599F2F272B42810087F250 /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = "<group>"; };
|
37599F2F272B42810087F250 /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = "<group>"; };
|
||||||
37599F33272B44000087F250 /* FavoritesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesModel.swift; sourceTree = "<group>"; };
|
37599F33272B44000087F250 /* FavoritesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesModel.swift; sourceTree = "<group>"; };
|
||||||
37599F37272B4D740087F250 /* FavoriteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteButton.swift; sourceTree = "<group>"; };
|
37599F37272B4D740087F250 /* FavoriteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteButton.swift; sourceTree = "<group>"; };
|
||||||
|
375B8AB228B580D300397B31 /* KeychainModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainModel.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>"; };
|
||||||
375E45F427B1976B00BA7902 /* MPVOGLView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPVOGLView.swift; sourceTree = "<group>"; };
|
375E45F427B1976B00BA7902 /* MPVOGLView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPVOGLView.swift; sourceTree = "<group>"; };
|
||||||
375E45F727B1AC4700BA7902 /* PlayerControlsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsModel.swift; sourceTree = "<group>"; };
|
375E45F727B1AC4700BA7902 /* PlayerControlsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsModel.swift; sourceTree = "<group>"; };
|
||||||
@ -1321,6 +1328,7 @@
|
|||||||
37C2211F27ADA3A200305B41 /* libz.tbd in Frameworks */,
|
37C2211F27ADA3A200305B41 /* libz.tbd in Frameworks */,
|
||||||
37FB284B2722099E00A57617 /* SDWebImageSwiftUI in Frameworks */,
|
37FB284B2722099E00A57617 /* SDWebImageSwiftUI in Frameworks */,
|
||||||
3736A210286BB72300C9E5EE /* libavcodec.xcframework in Frameworks */,
|
3736A210286BB72300C9E5EE /* libavcodec.xcframework in Frameworks */,
|
||||||
|
375B8AB128B57F4200397B31 /* KeychainAccess in Frameworks */,
|
||||||
3736A21A286BB72300C9E5EE /* libmpv.xcframework in Frameworks */,
|
3736A21A286BB72300C9E5EE /* libmpv.xcframework in Frameworks */,
|
||||||
3765917C27237D21009F956E /* PINCache in Frameworks */,
|
3765917C27237D21009F956E /* PINCache in Frameworks */,
|
||||||
37BD07B72698AB2E003EBB87 /* Defaults in Frameworks */,
|
37BD07B72698AB2E003EBB87 /* Defaults in Frameworks */,
|
||||||
@ -1375,6 +1383,7 @@
|
|||||||
370F4FD927CC16CB001B35DC /* libxcb-shm.0.0.0.dylib in Frameworks */,
|
370F4FD927CC16CB001B35DC /* libxcb-shm.0.0.0.dylib in Frameworks */,
|
||||||
370F4FCA27CC16CB001B35DC /* libXau.6.dylib in Frameworks */,
|
370F4FCA27CC16CB001B35DC /* libXau.6.dylib in Frameworks */,
|
||||||
370F4FD527CC16CB001B35DC /* libfontconfig.1.dylib in Frameworks */,
|
370F4FD527CC16CB001B35DC /* libfontconfig.1.dylib in Frameworks */,
|
||||||
|
375B8AB728B583BD00397B31 /* KeychainAccess in Frameworks */,
|
||||||
37A5DBC6285E06B100CA4DD1 /* SwiftUIPager in Frameworks */,
|
37A5DBC6285E06B100CA4DD1 /* SwiftUIPager in Frameworks */,
|
||||||
370F4FCE27CC16CB001B35DC /* libswresample.4.3.100.dylib in Frameworks */,
|
370F4FCE27CC16CB001B35DC /* libswresample.4.3.100.dylib in Frameworks */,
|
||||||
370F4FDA27CC16CB001B35DC /* libmpv.dylib in Frameworks */,
|
370F4FDA27CC16CB001B35DC /* libmpv.dylib in Frameworks */,
|
||||||
@ -1422,6 +1431,7 @@
|
|||||||
3736A20D286BB72300C9E5EE /* libavutil.xcframework in Frameworks */,
|
3736A20D286BB72300C9E5EE /* libavutil.xcframework in Frameworks */,
|
||||||
3736A213286BB72300C9E5EE /* libswresample.xcframework in Frameworks */,
|
3736A213286BB72300C9E5EE /* libswresample.xcframework in Frameworks */,
|
||||||
37FB285427220D8400A57617 /* SDWebImagePINPlugin in Frameworks */,
|
37FB285427220D8400A57617 /* SDWebImagePINPlugin in Frameworks */,
|
||||||
|
3732BFD028B83763009F3F4D /* KeychainAccess in Frameworks */,
|
||||||
3772003927E8EEB700CB2475 /* AVFoundation.framework in Frameworks */,
|
3772003927E8EEB700CB2475 /* AVFoundation.framework in Frameworks */,
|
||||||
3736A20F286BB72300C9E5EE /* libass.xcframework in Frameworks */,
|
3736A20F286BB72300C9E5EE /* libass.xcframework in Frameworks */,
|
||||||
3736A1FF286BB72300C9E5EE /* libavdevice.xcframework in Frameworks */,
|
3736A1FF286BB72300C9E5EE /* libavdevice.xcframework in Frameworks */,
|
||||||
@ -2072,6 +2082,7 @@
|
|||||||
37599F33272B44000087F250 /* FavoritesModel.swift */,
|
37599F33272B44000087F250 /* FavoritesModel.swift */,
|
||||||
37BC50AB2778BCBA00510953 /* HistoryModel.swift */,
|
37BC50AB2778BCBA00510953 /* HistoryModel.swift */,
|
||||||
377ABC3F286E4AD5009C986F /* InstancesManifest.swift */,
|
377ABC3F286E4AD5009C986F /* InstancesManifest.swift */,
|
||||||
|
375B8AB228B580D300397B31 /* KeychainModel.swift */,
|
||||||
377ABC43286E4B74009C986F /* ManifestedInstance.swift */,
|
377ABC43286E4B74009C986F /* ManifestedInstance.swift */,
|
||||||
37EF5C212739D37B00B03725 /* MenuModel.swift */,
|
37EF5C212739D37B00B03725 /* MenuModel.swift */,
|
||||||
371F2F19269B43D300E4A7AB /* NavigationModel.swift */,
|
371F2F19269B43D300E4A7AB /* NavigationModel.swift */,
|
||||||
@ -2295,6 +2306,7 @@
|
|||||||
372AA40F286D067B0000B1DC /* Repeat */,
|
372AA40F286D067B0000B1DC /* Repeat */,
|
||||||
37EE6DC428A305AD00BFD632 /* Reachability */,
|
37EE6DC428A305AD00BFD632 /* Reachability */,
|
||||||
3799AC0828B03CED001376F9 /* ActiveLabel */,
|
3799AC0828B03CED001376F9 /* ActiveLabel */,
|
||||||
|
375B8AB028B57F4200397B31 /* KeychainAccess */,
|
||||||
);
|
);
|
||||||
productName = "Yattee (iOS)";
|
productName = "Yattee (iOS)";
|
||||||
productReference = 37D4B0C92671614900C925CA /* Yattee.app */;
|
productReference = 37D4B0C92671614900C925CA /* Yattee.app */;
|
||||||
@ -2331,6 +2343,7 @@
|
|||||||
37CF8B8528535E5A00B71E37 /* SDWebImage */,
|
37CF8B8528535E5A00B71E37 /* SDWebImage */,
|
||||||
37A5DBC5285E06B100CA4DD1 /* SwiftUIPager */,
|
37A5DBC5285E06B100CA4DD1 /* SwiftUIPager */,
|
||||||
372AA413286D06A10000B1DC /* Repeat */,
|
372AA413286D06A10000B1DC /* Repeat */,
|
||||||
|
375B8AB628B583BD00397B31 /* KeychainAccess */,
|
||||||
);
|
);
|
||||||
productName = "Yattee (macOS)";
|
productName = "Yattee (macOS)";
|
||||||
productReference = 37D4B0CF2671614900C925CA /* Yattee.app */;
|
productReference = 37D4B0CF2671614900C925CA /* Yattee.app */;
|
||||||
@ -2407,6 +2420,7 @@
|
|||||||
37CF8B8728535E6300B71E37 /* SDWebImage */,
|
37CF8B8728535E6300B71E37 /* SDWebImage */,
|
||||||
372AA411286D06950000B1DC /* Repeat */,
|
372AA411286D06950000B1DC /* Repeat */,
|
||||||
37E80F42287B7AAF00561799 /* SwiftUIPager */,
|
37E80F42287B7AAF00561799 /* SwiftUIPager */,
|
||||||
|
3732BFCF28B83763009F3F4D /* KeychainAccess */,
|
||||||
);
|
);
|
||||||
productName = Yattee;
|
productName = Yattee;
|
||||||
productReference = 37D4B158267164AE00C925CA /* Yattee.app */;
|
productReference = 37D4B158267164AE00C925CA /* Yattee.app */;
|
||||||
@ -2506,6 +2520,7 @@
|
|||||||
372AA40E286D067B0000B1DC /* XCRemoteSwiftPackageReference "Repeat" */,
|
372AA40E286D067B0000B1DC /* XCRemoteSwiftPackageReference "Repeat" */,
|
||||||
37EE6DC328A305AD00BFD632 /* XCRemoteSwiftPackageReference "Reachability" */,
|
37EE6DC328A305AD00BFD632 /* XCRemoteSwiftPackageReference "Reachability" */,
|
||||||
3799AC0728B03CEC001376F9 /* XCRemoteSwiftPackageReference "ActiveLabel.swift" */,
|
3799AC0728B03CEC001376F9 /* XCRemoteSwiftPackageReference "ActiveLabel.swift" */,
|
||||||
|
375B8AAF28B57F4200397B31 /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 37D4B0CA2671614900C925CA /* Products */;
|
productRefGroup = 37D4B0CA2671614900C925CA /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@ -2857,6 +2872,7 @@
|
|||||||
376A33E42720CB35000C1D6B /* Account.swift in Sources */,
|
376A33E42720CB35000C1D6B /* Account.swift in Sources */,
|
||||||
3756C2A62861131100E4B059 /* NetworkState.swift in Sources */,
|
3756C2A62861131100E4B059 /* NetworkState.swift in Sources */,
|
||||||
37BA794326DBA973002A0235 /* PlaylistsModel.swift in Sources */,
|
37BA794326DBA973002A0235 /* PlaylistsModel.swift in Sources */,
|
||||||
|
375B8AB328B580D300397B31 /* KeychainModel.swift in Sources */,
|
||||||
37BC50AC2778BCBA00510953 /* HistoryModel.swift in Sources */,
|
37BC50AC2778BCBA00510953 /* HistoryModel.swift in Sources */,
|
||||||
37AAF29026740715007FC770 /* Channel.swift in Sources */,
|
37AAF29026740715007FC770 /* Channel.swift in Sources */,
|
||||||
37DD9DBA2785D60300539416 /* FramePreferenceKey.swift in Sources */,
|
37DD9DBA2785D60300539416 /* FramePreferenceKey.swift in Sources */,
|
||||||
@ -3032,6 +3048,7 @@
|
|||||||
37F64FE526FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
|
37F64FE526FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
|
||||||
377ABC45286E4B74009C986F /* ManifestedInstance.swift in Sources */,
|
377ABC45286E4B74009C986F /* ManifestedInstance.swift in Sources */,
|
||||||
3795593727B08538007FF8F4 /* StreamControl.swift in Sources */,
|
3795593727B08538007FF8F4 /* StreamControl.swift in Sources */,
|
||||||
|
375B8AB428B580D300397B31 /* KeychainModel.swift in Sources */,
|
||||||
37F7AB5528A951B200FB46B5 /* Power.swift in Sources */,
|
37F7AB5528A951B200FB46B5 /* Power.swift in Sources */,
|
||||||
372CFD16285F2E2A00B0B54B /* ControlsBar.swift in Sources */,
|
372CFD16285F2E2A00B0B54B /* ControlsBar.swift in Sources */,
|
||||||
37FFC441272734C3009FFD26 /* Throttle.swift in Sources */,
|
37FFC441272734C3009FFD26 /* Throttle.swift in Sources */,
|
||||||
@ -3372,6 +3389,7 @@
|
|||||||
3752069F285E910600CA655F /* ChapterView.swift in Sources */,
|
3752069F285E910600CA655F /* ChapterView.swift in Sources */,
|
||||||
37F4AD1D28612B23004D0F66 /* OpeningStream.swift in Sources */,
|
37F4AD1D28612B23004D0F66 /* OpeningStream.swift in Sources */,
|
||||||
3705B180267B4DFB00704544 /* TrendingCountry.swift in Sources */,
|
3705B180267B4DFB00704544 /* TrendingCountry.swift in Sources */,
|
||||||
|
375B8AB528B580D300397B31 /* KeychainModel.swift in Sources */,
|
||||||
373CFACD26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
373CFACD26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
||||||
37C3A24B27235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */,
|
37C3A24B27235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */,
|
||||||
37F4AD2128612DFD004D0F66 /* Buffering.swift in Sources */,
|
37F4AD2128612DFD004D0F66 /* Buffering.swift in Sources */,
|
||||||
@ -4328,6 +4346,14 @@
|
|||||||
minimumVersion = 0.6.0;
|
minimumVersion = 0.6.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
375B8AAF28B57F4200397B31 /* XCRemoteSwiftPackageReference "KeychainAccess" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git";
|
||||||
|
requirement = {
|
||||||
|
branch = master;
|
||||||
|
kind = branch;
|
||||||
|
};
|
||||||
|
};
|
||||||
3765917827237D07009F956E /* XCRemoteSwiftPackageReference "PINCache" */ = {
|
3765917827237D07009F956E /* XCRemoteSwiftPackageReference "PINCache" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/pinterest/PINCache";
|
repositoryURL = "https://github.com/pinterest/PINCache";
|
||||||
@ -4500,6 +4526,21 @@
|
|||||||
package = 372AA40E286D067B0000B1DC /* XCRemoteSwiftPackageReference "Repeat" */;
|
package = 372AA40E286D067B0000B1DC /* XCRemoteSwiftPackageReference "Repeat" */;
|
||||||
productName = Repeat;
|
productName = Repeat;
|
||||||
};
|
};
|
||||||
|
3732BFCF28B83763009F3F4D /* KeychainAccess */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 375B8AAF28B57F4200397B31 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
||||||
|
productName = KeychainAccess;
|
||||||
|
};
|
||||||
|
375B8AB028B57F4200397B31 /* KeychainAccess */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 375B8AAF28B57F4200397B31 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
||||||
|
productName = KeychainAccess;
|
||||||
|
};
|
||||||
|
375B8AB628B583BD00397B31 /* KeychainAccess */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 375B8AAF28B57F4200397B31 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
||||||
|
productName = KeychainAccess;
|
||||||
|
};
|
||||||
3765917B27237D21009F956E /* PINCache */ = {
|
3765917B27237D21009F956E /* PINCache */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 3765917827237D07009F956E /* XCRemoteSwiftPackageReference "PINCache" */;
|
package = 3765917827237D07009F956E /* XCRemoteSwiftPackageReference "PINCache" */;
|
||||||
|
@ -27,6 +27,15 @@
|
|||||||
"version" : "6.3.0"
|
"version" : "6.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "keychainaccess",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "6299daec1d74be12164fec090faf9ed14d0da9d6"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "libwebp-xcode",
|
"identity" : "libwebp-xcode",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
@ -9,6 +9,7 @@ struct AccountSelectionView: View {
|
|||||||
|
|
||||||
@Default(.accounts) private var accounts
|
@Default(.accounts) private var accounts
|
||||||
@Default(.instances) private var instances
|
@Default(.instances) private var instances
|
||||||
|
@Default(.accountPickerDisplaysAnonymousAccounts) private var accountPickerDisplaysAnonymousAccounts
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Section(header: Text(showHeader ? "Current Location" : "")) {
|
Section(header: Text(showHeader ? "Current Location" : "")) {
|
||||||
@ -32,7 +33,8 @@ struct AccountSelectionView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var allAccounts: [Account] {
|
var allAccounts: [Account] {
|
||||||
accounts + instances.map(\.anonymousAccount) + [accountsModel.publicAccount].compactMap { $0 }
|
let anonymousAccounts = accountPickerDisplaysAnonymousAccounts ? instances.map(\.anonymousAccount) : []
|
||||||
|
return accounts + anonymousAccounts + [accountsModel.publicAccount].compactMap { $0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
private var nextAccount: Account? {
|
private var nextAccount: Account? {
|
||||||
|
Loading…
Reference in New Issue
Block a user