Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Flipcash/Core/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
object: nil
)

NotificationCenter.default.addObserver(
self,
selector: #selector(handleDeepLinkNotification(_:)),
name: .shortcutDeepLinkReceived,
object: nil
)

return true
}

/// Routes scene events to ``SceneDelegate``. SwiftUI's `App` lifecycle
/// uses its own implicit scene delegate by default, swallowing
/// `windowScene(_:performActionFor:)`; without this method, the
/// `UISceneDelegateClassName` declared in `Info.plist` is not honored
/// and quick actions are dropped.
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
let config = UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
config.delegateClass = SceneDelegate.self
return config
}

// MARK: - Lifecycle -

func scenePhaseChanged(_ phase: ScenePhase) {
Expand Down
37 changes: 35 additions & 2 deletions Flipcash/Core/Controllers/Deep Links/DeepLinkController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,20 @@ final class DeepLinkController {

case .token(let mint):
return actionForCurrencyInfo(mint: mint)


case .give:
return actionForOpenSheet(.give)

case .wallet:
return actionForOpenSheet(.balance)

case .discover:
return actionForOpenSheet(.discover)

case .unknown:
break
}

return nil
}

Expand Down Expand Up @@ -134,6 +143,13 @@ final class DeepLinkController {
sessionAuthenticator: sessionAuthenticator
)
}

private func actionForOpenSheet(_ sheet: AppRouter.SheetPresentation) -> DeepLinkAction {
DeepLinkAction(
kind: .openSheet(sheet),
sessionAuthenticator: sessionAuthenticator
)
}
}

extension DeepLinkController {
Expand Down Expand Up @@ -212,6 +228,21 @@ struct DeepLinkAction {
Analytics.deeplinkRouted(kind: kind)
container.appRouter.navigate(to: .currencyInfo(mint))
}

case .openSheet(let sheet):
if case .loggedIn(let container) = sessionAuthenticator.state {
Analytics.deeplinkRouted(kind: kind)
if sheet == .give {
let rate = container.ratesController.rateForBalanceCurrency()
guard container.session.hasGiveableBalance(for: rate) else {
container.session.dialogItem = .noGiveableBalance(
onDiscover: { container.appRouter.present(.discover) }
)
return
}
}
container.appRouter.present(sheet)
}
}
}
}
Expand All @@ -224,6 +255,7 @@ extension DeepLinkAction {
case receiveCashLink(MnemonicPhrase)
case verifyEmail(VerificationDescription)
case currencyInfo(PublicKey)
case openSheet(AppRouter.SheetPresentation)
}
}

Expand All @@ -234,6 +266,7 @@ extension DeepLinkAction.Kind {
case .receiveCashLink: "CashLink"
case .verifyEmail: "EmailVerification"
case .currencyInfo: "TokenInfo"
case .openSheet(let sheet): "Sheet:\(sheet)"
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions Flipcash/Core/Controllers/Deep Links/Route.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ nonisolated extension Route {
case cash
case verifyEmail
case token(PublicKey)
case give
case wallet
case discover
case unknown(String)

static func parse(path: String) -> Path? {
Expand All @@ -111,6 +114,12 @@ nonisolated extension Route {
return nil
}
return .token(mint)
case "give":
return .give
case "wallet":
return .wallet
case "discover":
return .discover
default:
return .unknown(url.lastPathComponent)
}
Expand Down
1 change: 1 addition & 0 deletions Flipcash/Core/Controllers/NotificationController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,6 @@ extension NSNotification.Name {
static let pushNotificationWillPresent = Notification.Name("com.code.pushNotificationWillPresent")
static let pushDeepLinkReceived = Notification.Name("com.code.pushDeepLinkReceived")
static let qrDeepLinkReceived = Notification.Name("com.code.qrDeepLinkReceived")
static let shortcutDeepLinkReceived = Notification.Name("com.code.shortcutDeepLinkReceived")
static let messageNotificationReceived = Notification.Name("com.code.messageNotificationReceived")
}
43 changes: 43 additions & 0 deletions Flipcash/Core/SceneDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// SceneDelegate.swift
// Flipcash
//

import UIKit
import FlipcashCore

private let logger = Logger(label: "flipcash.scene-delegate")

/// Bridges quick-action taps into the existing deep-link pipeline. SwiftUI's
/// `App` lifecycle doesn't forward `UIApplicationShortcutItem` events to
/// `AppDelegate`, so we receive them here, pull the embedded URL out of the
/// shortcut's `userInfo`, and post the same notification the rest of the
/// app's URL handlers already observe.
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let shortcut = connectionOptions.shortcutItem {
postDeepLink(for: shortcut)
}
}

func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
completionHandler(postDeepLink(for: shortcutItem))
}

@discardableResult
private func postDeepLink(for shortcut: UIApplicationShortcutItem) -> Bool {
guard let urlString = shortcut.userInfo?["url"] as? String,
let url = URL(string: urlString) else {
logger.warning("Quick action missing url userInfo", metadata: ["type": "\(shortcut.type)"])
return false
}
NotificationCenter.default.post(
name: .shortcutDeepLinkReceived,
object: nil,
userInfo: ["url": url]
)
return true
}
}
2 changes: 1 addition & 1 deletion Flipcash/Core/Screens/Main/ScanViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class ScanViewModel {
switch route.path {
case .cash, .token:
return true
case .login, .verifyEmail, .unknown:
case .login, .verifyEmail, .give, .wallet, .discover, .unknown:
return false
}
}
Expand Down
1 change: 0 additions & 1 deletion Flipcash/Core/Session/SessionAuthenticator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,6 @@ struct SessionContainer {
.environment(onrampCoordinator)
.environment(onrampDeeplinkInbox)
}

}

extension View {
Expand Down
59 changes: 59 additions & 0 deletions Flipcash/Supporting Files/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,65 @@
</array>
<key>SQLiteVersion</key>
<integer>11</integer>
<key>UIApplicationShortcutItems</key>
<array>
<dict>
<key>UIApplicationShortcutItemType</key>
<string>com.flipcash.shortcut.give</string>
<key>UIApplicationShortcutItemTitle</key>
<string>Give</string>
<key>UIApplicationShortcutItemIconSymbolName</key>
<string>banknote</string>
<key>UIApplicationShortcutItemUserInfo</key>
<dict>
<key>url</key>
<string>flipcash://give</string>
</dict>
</dict>
<dict>
<key>UIApplicationShortcutItemType</key>
<string>com.flipcash.shortcut.wallet</string>
<key>UIApplicationShortcutItemTitle</key>
<string>Wallet</string>
<key>UIApplicationShortcutItemIconSymbolName</key>
<string>wallet.bifold</string>
<key>UIApplicationShortcutItemUserInfo</key>
<dict>
<key>url</key>
<string>flipcash://wallet</string>
</dict>
</dict>
<dict>
<key>UIApplicationShortcutItemType</key>
<string>com.flipcash.shortcut.discover</string>
<key>UIApplicationShortcutItemTitle</key>
<string>Discover</string>
<key>UIApplicationShortcutItemIconSymbolName</key>
<string>binoculars</string>
<key>UIApplicationShortcutItemUserInfo</key>
<dict>
<key>url</key>
<string>flipcash://discover</string>
</dict>
</dict>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UILaunchScreen</key>
<dict>
<key>UIColorName</key>
Expand Down