forked from shadowfacts/Tusker
Start OAuth
This commit is contained in:
parent
8526613189
commit
8d268fad18
|
@ -1,6 +1,6 @@
|
||||||
[submodule "MastodonKit"]
|
[submodule "MastodonKit"]
|
||||||
path = MastodonKit
|
path = MastodonKit
|
||||||
url = git://github.com/MastodonKit/MastodonKit.git
|
url = git://github.com/shadowfacts/MastodonKit.git
|
||||||
[submodule "SwiftSoup"]
|
[submodule "SwiftSoup"]
|
||||||
path = SwiftSoup
|
path = SwiftSoup
|
||||||
url = git://github.com/scinfu/SwiftSoup.git
|
url = git://github.com/scinfu/SwiftSoup.git
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 43144bf87cea83c81ad899fc0488be3eae645a01
|
Subproject commit 6a03c64b6788faf5915c2918d429e5031af04fe6
|
|
@ -7,6 +7,9 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
|
||||||
|
D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */; };
|
||||||
|
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
||||||
D6BED16F212663DA00F02DA0 /* SwiftSoup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; };
|
D6BED16F212663DA00F02DA0 /* SwiftSoup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; };
|
||||||
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; };
|
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; };
|
||||||
|
@ -57,6 +60,9 @@
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
||||||
|
D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = "<group>"; };
|
||||||
|
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
|
||||||
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
|
D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6D4DDCC212518A000E1C4BB /* Tusker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tusker.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
D6D4DDCC212518A000E1C4BB /* Tusker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tusker.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -139,6 +145,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
||||||
|
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
||||||
D6F953F121251A2F00CF0F2B /* Controllers */,
|
D6F953F121251A2F00CF0F2B /* Controllers */,
|
||||||
D6F953E9212519B800CF0F2B /* View Controllers */,
|
D6F953E9212519B800CF0F2B /* View Controllers */,
|
||||||
D6BED1722126661300F02DA0 /* Views */,
|
D6BED1722126661300F02DA0 /* Views */,
|
||||||
|
@ -173,6 +180,7 @@
|
||||||
children = (
|
children = (
|
||||||
D6D4DDD1212518A000E1C4BB /* ViewController.swift */,
|
D6D4DDD1212518A000E1C4BB /* ViewController.swift */,
|
||||||
D6F953EB212519E700CF0F2B /* StatusesTableViewController.swift */,
|
D6F953EB212519E700CF0F2B /* StatusesTableViewController.swift */,
|
||||||
|
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */,
|
||||||
);
|
);
|
||||||
path = "View Controllers";
|
path = "View Controllers";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -182,6 +190,7 @@
|
||||||
children = (
|
children = (
|
||||||
D6D4DDD3212518A000E1C4BB /* Main.storyboard */,
|
D6D4DDD3212518A000E1C4BB /* Main.storyboard */,
|
||||||
D6F953ED21251A0700CF0F2B /* Statuses.storyboard */,
|
D6F953ED21251A0700CF0F2B /* Statuses.storyboard */,
|
||||||
|
D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */,
|
||||||
);
|
);
|
||||||
path = Storyboards;
|
path = Storyboards;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -300,6 +309,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */,
|
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */,
|
||||||
|
D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */,
|
||||||
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */,
|
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */,
|
||||||
D6F953EE21251A0700CF0F2B /* Statuses.storyboard in Resources */,
|
D6F953EE21251A0700CF0F2B /* Statuses.storyboard in Resources */,
|
||||||
D6D4DDD5212518A000E1C4BB /* Main.storyboard in Resources */,
|
D6D4DDD5212518A000E1C4BB /* Main.storyboard in Resources */,
|
||||||
|
@ -330,6 +340,8 @@
|
||||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
||||||
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */,
|
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */,
|
||||||
D6D4DDD2212518A000E1C4BB /* ViewController.swift in Sources */,
|
D6D4DDD2212518A000E1C4BB /* ViewController.swift in Sources */,
|
||||||
|
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
||||||
|
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
|
||||||
D6F953EC212519E700CF0F2B /* StatusesTableViewController.swift in Sources */,
|
D6F953EC212519E700CF0F2B /* StatusesTableViewController.swift in Sources */,
|
||||||
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */,
|
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|
|
@ -13,15 +13,42 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
// Override point for customization after application launch.
|
// Override point for customization after application launch.
|
||||||
|
|
||||||
MastodonController.shared.connect()
|
// MastodonController.shared.connect()
|
||||||
|
|
||||||
|
if LocalData.shared.hasLaunchedBefore {
|
||||||
|
MastodonController.shared.createClient() {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showOnboarding()
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
|
||||||
|
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return false }
|
||||||
|
|
||||||
|
print("opened with url: \(url)")
|
||||||
|
|
||||||
|
if components.host == "oauth" {
|
||||||
|
let code = components.queryItems?.first {
|
||||||
|
$0.name == "code"
|
||||||
|
}
|
||||||
|
if let authCode = code?.value {
|
||||||
|
// LocalData.shared.refreshToken = refreshToken
|
||||||
|
MastodonController.shared.authorize(authorizationCode: authCode) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func applicationWillResignActive(_ application: UIApplication) {
|
func applicationWillResignActive(_ application: UIApplication) {
|
||||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||||
|
@ -44,6 +71,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppDelegate: OnboardingViewControllerDelegate {
|
||||||
|
|
||||||
|
func showOnboarding() {
|
||||||
|
if let window = self.window,
|
||||||
|
let onboardingViewController = UIStoryboard(name: "Onboarding", bundle: nil).instantiateInitialViewController() as? OnboardingViewController {
|
||||||
|
|
||||||
|
onboardingViewController.delegate = self
|
||||||
|
window.makeKeyAndVisible()
|
||||||
|
window.rootViewController?.present(onboardingViewController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hideOnboarding() {
|
||||||
|
if let window = UIApplication.shared.keyWindow {
|
||||||
|
window.rootViewController?.dismiss(animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func didFinishOnboarding() {
|
||||||
|
hideOnboarding()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,62 +13,81 @@ class MastodonController {
|
||||||
|
|
||||||
static let shared = MastodonController()
|
static let shared = MastodonController()
|
||||||
|
|
||||||
var userDefaults = UserDefaults()
|
// var userDefaults = UserDefaults()
|
||||||
|
|
||||||
var client: Client!
|
var client: Client!
|
||||||
|
|
||||||
lazy var clientID: String? = self.userDefaults.string(forKey: "clientID")
|
// lazy var clientID: String? = self.userDefaults.string(forKey: "clientID")
|
||||||
lazy var clientSecret: String? = self.userDefaults.string(forKey: "clientSecret")
|
// lazy var clientSecret: String? = self.userDefaults.string(forKey: "clientSecret")
|
||||||
|
//
|
||||||
lazy var accessToken: String? = self.userDefaults.string(forKey: "accessToken")
|
// lazy var accessToken: String? = self.userDefaults.string(forKey: "accessToken")
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func connect() {
|
func createClient(completion: @escaping () -> Void) {
|
||||||
let url = ProcessInfo.processInfo.environment["mastodon_url"]!
|
guard let url = LocalData.shared.instanceURL else { fatalError("Can't connect without instance URL") }
|
||||||
|
|
||||||
if let accessToken = accessToken {
|
|
||||||
client = Client(baseURL: url, accessToken: accessToken)
|
|
||||||
} else {
|
|
||||||
client = Client(baseURL: url)
|
client = Client(baseURL: url)
|
||||||
|
|
||||||
login()
|
if let refreshToken = LocalData.shared.refreshToken {
|
||||||
|
// client.accessToken = accessToken
|
||||||
|
// completion()
|
||||||
|
authorize(authorizationCode: refreshToken, completion: completion)
|
||||||
|
} else {
|
||||||
|
register(completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func register(completion: @escaping () -> Void) {
|
private func register(completion: @escaping () -> Void) {
|
||||||
if clientID != nil,
|
guard LocalData.shared.clientID == nil,
|
||||||
clientSecret != nil {
|
LocalData.shared.clientSecret == nil else {
|
||||||
completion()
|
completion()
|
||||||
} else {
|
return
|
||||||
let registerRequest = Clients.register(clientName: "Tusker", scopes: [.read, .write, .follow])
|
}
|
||||||
|
|
||||||
|
let registerRequest = Clients.register(clientName: "Tusker", redirectURI: "tusker://oauth", scopes: [.read, .write, .follow])
|
||||||
|
|
||||||
client.run(registerRequest) { result in
|
client.run(registerRequest) { result in
|
||||||
guard case let .success(application, _) = result else { fatalError() }
|
guard case let .success(application, _) = result else { fatalError() }
|
||||||
self.clientID = application.clientID
|
LocalData.shared.clientID = application.clientID
|
||||||
self.clientSecret = application.clientSecret
|
LocalData.shared.clientSecret = application.clientSecret
|
||||||
self.userDefaults.set(self.clientID, forKey: "clientID")
|
|
||||||
self.userDefaults.set(self.clientSecret, forKey: "clientSecret")
|
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private func login() {
|
func authorize(authorizationCode: String, completion: @escaping () -> Void) {
|
||||||
// TODO: OAuth
|
// let parameters = [
|
||||||
let username = ProcessInfo.processInfo.environment["mastodon_username"]!
|
// Parameter(name: "client_id", value: LocalData.shared.clientID),
|
||||||
let password = ProcessInfo.processInfo.environment["mastodon_password"]!
|
// Parameter(name: "client_secret", value: LocalData.shared.clientSecret),
|
||||||
|
// Parameter(name: "grant_type", value: "refresh_token"),
|
||||||
register() {
|
// Parameter(name: "refresh_token", value: LocalData.shared.refreshToken)
|
||||||
let loginReq = Login.silent(clientID: self.clientID!, clientSecret: self.clientSecret!, scopes: [.read, .write, .follow], username: username, password: password)
|
// ]
|
||||||
|
// let method = HTTPMethod.post(.parameters(parameters))
|
||||||
self.client.run(loginReq) { result in
|
let authorizeRequest = Login.authorize(code: authorizationCode, clientID: LocalData.shared.clientID!, clientSecret: LocalData.shared.clientSecret!)
|
||||||
guard case let .success(loginSettings, _) = result else { fatalError() }
|
client.run(authorizeRequest) { result in
|
||||||
self.accessToken = loginSettings.accessToken
|
guard case let .success(settings, _) = result else { fatalError() }
|
||||||
self.userDefaults.set(self.accessToken, forKey: "accessToken")
|
LocalData.shared.refreshToken = settings.refreshToken
|
||||||
}
|
LocalData.shared.accessToken = settings.accessToken
|
||||||
|
self.client.accessToken = settings.accessToken
|
||||||
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// private func login() {
|
||||||
|
// // TODO: OAuth
|
||||||
|
// let username = ProcessInfo.processInfo.environment["mastodon_username"]!
|
||||||
|
// let password = ProcessInfo.processInfo.environment["mastodon_password"]!
|
||||||
|
//
|
||||||
|
// register() {
|
||||||
|
// let loginReq = Login.silent(clientID: self.clientID!, clientSecret: self.clientSecret!, scopes: [.read, .write, .follow], username: username, password: password)
|
||||||
|
//
|
||||||
|
// self.client.run(loginReq) { result in
|
||||||
|
// guard case let .success(loginSettings, _) = result else { fatalError() }
|
||||||
|
// self.accessToken = loginSettings.accessToken
|
||||||
|
// self.userDefaults.set(self.accessToken, forKey: "accessToken")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,19 @@
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>net.shadowfacts.Tusker</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>tusker</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
//
|
||||||
|
// LocalData.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 8/18/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class LocalData {
|
||||||
|
|
||||||
|
static let shared = LocalData()
|
||||||
|
|
||||||
|
let defaults = UserDefaults()
|
||||||
|
|
||||||
|
private let hasLaunchedBeforeKey = "hasLaunchedBefore"
|
||||||
|
var hasLaunchedBefore: Bool {
|
||||||
|
get {
|
||||||
|
return defaults.bool(forKey: hasLaunchedBeforeKey)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
defaults.set(newValue, forKey: hasLaunchedBeforeKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let instanceURLKey = "instanceURL"
|
||||||
|
var instanceURL: String? {
|
||||||
|
get {
|
||||||
|
return defaults.string(forKey: instanceURLKey)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
defaults.set(newValue, forKey: instanceURLKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let clientIDKey = "clientID"
|
||||||
|
var clientID: String? {
|
||||||
|
get {
|
||||||
|
return defaults.string(forKey: clientIDKey)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
defaults.set(newValue, forKey: clientIDKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let clientSecretKey = "clientSecret"
|
||||||
|
var clientSecret: String? {
|
||||||
|
get {
|
||||||
|
return defaults.string(forKey: clientSecretKey)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
defaults.set(newValue, forKey: clientSecretKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let refreshTokenKey = "refreshToken"
|
||||||
|
var refreshToken: String? {
|
||||||
|
get {
|
||||||
|
return defaults.string(forKey: refreshTokenKey)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
defaults.set(newValue, forKey: refreshTokenKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let accessTokenKey = "accessToken"
|
||||||
|
var accessToken: String? {
|
||||||
|
get {
|
||||||
|
return defaults.string(forKey: accessTokenKey)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
defaults.set(newValue, forKey: accessTokenKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.13.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="xbJ-7P-Ihc">
|
||||||
|
<device id="retina4_7" orientation="portrait">
|
||||||
|
<adaptation id="fullscreen"/>
|
||||||
|
</device>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.9"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Onboarding View Controller-->
|
||||||
|
<scene sceneID="3hu-e1-Uan">
|
||||||
|
<objects>
|
||||||
|
<viewController id="xbJ-7P-Ihc" customClass="OnboardingViewController" customModule="Tusker" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" id="ZMe-1q-zVz">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="aEz-P4-y4f">
|
||||||
|
<rect key="frame" x="41.5" y="248.5" width="292.5" height="170.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enter your instance URL to get started" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4h2-ae-nVd">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="292.5" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="https://mastodon.social" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="UEc-G9-MKo">
|
||||||
|
<rect key="frame" x="0.0" y="40.5" width="292.5" height="30"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
|
<textInputTraits key="textInputTraits"/>
|
||||||
|
</textField>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="D3p-9x-aDh">
|
||||||
|
<rect key="frame" x="0.0" y="90.5" width="292.5" height="30"/>
|
||||||
|
<state key="normal" title="Login"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="loginPressed:" destination="xbJ-7P-Ihc" eventType="touchUpInside" id="Fs9-m3-flX"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="peM-5x-Puc">
|
||||||
|
<rect key="frame" x="0.0" y="140.5" width="292.5" height="30"/>
|
||||||
|
<state key="normal" title="Clear Data"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="clearDataPressed:" destination="xbJ-7P-Ihc" eventType="touchUpInside" id="qjw-j7-p7m"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="aEz-P4-y4f" firstAttribute="centerY" secondItem="ZMe-1q-zVz" secondAttribute="centerY" id="KG2-wQ-pZk"/>
|
||||||
|
<constraint firstItem="aEz-P4-y4f" firstAttribute="centerX" secondItem="ZMe-1q-zVz" secondAttribute="centerX" id="M0Y-OB-8Rx"/>
|
||||||
|
</constraints>
|
||||||
|
<viewLayoutGuide key="safeArea" id="lgk-Lx-3MP"/>
|
||||||
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="urlTextField" destination="UEc-G9-MKo" id="ui8-wT-DaN"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="ilw-Uq-lDu" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="-257" y="81"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
|
@ -0,0 +1,93 @@
|
||||||
|
//
|
||||||
|
// OnboardingViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 8/18/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import AuthenticationServices
|
||||||
|
|
||||||
|
protocol OnboardingViewControllerDelegate {
|
||||||
|
|
||||||
|
func didFinishOnboarding()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class OnboardingViewController: UIViewController {
|
||||||
|
|
||||||
|
var delegate: OnboardingViewControllerDelegate?
|
||||||
|
|
||||||
|
@IBOutlet weak var urlTextField: UITextField!
|
||||||
|
|
||||||
|
var authenticationSession: ASWebAuthenticationSession?
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func loginPressed(_ sender: Any) {
|
||||||
|
guard let text = urlTextField.text,
|
||||||
|
var components = URLComponents(string: text) else { return }
|
||||||
|
|
||||||
|
LocalData.shared.instanceURL = text
|
||||||
|
MastodonController.shared.createClient {
|
||||||
|
let clientID = LocalData.shared.clientID!
|
||||||
|
|
||||||
|
let callbackURL = "tusker://oauth"
|
||||||
|
|
||||||
|
components.path = "/oauth/authorize"
|
||||||
|
components.queryItems = [
|
||||||
|
URLQueryItem(name: "client_id", value: clientID),
|
||||||
|
URLQueryItem(name: "response_type", value: "code"),
|
||||||
|
URLQueryItem(name: "scope", value: "read write follow"),
|
||||||
|
URLQueryItem(name: "redirect_uri", value: callbackURL)
|
||||||
|
]
|
||||||
|
let url = components.url!
|
||||||
|
|
||||||
|
print("oauth url: \(url)")
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.delegate?.didFinishOnboarding()
|
||||||
|
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// self.delegate?.didFinishOnboarding()
|
||||||
|
// self.authenticationSession = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackURL) { url, error in
|
||||||
|
// guard error == nil,
|
||||||
|
// let url = url,
|
||||||
|
// let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { fatalError() }
|
||||||
|
//
|
||||||
|
// print("callback url: \(url)")
|
||||||
|
//
|
||||||
|
// let item = components.queryItems?.first { $0.name == "code" }
|
||||||
|
// if let accessToken = item?.value {
|
||||||
|
// LocalData.shared.accessToken = accessToken
|
||||||
|
// MastodonController.shared.client.accessToken = accessToken
|
||||||
|
// self.delegate?.didFinishOnboarding()
|
||||||
|
// self.authenticationSession = nil
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// self.authenticationSession!.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func clearDataPressed(_ sender: Any) {
|
||||||
|
LocalData.shared.instanceURL = nil
|
||||||
|
LocalData.shared.clientID = nil
|
||||||
|
LocalData.shared.clientSecret = nil
|
||||||
|
LocalData.shared.refreshToken = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// MARK: - Navigation
|
||||||
|
|
||||||
|
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
||||||
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
|
// Get the new view controller using segue.destination.
|
||||||
|
// Pass the selected object to the new view controller.
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ class StatusesTableViewController: UITableViewController {
|
||||||
var older: RequestRange?
|
var older: RequestRange?
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
guard MastodonController.shared.client?.accessToken != nil else { return }
|
||||||
MastodonController.shared.client.run(Timelines.home()) { result in
|
MastodonController.shared.client.run(Timelines.home()) { result in
|
||||||
guard case let .success(statuses, pagination) = result else { fatalError() }
|
guard case let .success(statuses, pagination) = result else { fatalError() }
|
||||||
self.statuses = statuses
|
self.statuses = statuses
|
||||||
|
|
Loading…
Reference in New Issue