IceCubesApp/IceCubesApp/App/Tabs/Settings/AddAccountsView.swift

206 lines
5.8 KiB
Swift
Raw Normal View History

2023-01-17 10:36:01 +00:00
import AppAccount
import Combine
2022-12-29 13:07:58 +00:00
import DesignSystem
2023-01-17 10:36:01 +00:00
import Env
import Models
import Network
2022-12-29 13:07:58 +00:00
import NukeUI
import Shimmer
2023-01-17 10:36:01 +00:00
import SwiftUI
2022-12-29 13:07:58 +00:00
struct AddAccountView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.scenePhase) private var scenePhase
2023-01-17 10:36:01 +00:00
2022-12-29 13:07:58 +00:00
@EnvironmentObject private var appAccountsManager: AppAccountsManager
@EnvironmentObject private var currentAccount: CurrentAccount
@EnvironmentObject private var currentInstance: CurrentInstance
2023-01-08 13:16:43 +00:00
@EnvironmentObject private var pushNotifications: PushNotificationsService
2022-12-29 13:07:58 +00:00
@EnvironmentObject private var theme: Theme
2023-01-17 10:36:01 +00:00
2022-12-29 13:07:58 +00:00
@State private var instanceName: String = ""
@State private var instance: Instance?
@State private var isSigninIn = false
@State private var signInClient: Client?
@State private var instances: [InstanceSocial] = []
2023-01-01 08:19:00 +00:00
@State private var instanceFetchError: String?
private let instanceNamePublisher = PassthroughSubject<String, Never>()
2023-01-17 10:36:01 +00:00
2023-01-01 08:19:00 +00:00
@FocusState private var isInstanceURLFieldFocused: Bool
2023-01-17 10:36:01 +00:00
2022-12-29 13:07:58 +00:00
var body: some View {
NavigationStack {
Form {
TextField("Instance URL", text: $instanceName)
2022-12-29 13:07:58 +00:00
.listRowBackground(theme.primaryBackgroundColor)
.keyboardType(.URL)
.textContentType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
2023-01-01 08:19:00 +00:00
.focused($isInstanceURLFieldFocused)
if let instanceFetchError {
Text(instanceFetchError)
}
2022-12-29 13:07:58 +00:00
if let instance {
signInSection
2023-01-07 17:12:56 +00:00
InstanceInfoSection(instance: instance)
2022-12-29 13:07:58 +00:00
} else {
instancesListView
}
}
.formStyle(.grouped)
.navigationTitle("Add account")
.navigationBarTitleDisplayMode(.inline)
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
2023-01-01 08:19:00 +00:00
.scrollDismissesKeyboard(.immediately)
2022-12-29 13:07:58 +00:00
.toolbar {
2023-01-12 05:30:43 +00:00
if !appAccountsManager.availableAccounts.isEmpty {
ToolbarItem(placement: .navigationBarLeading) {
Button("Cancel", action: { dismiss() })
}
2022-12-29 13:07:58 +00:00
}
}
.onAppear {
2023-01-01 08:19:00 +00:00
isInstanceURLFieldFocused = true
2022-12-29 13:07:58 +00:00
let client = InstanceSocialClient()
Task {
self.instances = await client.fetchInstances()
}
isSigninIn = false
2022-12-29 13:07:58 +00:00
}
.onChange(of: instanceName) { newValue in
instanceNamePublisher.send(newValue)
}
.onReceive(instanceNamePublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { newValue in
2022-12-29 13:07:58 +00:00
let client = Client(server: newValue)
Task {
do {
self.instance = try await client.get(endpoint: Instances.instance)
self.instanceFetchError = nil
2023-01-01 08:19:00 +00:00
} catch _ as DecodingError {
self.instance = nil
self.instanceFetchError = "This instance is not currently supported."
2022-12-29 13:07:58 +00:00
} catch {
self.instance = nil
}
}
}
.onChange(of: scenePhase, perform: { scenePhase in
switch scenePhase {
case .active:
isSigninIn = false
default:
break
}
})
2022-12-29 13:07:58 +00:00
.onOpenURL(perform: { url in
Task {
await continueSignIn(url: url)
}
})
}
}
2023-01-17 10:36:01 +00:00
private var signInSection: some View {
Section {
Button {
isSigninIn = true
Task {
await signIn()
}
} label: {
HStack {
Spacer()
if isSigninIn {
ProgressView()
.tint(theme.labelColor)
} else {
Text("Sign in")
.font(.headline)
}
Spacer()
}
}
.buttonStyle(.borderedProminent)
}
.listRowBackground(theme.tintColor)
}
2023-01-17 10:36:01 +00:00
2022-12-29 13:07:58 +00:00
private var instancesListView: some View {
Section("Suggestions") {
if instances.isEmpty {
placeholderRow
2022-12-29 13:07:58 +00:00
} else {
2023-01-17 10:36:01 +00:00
ForEach(instanceName.isEmpty ? instances : instances.filter { $0.name.contains(instanceName.lowercased()) }) { instance in
Button {
2022-12-29 13:07:58 +00:00
self.instanceName = instance.name
} label: {
VStack(alignment: .leading, spacing: 4) {
Text(instance.name)
.font(.headline)
.foregroundColor(.primary)
Text(instance.info?.shortDescription ?? "")
.font(.body)
.foregroundColor(.gray)
Text("\(instance.users) users ⸱ \(instance.statuses) posts")
.font(.footnote)
.foregroundColor(.gray)
}
2022-12-29 13:07:58 +00:00
}
.listRowBackground(theme.primaryBackgroundColor)
2022-12-29 13:07:58 +00:00
}
}
}
}
2023-01-17 10:36:01 +00:00
private var placeholderRow: some View {
VStack(alignment: .leading, spacing: 4) {
Text("Loading...")
.font(.headline)
.foregroundColor(.primary)
Text("Loading, loading, loading ....")
.font(.body)
.foregroundColor(.gray)
Text("Loading ...")
.font(.footnote)
.foregroundColor(.gray)
}
.redacted(reason: .placeholder)
.shimmering()
.listRowBackground(theme.primaryBackgroundColor)
}
2023-01-17 10:36:01 +00:00
2022-12-29 13:07:58 +00:00
private func signIn() async {
do {
signInClient = .init(server: instanceName)
if let oauthURL = try await signInClient?.oauthURL() {
await UIApplication.shared.open(oauthURL)
2022-12-29 13:07:58 +00:00
} else {
isSigninIn = false
}
} catch {
isSigninIn = false
}
}
2023-01-17 10:36:01 +00:00
2022-12-29 13:07:58 +00:00
private func continueSignIn(url: URL) async {
guard let client = signInClient else {
isSigninIn = false
return
}
do {
let oauthToken = try await client.continueOauthFlow(url: url)
appAccountsManager.add(account: AppAccount(server: client.server, oauthToken: oauthToken))
2023-01-08 09:22:52 +00:00
Task {
await pushNotifications.updateSubscriptions(accounts: appAccountsManager.pushAccounts)
}
2022-12-29 13:07:58 +00:00
isSigninIn = false
dismiss()
} catch {
isSigninIn = false
}
}
}