Implement URL scheme
This commit is contained in:
parent
f71c4964f5
commit
de962a0c09
|
@ -12,6 +12,7 @@ import CoreDataStack
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
import MastodonExtension
|
import MastodonExtension
|
||||||
import MastodonUI
|
import MastodonUI
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
#if PROFILE
|
#if PROFILE
|
||||||
import FPSIndicator
|
import FPSIndicator
|
||||||
|
@ -67,6 +68,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
sceneCoordinator.setup()
|
sceneCoordinator.setup()
|
||||||
window.makeKeyAndVisible()
|
window.makeKeyAndVisible()
|
||||||
|
|
||||||
|
if let urlContext = connectionOptions.urlContexts.first {
|
||||||
|
handleUrl(context: urlContext)
|
||||||
|
}
|
||||||
|
|
||||||
#if SNAPSHOT
|
#if SNAPSHOT
|
||||||
// speedup animation
|
// speedup animation
|
||||||
// window.layer.speed = 999
|
// window.layer.speed = 999
|
||||||
|
@ -187,21 +192,7 @@ extension SceneDelegate {
|
||||||
coordinator.switchToTabBar(tab: .notifications)
|
coordinator.switchToTabBar(tab: .notifications)
|
||||||
|
|
||||||
case "org.joinmastodon.app.new-post":
|
case "org.joinmastodon.app.new-post":
|
||||||
if coordinator?.tabBarController.topMost is ComposeViewController {
|
showComposeViewController()
|
||||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): composing…")
|
|
||||||
} else {
|
|
||||||
if let authContext = coordinator?.authContext {
|
|
||||||
let composeViewModel = ComposeViewModel(
|
|
||||||
context: AppContext.shared,
|
|
||||||
authContext: authContext,
|
|
||||||
destination: .topLevel
|
|
||||||
)
|
|
||||||
_ = coordinator?.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil))
|
|
||||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): present compose scene")
|
|
||||||
} else {
|
|
||||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): not authenticated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "org.joinmastodon.app.search":
|
case "org.joinmastodon.app.search":
|
||||||
coordinator?.switchToTabBar(tab: .search)
|
coordinator?.switchToTabBar(tab: .search)
|
||||||
|
@ -219,4 +210,89 @@ extension SceneDelegate {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
||||||
|
// Determine who sent the URL.
|
||||||
|
if let urlContext = URLContexts.first {
|
||||||
|
handleUrl(context: urlContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showComposeViewController() {
|
||||||
|
if coordinator?.tabBarController.topMost is ComposeViewController {
|
||||||
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): composing…")
|
||||||
|
} else {
|
||||||
|
if let authContext = coordinator?.authContext {
|
||||||
|
let composeViewModel = ComposeViewModel(
|
||||||
|
context: AppContext.shared,
|
||||||
|
authContext: authContext,
|
||||||
|
destination: .topLevel
|
||||||
|
)
|
||||||
|
_ = coordinator?.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil))
|
||||||
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): present compose scene")
|
||||||
|
} else {
|
||||||
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): not authenticated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleUrl(context: UIOpenURLContext) {
|
||||||
|
let sendingAppID = context.options.sourceApplication
|
||||||
|
let url = context.url
|
||||||
|
|
||||||
|
if !UIApplication.shared.canOpenURL(url) { return }
|
||||||
|
|
||||||
|
print("source application = \(sendingAppID ?? "Unknown")")
|
||||||
|
print("url = \(url)")
|
||||||
|
|
||||||
|
if let username = url.user {
|
||||||
|
guard let host = url.host else { return }
|
||||||
|
let components = url.pathComponents
|
||||||
|
if components.count == 3 && components[1] == "status" {
|
||||||
|
let statusId = components[2]
|
||||||
|
// View post from user
|
||||||
|
print("view status \(statusId)")
|
||||||
|
if let authContext = coordinator?.authContext {
|
||||||
|
Task {
|
||||||
|
guard let thread = try await AppContext.shared.apiService.fetchThread(
|
||||||
|
statusID: statusId,
|
||||||
|
domain: host,
|
||||||
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
|
) else { return }
|
||||||
|
|
||||||
|
let threadViewModel = CachedThreadViewModel(context: AppContext.shared,
|
||||||
|
authContext: authContext,
|
||||||
|
status: thread)
|
||||||
|
coordinator?.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("view profile \(username)@\(host)")
|
||||||
|
if let authContext = coordinator?.authContext {
|
||||||
|
Task { @MainActor in
|
||||||
|
guard let user = try await AppContext.shared.apiService.fetchUser(
|
||||||
|
username: username,
|
||||||
|
domain: host,
|
||||||
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
|
) else { return }
|
||||||
|
|
||||||
|
let profileViewModel = RemoteProfileViewModel(context: AppContext.shared,
|
||||||
|
authContext: authContext,
|
||||||
|
userID: user.id)
|
||||||
|
self.coordinator?.present(
|
||||||
|
scene: .profile(viewModel: profileViewModel),
|
||||||
|
from: nil,
|
||||||
|
transition: .show
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
guard let action = url.host else { return }
|
||||||
|
if action == "post" {
|
||||||
|
print("make post")
|
||||||
|
showComposeViewController()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import os.log
|
import os.log
|
||||||
|
import CoreDataStack
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import CommonOSLog
|
import CommonOSLog
|
||||||
|
@ -199,3 +200,43 @@ extension APIService {
|
||||||
return response
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension APIService {
|
||||||
|
public func fetchUser(username: String, domain: String, authenticationBox: MastodonAuthenticationBox)
|
||||||
|
async throws -> MastodonUser? {
|
||||||
|
let query = Mastodon.API.Account.AccountLookupQuery(acct: "\(username)@\(domain)")
|
||||||
|
let authorization = authenticationBox.userAuthorization
|
||||||
|
|
||||||
|
let response = try await Mastodon.API.Account.lookupAccount(
|
||||||
|
session: session,
|
||||||
|
domain: authenticationBox.domain,
|
||||||
|
query: query,
|
||||||
|
authorization: authorization
|
||||||
|
).singleOutput()
|
||||||
|
|
||||||
|
// user
|
||||||
|
let managedObjectContext = self.backgroundManagedObjectContext
|
||||||
|
try await managedObjectContext.performChanges {
|
||||||
|
_ = Persistence.MastodonUser.createOrMerge(
|
||||||
|
in: managedObjectContext,
|
||||||
|
context: Persistence.MastodonUser.PersistContext(
|
||||||
|
domain: domain,
|
||||||
|
entity: response.value,
|
||||||
|
cache: nil,
|
||||||
|
networkDate: response.networkDate
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var result: MastodonUser?
|
||||||
|
try await managedObjectContext.perform {
|
||||||
|
result = Persistence.MastodonUser.fetch(in: managedObjectContext,
|
||||||
|
context: Persistence.MastodonUser.PersistContext(
|
||||||
|
domain: domain,
|
||||||
|
entity: response.value,
|
||||||
|
cache: nil,
|
||||||
|
networkDate: response.networkDate
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,9 +16,10 @@ extension APIService {
|
||||||
|
|
||||||
public func statusContext(
|
public func statusContext(
|
||||||
statusID: Mastodon.Entity.Status.ID,
|
statusID: Mastodon.Entity.Status.ID,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox,
|
||||||
|
domain: String? = nil
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Context> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Context> {
|
||||||
let domain = authenticationBox.domain
|
let domain = domain ?? authenticationBox.domain
|
||||||
let authorization = authenticationBox.userAuthorization
|
let authorization = authenticationBox.userAuthorization
|
||||||
|
|
||||||
let response = try await Mastodon.API.Statuses.statusContext(
|
let response = try await Mastodon.API.Statuses.statusContext(
|
||||||
|
@ -51,4 +52,79 @@ extension APIService {
|
||||||
return response
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
|
|
||||||
|
public func fetchThread(
|
||||||
|
statusID: Mastodon.Entity.Status.ID,
|
||||||
|
domain: String,
|
||||||
|
authenticationBox: MastodonAuthenticationBox
|
||||||
|
) async throws -> Status? {
|
||||||
|
let authorization = authenticationBox.userAuthorization
|
||||||
|
let managedObjectContext = self.backgroundManagedObjectContext
|
||||||
|
|
||||||
|
let responseOne = try await Mastodon.API.Statuses.status(
|
||||||
|
session: session,
|
||||||
|
domain: domain,
|
||||||
|
statusID: statusID,
|
||||||
|
authorization: authorization
|
||||||
|
).singleOutput()
|
||||||
|
|
||||||
|
try await managedObjectContext.performChanges {
|
||||||
|
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||||
|
_ = Persistence.Status.createOrMerge(
|
||||||
|
in: managedObjectContext,
|
||||||
|
context: Persistence.Status.PersistContext(
|
||||||
|
domain: domain,
|
||||||
|
entity: responseOne.value,
|
||||||
|
me: me,
|
||||||
|
statusCache: nil,
|
||||||
|
userCache: nil,
|
||||||
|
networkDate: responseOne.networkDate
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let responseTwo = try await Mastodon.API.Statuses.statusContext(
|
||||||
|
session: session,
|
||||||
|
domain: domain,
|
||||||
|
statusID: statusID,
|
||||||
|
authorization: authorization
|
||||||
|
).singleOutput()
|
||||||
|
|
||||||
|
try await managedObjectContext.performChanges {
|
||||||
|
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||||
|
let value = responseTwo.value.ancestors + responseTwo.value.descendants
|
||||||
|
|
||||||
|
for entity in value {
|
||||||
|
_ = Persistence.Status.createOrMerge(
|
||||||
|
in: managedObjectContext,
|
||||||
|
context: Persistence.Status.PersistContext(
|
||||||
|
domain: domain,
|
||||||
|
entity: entity,
|
||||||
|
me: me,
|
||||||
|
statusCache: nil,
|
||||||
|
userCache: nil,
|
||||||
|
networkDate: responseTwo.networkDate
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result: Status?
|
||||||
|
try await managedObjectContext.perform {
|
||||||
|
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||||
|
|
||||||
|
if let status = Persistence.Status.fetch(in: managedObjectContext,
|
||||||
|
context: Persistence.Status.PersistContext(
|
||||||
|
domain: domain,
|
||||||
|
entity: responseOne.value,
|
||||||
|
me: me,
|
||||||
|
statusCache: nil,
|
||||||
|
userCache: nil,
|
||||||
|
networkDate: responseOne.networkDate
|
||||||
|
)) {
|
||||||
|
result = status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue