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

250 lines
7.4 KiB
Swift
Raw Normal View History

2023-01-17 11:36:01 +01:00
import AppAccount
import Combine
2022-12-29 14:07:58 +01:00
import DesignSystem
2023-01-17 11:36:01 +01:00
import Env
import Models
import Network
2022-12-29 14:07:58 +01:00
import NukeUI
2023-01-22 06:38:30 +01:00
import SafariServices
2022-12-29 14:07:58 +01:00
import Shimmer
2023-01-17 11:36:01 +01:00
import SwiftUI
2022-12-29 14:07:58 +01:00
struct AddAccountView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.scenePhase) private var scenePhase
2023-01-17 11:36:01 +01:00
2022-12-29 14:07:58 +01:00
@EnvironmentObject private var appAccountsManager: AppAccountsManager
@EnvironmentObject private var currentAccount: CurrentAccount
@EnvironmentObject private var currentInstance: CurrentInstance
2023-01-08 14:16:43 +01:00
@EnvironmentObject private var pushNotifications: PushNotificationsService
2022-12-29 14:07:58 +01:00
@EnvironmentObject private var theme: Theme
2023-01-17 11:36:01 +01:00
2022-12-29 14:07:58 +01: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] = []
@State private var instanceFetchError: LocalizedStringKey?
2023-01-19 11:59:40 +01:00
@State private var oauthURL: URL?
private let instanceNamePublisher = PassthroughSubject<String, Never>()
2023-01-17 11:36:01 +01:00
2023-01-01 09:19:00 +01:00
@FocusState private var isInstanceURLFieldFocused: Bool
2023-01-17 11:36:01 +01:00
2022-12-29 14:07:58 +01:00
var body: some View {
NavigationStack {
Form {
TextField("instance.url", text: $instanceName)
2022-12-29 14:07:58 +01:00
.listRowBackground(theme.primaryBackgroundColor)
.keyboardType(.URL)
.textContentType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
2023-01-01 09:19:00 +01:00
.focused($isInstanceURLFieldFocused)
if let instanceFetchError {
Text(instanceFetchError)
}
2023-01-21 16:54:43 +01:00
if instance != nil || !instanceName.isEmpty {
signInSection
2023-01-21 16:54:43 +01:00
}
if let instance {
2023-01-07 18:12:56 +01:00
InstanceInfoSection(instance: instance)
2022-12-29 14:07:58 +01:00
} else {
instancesListView
}
}
.formStyle(.grouped)
.navigationTitle("account.add.navigation-title")
2022-12-29 14:07:58 +01:00
.navigationBarTitleDisplayMode(.inline)
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
2023-01-01 09:19:00 +01:00
.scrollDismissesKeyboard(.immediately)
2022-12-29 14:07:58 +01:00
.toolbar {
2023-01-12 06:30:43 +01:00
if !appAccountsManager.availableAccounts.isEmpty {
ToolbarItem(placement: .navigationBarLeading) {
Button("action.cancel", action: { dismiss() })
2023-01-12 06:30:43 +01:00
}
2022-12-29 14:07:58 +01:00
}
}
.onAppear {
2023-01-01 09:19:00 +01:00
isInstanceURLFieldFocused = true
2022-12-29 14:07:58 +01:00
let client = InstanceSocialClient()
Task {
2023-01-21 16:54:43 +01:00
let instances = await client.fetchInstances()
withAnimation {
self.instances = instances
}
2022-12-29 14:07:58 +01:00
}
isSigninIn = false
2022-12-29 14:07:58 +01:00
}
.onChange(of: instanceName) { newValue in
instanceNamePublisher.send(newValue)
}
.onReceive(instanceNamePublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { newValue in
2023-01-22 06:38:30 +01:00
let newValue = newValue
2023-01-21 16:54:43 +01:00
.replacingOccurrences(of: "http://", with: "")
.replacingOccurrences(of: "https://", with: "")
2022-12-29 14:07:58 +01:00
let client = Client(server: newValue)
Task {
do {
// bare bones preflight for domain validity
if client.server.contains(".") && client.server.last != "." {
let instance: Instance = try await client.get(endpoint: Instances.instance)
withAnimation {
self.instance = instance
}
instanceFetchError = nil
} else {
instance = nil
instanceFetchError = nil
2023-01-21 16:54:43 +01:00
}
2023-01-01 09:19:00 +01:00
} catch _ as DecodingError {
2023-01-21 16:54:43 +01:00
instance = nil
instanceFetchError = "account.add.error.instance-not-supported"
2022-12-29 14:07:58 +01:00
} catch {
2023-01-21 16:54:43 +01:00
instance = nil
2022-12-29 14:07:58 +01:00
}
}
}
.onChange(of: scenePhase, perform: { scenePhase in
switch scenePhase {
case .active:
isSigninIn = false
default:
break
}
})
2022-12-29 14:07:58 +01:00
.onOpenURL(perform: { url in
Task {
await continueSignIn(url: url)
}
})
2023-01-21 16:54:43 +01:00
.onChange(of: oauthURL, perform: { newValue in
if newValue == nil {
isSigninIn = false
}
})
2023-01-19 11:59:40 +01:00
.sheet(item: $oauthURL, content: { url in
SafariView(url: url)
})
2022-12-29 14:07:58 +01:00
}
}
2023-01-17 11:36:01 +01:00
private var signInSection: some View {
Section {
Button {
2023-01-21 16:54:43 +01:00
withAnimation {
isSigninIn = true
}
Task {
await signIn()
}
} label: {
HStack {
Spacer()
2023-01-21 16:54:43 +01:00
if isSigninIn || !instanceName.isEmpty && instance == nil {
ProgressView()
2023-01-21 16:54:43 +01:00
.id(instanceName)
.tint(theme.labelColor)
} else {
Text("account.add.sign-in")
.font(.scaledHeadline)
}
Spacer()
}
}
.buttonStyle(.borderedProminent)
}
.listRowBackground(theme.tintColor)
}
2023-01-17 11:36:01 +01:00
2022-12-29 14:07:58 +01:00
private var instancesListView: some View {
Section("instance.suggestions") {
2022-12-29 14:07:58 +01:00
if instances.isEmpty {
placeholderRow
2022-12-29 14:07:58 +01:00
} else {
2023-01-17 11:36:01 +01:00
ForEach(instanceName.isEmpty ? instances : instances.filter { $0.name.contains(instanceName.lowercased()) }) { instance in
Button {
2022-12-29 14:07:58 +01:00
self.instanceName = instance.name
} label: {
VStack(alignment: .leading, spacing: 4) {
Text(instance.name)
.font(.scaledHeadline)
.foregroundColor(.primary)
Text(instance.info?.shortDescription ?? "")
.font(.scaledBody)
.foregroundColor(.gray)
(Text("instance.list.users-\(instance.users)")
+ Text("")
+ Text("instance.list.posts-\(instance.statuses)"))
2023-01-22 06:38:30 +01:00
.font(.scaledFootnote)
.foregroundColor(.gray)
}
2022-12-29 14:07:58 +01:00
}
.listRowBackground(theme.primaryBackgroundColor)
2022-12-29 14:07:58 +01:00
}
}
}
}
2023-01-17 11:36:01 +01:00
private var placeholderRow: some View {
VStack(alignment: .leading, spacing: 4) {
Text("placeholder.loading.short")
.font(.scaledHeadline)
.foregroundColor(.primary)
Text("placeholder.loading.long")
.font(.scaledBody)
.foregroundColor(.gray)
Text("placeholder.loading.short")
.font(.scaledFootnote)
.foregroundColor(.gray)
}
.redacted(reason: .placeholder)
.shimmering()
.listRowBackground(theme.primaryBackgroundColor)
}
2023-01-17 11:36:01 +01:00
2022-12-29 14:07:58 +01:00
private func signIn() async {
do {
signInClient = .init(server: instanceName)
if let oauthURL = try await signInClient?.oauthURL() {
2023-01-19 11:59:40 +01:00
self.oauthURL = oauthURL
2022-12-29 14:07:58 +01:00
} else {
isSigninIn = false
}
} catch {
isSigninIn = false
}
}
2023-01-17 11:36:01 +01:00
2022-12-29 14:07:58 +01:00
private func continueSignIn(url: URL) async {
guard let client = signInClient else {
isSigninIn = false
return
}
do {
2023-01-19 11:59:40 +01:00
oauthURL = nil
2022-12-29 14:07:58 +01:00
let oauthToken = try await client.continueOauthFlow(url: url)
appAccountsManager.add(account: AppAccount(server: client.server, oauthToken: oauthToken))
2023-01-08 10:22:52 +01:00
Task {
await pushNotifications.updateSubscriptions(accounts: appAccountsManager.pushAccounts)
}
2022-12-29 14:07:58 +01:00
isSigninIn = false
dismiss()
} catch {
2023-01-19 11:59:40 +01:00
oauthURL = nil
2022-12-29 14:07:58 +01:00
isSigninIn = false
}
}
}
2023-01-19 11:59:40 +01:00
struct SafariView: UIViewControllerRepresentable {
let url: URL
2023-01-22 06:38:30 +01:00
func makeUIViewController(context _: UIViewControllerRepresentableContext<SafariView>) -> SFSafariViewController {
2023-01-19 11:59:40 +01:00
SFSafariViewController(url: url)
}
2023-01-22 06:38:30 +01:00
func updateUIViewController(_: SFSafariViewController, context _: UIViewControllerRepresentableContext<SafariView>) {}
2023-01-19 11:59:40 +01:00
}