2023-12-29 11:17:37 +01:00
|
|
|
//Made by Lumaa
|
|
|
|
|
|
|
|
import SwiftUI
|
2024-02-24 13:21:31 +01:00
|
|
|
import SwiftData
|
2023-12-29 11:17:37 +01:00
|
|
|
import AuthenticationServices
|
|
|
|
|
|
|
|
struct AddInstanceView: View {
|
|
|
|
@Environment(\.webAuthenticationSession) private var webAuthenticationSession
|
|
|
|
@Environment(\.dismiss) private var dismiss
|
2024-02-24 13:21:31 +01:00
|
|
|
@Environment(\.modelContext) private var modelContext
|
|
|
|
|
|
|
|
@Query private var loggedAccounts: [LoggedAccount]
|
2023-12-29 11:17:37 +01:00
|
|
|
|
|
|
|
// Instance URL and verify
|
|
|
|
@State private var instanceUrl: String = ""
|
2024-02-16 16:21:46 +01:00
|
|
|
|
2023-12-29 11:17:37 +01:00
|
|
|
@State private var verifying: Bool = false
|
|
|
|
@State private var verified: Bool = false
|
|
|
|
@State private var verifyError: Bool = false
|
2024-02-16 16:21:46 +01:00
|
|
|
|
|
|
|
@State private var blockList: [String] = []
|
|
|
|
@State private var responsability: Bool = false
|
|
|
|
@State private var showingResponsability: Bool = false
|
|
|
|
@State private var agreedResponsability: Bool = false
|
|
|
|
|
2023-12-29 11:17:37 +01:00
|
|
|
@State private var instanceInfo: Instance?
|
|
|
|
|
|
|
|
@State private var signInClient: Client?
|
|
|
|
@Binding public var logged: Bool
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
Form {
|
|
|
|
Section {
|
|
|
|
TextField("login.mastodon.instance", text: $instanceUrl)
|
|
|
|
.keyboardType(.URL)
|
|
|
|
.textContentType(.URL)
|
|
|
|
.autocorrectionDisabled()
|
|
|
|
.textInputAutocapitalization(.never)
|
2024-02-11 16:28:42 +01:00
|
|
|
.submitLabel(.continue)
|
2023-12-29 11:17:37 +01:00
|
|
|
.disabled(verifying)
|
2024-02-11 16:28:42 +01:00
|
|
|
.onSubmit {
|
|
|
|
if !verified {
|
|
|
|
verify()
|
|
|
|
}
|
|
|
|
}
|
2023-12-29 11:17:37 +01:00
|
|
|
|
|
|
|
if !verifying {
|
|
|
|
if !verified {
|
|
|
|
Button {
|
|
|
|
verify()
|
|
|
|
} label: {
|
|
|
|
Text("login.mastodon.verify")
|
|
|
|
.disabled(instanceUrl.isEmpty)
|
|
|
|
}
|
|
|
|
.buttonStyle(.bordered)
|
|
|
|
|
|
|
|
if verifyError == true {
|
|
|
|
Text("login.mastodon.verify-error")
|
|
|
|
.foregroundStyle(.red)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Button {
|
|
|
|
Task {
|
|
|
|
await signIn()
|
|
|
|
}
|
|
|
|
} label: {
|
|
|
|
Text("login.mastodon.login")
|
|
|
|
.disabled(instanceUrl.isEmpty)
|
|
|
|
}
|
|
|
|
.buttonStyle(.bordered)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ProgressView()
|
|
|
|
.progressViewStyle(.circular)
|
|
|
|
.tint(Color.white)
|
|
|
|
.foregroundStyle(Color.white)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if verified && instanceInfo != nil {
|
|
|
|
Section {
|
|
|
|
VStack(alignment: .leading) {
|
|
|
|
Text(instanceInfo!.title)
|
|
|
|
.font(.headline)
|
|
|
|
Text(instanceInfo!.shortDescription)
|
|
|
|
}
|
|
|
|
|
|
|
|
VStack(alignment: .leading, spacing: 15) {
|
|
|
|
Text("instance.rules")
|
|
|
|
.font(.headline)
|
|
|
|
|
|
|
|
if !(instanceInfo!.rules?.isEmpty ?? true) {
|
|
|
|
ForEach(instanceInfo!.rules!) { rule in
|
|
|
|
Text(rule.text)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-02-16 16:21:46 +01:00
|
|
|
.task {
|
|
|
|
withAnimation {
|
|
|
|
verifying = true
|
|
|
|
}
|
|
|
|
|
|
|
|
blockList = Instance.getBlocklist()
|
|
|
|
|
|
|
|
withAnimation {
|
|
|
|
verifying = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.alert("login.instance.unsafe", isPresented: $showingResponsability, actions: {
|
|
|
|
Button(role: .destructive) {
|
|
|
|
responsability = true
|
|
|
|
agreedResponsability = true
|
|
|
|
showingResponsability.toggle()
|
|
|
|
} label: {
|
|
|
|
Text("login.instance.unsafe.agree")
|
|
|
|
}
|
|
|
|
|
|
|
|
Button(role: .cancel) {
|
|
|
|
responsability = true
|
|
|
|
agreedResponsability = false
|
|
|
|
showingResponsability.toggle()
|
|
|
|
} label: {
|
|
|
|
Text("login.instance.unsafe.disagree")
|
|
|
|
}
|
|
|
|
}, message: {
|
|
|
|
Text("login.instance.unsafe.description")
|
|
|
|
})
|
2023-12-29 12:52:11 +01:00
|
|
|
.scrollContentBackground(.hidden)
|
|
|
|
.background(Color.appBackground)
|
2023-12-29 11:17:37 +01:00
|
|
|
.onChange(of: instanceUrl) { _, newValue in
|
|
|
|
guard !self.verifying else { return }
|
|
|
|
verified = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func verify() {
|
|
|
|
withAnimation {
|
|
|
|
verifying = true
|
|
|
|
verified = false
|
|
|
|
verifyError = false
|
|
|
|
}
|
|
|
|
|
|
|
|
let cleanInstance = instanceUrl
|
|
|
|
.replacingOccurrences(of: "http://", with: "")
|
|
|
|
.replacingOccurrences(of: "https://", with: "")
|
|
|
|
|
2024-02-16 16:21:46 +01:00
|
|
|
if !isInstanceSafe() {
|
|
|
|
if responsability == false && agreedResponsability == false {
|
|
|
|
responsability = true
|
|
|
|
agreedResponsability = false
|
|
|
|
showingResponsability = true
|
|
|
|
|
|
|
|
withAnimation {
|
|
|
|
verifying = false
|
|
|
|
verified = false
|
|
|
|
verifyError = false
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
} else if responsability == true && agreedResponsability == true {
|
|
|
|
showingResponsability = false
|
|
|
|
} else if responsability == true && agreedResponsability == false {
|
|
|
|
showingResponsability = true
|
|
|
|
|
|
|
|
withAnimation {
|
|
|
|
verifying = false
|
|
|
|
verified = false
|
|
|
|
verifyError = false
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
responsability = false
|
|
|
|
agreedResponsability = false
|
|
|
|
UserDefaults.standard.removeObject(forKey: "unsafe")
|
|
|
|
}
|
|
|
|
|
2023-12-29 11:17:37 +01:00
|
|
|
let client = Client(server: cleanInstance)
|
|
|
|
|
|
|
|
Task {
|
|
|
|
do {
|
|
|
|
let instance: Instance = try await client.get(endpoint: Instances.instance)
|
|
|
|
|
|
|
|
withAnimation {
|
|
|
|
instanceInfo = instance
|
|
|
|
verifying = false
|
|
|
|
verified = true
|
|
|
|
verifyError = false
|
|
|
|
}
|
|
|
|
} catch {
|
|
|
|
print(error.localizedDescription)
|
|
|
|
|
|
|
|
withAnimation {
|
|
|
|
verifying = false
|
|
|
|
verified = false
|
|
|
|
verifyError = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func signIn() async {
|
|
|
|
let cleanInstance = instanceUrl
|
|
|
|
.replacingOccurrences(of: "http://", with: "")
|
|
|
|
.replacingOccurrences(of: "https://", with: "")
|
|
|
|
|
|
|
|
signInClient = .init(server: cleanInstance)
|
|
|
|
if let oauthURL = try? await signInClient?.oauthURL(),
|
|
|
|
let url = try? await webAuthenticationSession.authenticate(using: oauthURL, callbackURLScheme: AppInfo.scheme.replacingOccurrences(of: "://", with: "")) {
|
|
|
|
await continueSignIn(url: url)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func continueSignIn(url: URL) async {
|
|
|
|
guard let client = signInClient else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-24 13:21:31 +01:00
|
|
|
if agreedResponsability && responsability && loggedAccounts.count <= 0 {
|
2024-02-16 16:21:46 +01:00
|
|
|
UserDefaults.standard.setValue(true, forKey: "unsafe")
|
|
|
|
} else {
|
|
|
|
UserDefaults.standard.removeObject(forKey: "unsafe")
|
|
|
|
}
|
|
|
|
|
2023-12-29 11:17:37 +01:00
|
|
|
do {
|
|
|
|
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)
|
|
|
|
let appAcc = AppAccount(server: client.server, accountName: "\(account.acct)@\(client.server)", oauthToken: oauthToken)
|
2024-02-17 02:07:03 +01:00
|
|
|
|
|
|
|
let connections: [String] = try await client.get(endpoint: Instances.peers)
|
|
|
|
client.addConnections(connections)
|
|
|
|
|
2024-02-04 08:43:56 +01:00
|
|
|
appAcc.saveAsCurrent()
|
2024-02-11 17:00:29 +01:00
|
|
|
AccountManager.shared.setClient(client)
|
|
|
|
AccountManager.shared.setAccount(account)
|
2023-12-29 11:17:37 +01:00
|
|
|
|
2024-02-24 13:21:31 +01:00
|
|
|
let newLog: LoggedAccount = .init(appAccount: appAcc)
|
|
|
|
modelContext.insert(newLog)
|
|
|
|
|
|
|
|
HapticManager.playHaptics(haptics: Haptic.success)
|
|
|
|
|
2023-12-29 11:17:37 +01:00
|
|
|
signInClient = client
|
|
|
|
logged = true
|
|
|
|
dismiss()
|
|
|
|
} catch {
|
2024-02-24 13:21:31 +01:00
|
|
|
HapticManager.playHaptics(haptics: Haptic.error)
|
2023-12-29 11:17:37 +01:00
|
|
|
print(error)
|
|
|
|
}
|
|
|
|
}
|
2024-02-16 16:21:46 +01:00
|
|
|
|
|
|
|
/// Is the user input instance URL a safe instance
|
|
|
|
/// - returns: True, if the instance isn't consider as dangerous
|
|
|
|
private func isInstanceSafe() -> Bool {
|
|
|
|
let unsafe = blockList.contains(instanceUrl.trimmingCharacters(in: .whitespacesAndNewlines))
|
|
|
|
return !unsafe
|
|
|
|
}
|
2023-12-29 11:17:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#Preview {
|
|
|
|
AddInstanceView(logged: .constant(false))
|
|
|
|
}
|