Account Switcher

This commit is contained in:
Lumaa 2024-02-24 13:21:31 +01:00
parent 75483985ab
commit 9a99638896
5 changed files with 254 additions and 13 deletions

View File

@ -8,7 +8,7 @@ public class AccountManager: ObservableObject {
private var client: Client?
private var account: Account?
public static let shared: AccountManager = AccountManager()
public static var shared: AccountManager = AccountManager()
init(client: Client? = nil, account: Account? = nil) {
self.client = client

View File

@ -5,16 +5,22 @@ import SwiftUI
import SwiftData
@Model
class LoggedAccounts {
let appAccounts: [AppAccount]
let currentAccount: AppAccount
class LoggedAccount {
let token: OauthToken
let acct: String
let app: AppAccount?
static let shared: LoggedAccounts = LoggedAccounts()
init(token: OauthToken, acct: String) {
self.token = token
self.acct = acct
self.app = nil
}
init(appAccounts: [AppAccount] = [], current: AppAccount? = nil) {
let curr: AppAccount = current ?? AppAccount.loadAsCurrent()!
self.appAccounts = appAccounts.count < 1 ? [curr] : appAccounts
self.currentAccount = curr
init(appAccount: AppAccount) {
guard let token = appAccount.oauthToken, let acct = appAccount.accountName else { fatalError("Cannot convert AppAccount to LoggedAccount") }
self.token = token
self.acct = acct
self.app = appAccount
}
}
@ -22,6 +28,6 @@ public extension View {
@ViewBuilder
func modelData() -> some View {
self
.modelContainer(for: LoggedAccounts.self)
.modelContainer(for: LoggedAccount.self)
}
}

View File

@ -1399,6 +1399,70 @@
}
}
},
"settings.account-switcher.add" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Add new account"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ajouter un nouveau compte"
}
}
}
},
"settings.account-switcher.current" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Current"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Actuel"
}
}
}
},
"settings.account-switcher.log" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Log in"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Connecter"
}
}
}
},
"settings.account-switcher.relog" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "To get access to the Account Switcher, please log out and log back"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pour avoir accès au Account Switcher, veuillez vous déconnecter puis reconnecter"
}
}
}
},
"settings.cancel" : {
"localizations" : {
"en" : {

View File

@ -1,11 +1,15 @@
//Made by Lumaa
import SwiftUI
import SwiftData
import AuthenticationServices
struct AddInstanceView: View {
@Environment(\.webAuthenticationSession) private var webAuthenticationSession
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var modelContext
@Query private var loggedAccounts: [LoggedAccount]
// Instance URL and verify
@State private var instanceUrl: String = ""
@ -216,7 +220,7 @@ struct AddInstanceView: View {
return
}
if agreedResponsability && responsability {
if agreedResponsability && responsability && loggedAccounts.count <= 0 {
UserDefaults.standard.setValue(true, forKey: "unsafe")
} else {
UserDefaults.standard.removeObject(forKey: "unsafe")
@ -235,10 +239,16 @@ struct AddInstanceView: View {
AccountManager.shared.setClient(client)
AccountManager.shared.setAccount(account)
let newLog: LoggedAccount = .init(appAccount: appAcc)
modelContext.insert(newLog)
HapticManager.playHaptics(haptics: Haptic.success)
signInClient = client
logged = true
dismiss()
} catch {
HapticManager.playHaptics(haptics: Haptic.error)
print(error)
}
}

View File

@ -1,16 +1,66 @@
//Made by Lumaa
import SwiftUI
import SwiftData
//TODO: Bring back "Privacy" with mutelist, blocklist and default visibility
struct SettingsView: View {
@Environment(UniversalNavigator.self) private var uniNav: UniversalNavigator
@Query private var loggedAccounts: [LoggedAccount]
@StateObject var navigator: Navigator
@State private var switched: Bool = false
var body: some View {
NavigationStack(path: $navigator.path) {
List {
if loggedAccounts.count > 0 {
Section {
ForEach(loggedAccounts) { logged in
if let app = logged.app {
SwitcherRow(app: app)
.listRowThreaded()
}
}
if AppDelegate.premium || loggedAccounts.count < 3 {
Button {
uniNav.presentedSheet = .mastodonLogin(logged: $switched)
} label: {
Label("settings.account-switcher.add", systemImage: "person.crop.circle.badge.plus")
.foregroundStyle(Color.blue)
}
.listRowThreaded()
}
}
.onChange(of: switched) { _, new in
if new == true {
// switched correctly
HapticManager.playHaptics(haptics: Haptic.success)
uniNav.selectedTab = .timeline
navigator.path = []
}
}
} else {
Section {
//MARK: Remove in later update
HStack(alignment: .center) {
Spacer()
Text("settings.account-switcher.relog")
.foregroundStyle(Color.gray)
.multilineTextAlignment(.center)
.font(.caption)
Spacer()
}
.listRowThreaded()
}
}
Spacer()
.frame(height: 30)
.listRowThreaded()
Section {
Button {
navigator.navigate(to: .about)
@ -53,6 +103,7 @@ struct SettingsView: View {
.listRowThreaded()
}
}
.environmentObject(navigator)
.withAppRouter(navigator)
.withCovers(sheetDestination: $navigator.presentedCover)
.listThreaded()
@ -62,6 +113,116 @@ struct SettingsView: View {
}
}
#Preview {
SettingsView(navigator: .init())
extension SettingsView {
struct SwitcherRow: View {
@Environment(AccountManager.self) private var accountManager: AccountManager
@Environment(UniversalNavigator.self) private var uniNav: UniversalNavigator
@EnvironmentObject private var navigator: Navigator
var app: AppAccount
@State private var account: Account? = nil
@State private var error: Bool = false
private var currentAccount: Bool {
return AccountManager.shared.forceClient().server == app.server
}
init(app: AppAccount) {
self.app = app
}
var body: some View {
ZStack {
if let acc = account {
HStack {
ProfilePicture(url: acc.avatar, size: 64)
VStack(alignment: .leading) {
Text(acc.displayName ?? "@\(acc.acct)")
.multilineTextAlignment(.leading)
Text("@\(acc.acct)")
.multilineTextAlignment(.leading)
.font(.caption)
.foregroundStyle(Color.gray)
}
Spacer()
if !currentAccount {
Button {
Task {
let c: Client = Client(server: app.server, oauthToken: app.oauthToken)
let am: AccountManager = .init(client: c)
let fetched: Account? = await am.fetchAccount()
if fetched == nil {
am.clear()
error = true
} else {
AccountManager.shared.setAccount(fetched!)
AccountManager.shared.setClient(c)
uniNav.selectedTab = .timeline
navigator.path = []
}
}
} label: {
Text("settings.account-switcher.log")
}
.buttonStyle(LargeButton(filled: true, height: 7.5, disabled: currentAccount))
.disabled(currentAccount)
} else {
Text("settings.account-switcher.current")
.foregroundStyle(Color.gray)
.font(.caption)
.padding(.horizontal)
.lineLimit(1)
}
}
} else {
Circle()
.fill(error ? Color.red.opacity(0.45) : Color.gray.opacity(0.45))
.frame(width: 54, height: 54)
VStack(alignment: .leading) {
Text(Account.placeholder().displayName ?? "@\(Account.placeholder().acct)")
.redacted(reason: .placeholder)
.multilineTextAlignment(.leading)
Text("@\(Account.placeholder().acct)")
.redacted(reason: .placeholder)
.multilineTextAlignment(.leading)
.font(.caption)
.foregroundStyle(Color.gray)
}
Spacer()
Button {
print(acct)
} label: {
Text("settings.account-switcher.log")
.redacted(reason: .placeholder)
}
.buttonStyle(LargeButton(filled: true, height: 7.5))
}
}
.task {
account = await findAccount(acct: app.accountName!)
}
}
private func findAccount(acct: String) async -> Account? {
guard let client = accountManager.getClient() else { return nil }
do {
try await Task.sleep(for: .milliseconds(250))
let results: SearchResults = try await client.get(endpoint: Search.search(query: acct, type: "accounts", offset: nil, following: nil), forceVersion: .v2)
return results.accounts.first
} catch {
print(error)
}
return nil
}
}
}