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

269 lines
8.3 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
@Environment(AppAccountsManager.self) private var appAccountsManager
@Environment(CurrentAccount.self) private var currentAccount
@Environment(CurrentInstance.self) private var currentInstance
@Environment(PushNotificationsService.self) private var pushNotifications
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-03-13 13:38:28 +01:00
private var sanitizedName: String {
2023-03-13 13:38:28 +01:00
var name = instanceName
.replacingOccurrences(of: "http://", with: "")
.replacingOccurrences(of: "https://", with: "")
if name.contains("@") {
let parts = name.components(separatedBy: "@")
name = parts[parts.count - 1] // [@]username@server.address.com
}
2023-03-13 13:38:28 +01:00
return name
}
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)
}
2023-03-13 13:38:28 +01:00
.onReceive(instanceNamePublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { _ in
// let newValue = newValue
// .replacingOccurrences(of: "http://", with: "")
// .replacingOccurrences(of: "https://", with: "")
let client = Client(server: sanitizedName)
2022-12-29 14:07:58 +01:00
Task {
do {
// bare bones preflight for domain validity
2023-09-16 14:15:03 +02:00
if client.server.contains("."), client.server.last != "." {
let instance: Instance = try await client.get(endpoint: Instances.instance)
withAnimation {
self.instance = instance
2023-09-16 14:15:03 +02:00
instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box
}
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) { _, newValue in
switch newValue {
case .active:
isSigninIn = false
default:
break
}
}
2022-12-29 14:07:58 +01:00
.onOpenURL(perform: { url in
Task {
await continueSignIn(url: url)
}
})
.onChange(of: oauthURL) { _, newValue in
2023-01-21 16:54:43 +01:00
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()
if isSigninIn || !sanitizedName.isEmpty && instance == nil {
ProgressView()
.id(sanitizedName)
.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 {
ForEach(sanitizedName.isEmpty ? instances : instances.filter { $0.name.contains(sanitizedName.lowercased()) }) { instance in
Button {
2023-09-16 14:15:03 +02:00
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)
2023-09-18 18:55:11 +02:00
.allowsHitTesting(false)
.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: sanitizedName)
2022-12-29 14:07:58 +01:00
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)
let client = Client(server: client.server, oauthToken: oauthToken)
let account: Account = try await client.get(endpoint: Accounts.verifyCredentials)
appAccountsManager.add(account: AppAccount(server: client.server,
accountName: "\(account.acct)@\(client.server)",
oauthToken: oauthToken))
2023-01-08 10:22:52 +01:00
Task {
pushNotifications.setAccounts(accounts: appAccountsManager.pushAccounts)
await pushNotifications.updateSubscriptions(forceCreate: true)
2023-01-08 10:22:52 +01:00
}
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
}