Skip to content

Commit

Permalink
TRCL-3689 Add primer screen to prompt the user to enable notification (
Browse files Browse the repository at this point in the history
…#251)

* WIP

* WIP

* Settings

* Lint

* Clean up

* Clean up

* Code review feedback

* Clean up
  • Loading branch information
ruixhuang committed Sep 17, 2024
1 parent ae7a6d7 commit 453dcd9
Show file tree
Hide file tree
Showing 24 changed files with 321 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
import ParticlesKit
import Utilities

public class NotificationPermissionActionBuilder: NSObject, ObjectBuilderProtocol {
public func build<T>() -> T? {
let action = NotificationPermissionAction()
return action as? T
}
}

public class NotificationPermissionAction: PrivacyPermissionAction {
override public var primer: String? {
return "/primer/notification"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"destination":"LocationPermissionAction.xib"
},
"/authorization/notification":{
"destination":"NotificationPermissionAction.xib"
"destination":"PlatformParticles.NotificationPermissionActionBuilder"
},
"/authorization/photoalbum":{
"destination":"PhotoAlbumsPermissionAction.xib"
Expand Down Expand Up @@ -89,7 +89,7 @@
"presentation":"half"
},
"/primer/notification":{
"destination":"PrimerNotification.storyboard",
"destination":"dydxPresenters.dydxShareActionBuilder",
"presentation":"prompt"
},
"/primer/photoalbum":{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ open class SettingsViewPresenter: BaseSettingsViewPresenter {
loadSettings()
}

private func loadSettings() {
public func loadSettings() {
var sections = [SettingsViewModel.SectionViewModel]()
for fieldList in fieldLists ?? [] {
sections.append(createSection(group: fieldList))
Expand Down Expand Up @@ -68,6 +68,7 @@ open class SettingsViewPresenter: BaseSettingsViewPresenter {
self?.keyValueStore?.setValue(value, forKey: fieldName)
}
self?.loadSettings()
self?.onInputValueChanged(input: input)
}

if let xib = fieldInputDefinition.xib {
Expand Down Expand Up @@ -157,4 +158,8 @@ open class SettingsViewPresenter: BaseSettingsViewPresenter {
}
return textViewModel
}

open func onInputValueChanged(input: FieldInput) {

}
}
5 changes: 1 addition & 4 deletions Shared/CommonAppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,10 @@ open class CommonAppDelegate: ParticlesAppDelegate {
return "firebase"
}

private lazy var firebaseNotification: FirebaseNotificationHandler = {
open lazy var firebaseNotification: FirebaseNotificationHandler = {
return FirebaseNotificationHandler(tag: notificationTag)
}()

private let notificationHandlerDelegate = dydxNotificationHandlerDelegate()

override open func inject(completion: @escaping () -> Void) {
super.inject { [weak self] in
self?.injectUX(completion: completion)
Expand Down Expand Up @@ -186,7 +184,6 @@ open class CommonAppDelegate: ParticlesAppDelegate {
injectURLHandler()
super.startup { [weak self] in
self?.injectNotification()
self?.firebaseNotification.delegate = self?.notificationHandlerDelegate
completion()
}
}
Expand Down
16 changes: 16 additions & 0 deletions dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
023AB3C42BEAD56A005230B2 /* dydxMarginModeViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023AB3C32BEAD56A005230B2 /* dydxMarginModeViewBuilder.swift */; };
023AB3C82BEAD5F3005230B2 /* dydxTargetLeverageViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023AB3C72BEAD5F3005230B2 /* dydxTargetLeverageViewBuilder.swift */; };
023DC88029CCBD21000DD920 /* dydxOnboardQRCodeViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023DC87329CCBD21000DD920 /* dydxOnboardQRCodeViewBuilder.swift */; };
023FC7E32C94DCBF003D77A6 /* dydxPushNotificationToggleWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023FC7E22C94DCBF003D77A6 /* dydxPushNotificationToggleWorker.swift */; };
02439CCB29B03EEE00A083FE /* dydxCartera.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02439CC629B03ECD00A083FE /* dydxCartera.framework */; };
0243A76129BE572C00A083FE /* dydxCancelOrderActionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0243A76029BE572C00A083FE /* dydxCancelOrderActionBuilder.swift */; };
02465B96297F6E2E00A4CA55 /* dydxPortfolioHeaderPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02465B95297F6E2E00A4CA55 /* dydxPortfolioHeaderPresenter.swift */; };
Expand Down Expand Up @@ -104,6 +105,7 @@
02D1345828ECF30000B46941 /* dydxMarketsSearchViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D1345728ECF30000B46941 /* dydxMarketsSearchViewBuilder.swift */; };
02D1CEE129C8DEA7009ADF9A /* dydxLanguageViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D1CEE029C8DEA7009ADF9A /* dydxLanguageViewBuilder.swift */; };
02D1CEED29C8E016009ADF9A /* PlatformUIJedio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02D1CEE829C8DFFC009ADF9A /* PlatformUIJedio.framework */; };
02D9518C2C926B9C007BB2B4 /* dydxNotificationPrimerViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D9518B2C926B9C007BB2B4 /* dydxNotificationPrimerViewPresenter.swift */; };
02DA6E0D2AD897EE0048126C /* starkex-lib.js in Resources */ = {isa = PBXBuildFile; fileRef = 02DA6DFF2AD897EE0048126C /* starkex-lib.js */; };
02DA6E0E2AD897EE0048126C /* starkex-eth.js in Resources */ = {isa = PBXBuildFile; fileRef = 02DA6E0C2AD897EE0048126C /* starkex-eth.js */; };
02DDAD55292587C600CC7531 /* QRCodeDisplayBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DDAD54292587C600CC7531 /* QRCodeDisplayBuilder.swift */; };
Expand Down Expand Up @@ -430,6 +432,7 @@
023AB3C32BEAD56A005230B2 /* dydxMarginModeViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxMarginModeViewBuilder.swift; sourceTree = "<group>"; };
023AB3C72BEAD5F3005230B2 /* dydxTargetLeverageViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTargetLeverageViewBuilder.swift; sourceTree = "<group>"; };
023DC87329CCBD21000DD920 /* dydxOnboardQRCodeViewBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = dydxOnboardQRCodeViewBuilder.swift; sourceTree = "<group>"; };
023FC7E22C94DCBF003D77A6 /* dydxPushNotificationToggleWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxPushNotificationToggleWorker.swift; sourceTree = "<group>"; };
02439CC029B03ECD00A083FE /* dydxCartera.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = dydxCartera.xcodeproj; path = ../dydxCartera/dydxCartera.xcodeproj; sourceTree = "<group>"; };
0243A76029BE572C00A083FE /* dydxCancelOrderActionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxCancelOrderActionBuilder.swift; sourceTree = "<group>"; };
02465B95297F6E2E00A4CA55 /* dydxPortfolioHeaderPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxPortfolioHeaderPresenter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -493,6 +496,7 @@
02D1345728ECF30000B46941 /* dydxMarketsSearchViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxMarketsSearchViewBuilder.swift; sourceTree = "<group>"; };
02D1CEE029C8DEA7009ADF9A /* dydxLanguageViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxLanguageViewBuilder.swift; sourceTree = "<group>"; };
02D1CEE229C8DFFC009ADF9A /* PlatformUIJedio.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PlatformUIJedio.xcodeproj; path = ../../PlatformUIJedio/PlatformUIJedio.xcodeproj; sourceTree = "<group>"; };
02D9518B2C926B9C007BB2B4 /* dydxNotificationPrimerViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxNotificationPrimerViewPresenter.swift; sourceTree = "<group>"; };
02DA6DFF2AD897EE0048126C /* starkex-lib.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "starkex-lib.js"; sourceTree = "<group>"; };
02DA6E0C2AD897EE0048126C /* starkex-eth.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "starkex-eth.js"; sourceTree = "<group>"; };
02DDAD54292587C600CC7531 /* QRCodeDisplayBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeDisplayBuilder.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -680,6 +684,7 @@
02FF0BCF29AE92EE00781EDA /* Onboarding */,
0238FC47296DA54A002E1C1A /* OrderDetails */,
6496DC31295E05E000174CE7 /* Portfolio */,
02E897E82C9267DB006E0DEB /* Primers */,
0258BA1E299294A90098E1BE /* Profile */,
278A4D912B8FA5C1003898EB /* Rating */,
02550F5029EA0E7500C00CE4 /* Receipt */,
Expand Down Expand Up @@ -1004,6 +1009,7 @@
02669B7B2AD8661F00A756AA /* dydxCarteraConfigWorker.swift */,
64529A4B2AE8705E000810E6 /* dydxUpdateWorker.swift */,
278A4DA32B8FDD9D003898EB /* dydxRatingsWorker.swift */,
023FC7E22C94DCBF003D77A6 /* dydxPushNotificationToggleWorker.swift */,
);
path = Workers;
sourceTree = "<group>";
Expand Down Expand Up @@ -1319,6 +1325,14 @@
name = Products;
sourceTree = "<group>";
};
02E897E82C9267DB006E0DEB /* Primers */ = {
isa = PBXGroup;
children = (
02D9518B2C926B9C007BB2B4 /* dydxNotificationPrimerViewPresenter.swift */,
);
path = Primers;
sourceTree = "<group>";
};
02E90C4C29D62702004E2311 /* FeatureFlags */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2064,6 +2078,7 @@
02669B7C2AD8661F00A756AA /* dydxCarteraConfigWorker.swift in Sources */,
02B841B228EF6C6400C4D25B /* dydxMarketInfoViewBuilder.swift in Sources */,
02031F1C2AC3A7130069E00D /* dydxTradeSheetTipDraftViewPresenter.swift in Sources */,
02D9518C2C926B9C007BB2B4 /* dydxNotificationPrimerViewPresenter.swift in Sources */,
277E8FC92B1E576B005CCBCB /* dydxProfileRewardsViewPresenter.swift in Sources */,
64A4DB9B2966490C008D8E20 /* dydxTradeInputOrderTypePresenter.swift in Sources */,
0236F118296ABEF500EB995F /* dydxPortfolioPositionsViewPresenter.swift in Sources */,
Expand Down Expand Up @@ -2098,6 +2113,7 @@
278A4DA42B8FDD9D003898EB /* dydxRatingsWorker.swift in Sources */,
02DDAD55292587C600CC7531 /* QRCodeDisplayBuilder.swift in Sources */,
0268BBFB2A8BE27800D0C59B /* dydxTransferOutViewPresenter.swift in Sources */,
023FC7E32C94DCBF003D77A6 /* dydxPushNotificationToggleWorker.swift in Sources */,
2749F8FF2B853B8700D6BA16 /* dydxRewardsLaunchIncentivesPresenter.swift in Sources */,
027CA87229EDFC990069781A /* dydxTransferInputCtaButtonViewPresenter.swift in Sources */,
02D1CEE129C8DEA7009ADF9A /* dydxLanguageViewBuilder.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,11 @@
"/withdraw/confirm":{
"destination":"WithdrawalConfirmation.storyboard",
"presentation":"half"
}
},
"/primer/notification":{
"destination":"dydxPresenters.dydxNotificationPrimerViewBuilder",
"presentation":"half"
},
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ public class dydxCollectFeedbackActionBuilder: NSObject, ObjectBuilderProtocol {

open class dydxCollectFeedbackAction: NSObject, NavigableProtocol {
private var completion: RoutingCompletionBlock?

open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) {
switch request?.path {
case "/action/collect_feedback":
case "/action/collect_feedback":
if let feedbackUrl = URL(string: AbacusStateManager.shared.environment?.links?.feedback ?? "") {
Router.shared?.navigate(to: feedbackUrl, completion: { _, success in
completion?(nil, success)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ final class dydxAlertsWorker: BaseWorker {
let actions = (link != nil) ? [ErrorAction(text: DataLocalizer.localize(path: "APP.GENERAL.VIEW")) {
Router.shared?.navigate(to: RoutingRequest(path: link!), animated: true, completion: nil)
}] : nil
if SettingsStore.shared?.shouldDisplayInAppNotifications != false {
if SettingsStore.shared?.shouldDisplayInAppNotifications ?? true {
ErrorInfo.shared?.info(title: alert.title,
message: alert.text,
type: alert.type.infoType,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// dydxPushNotificationToggleWorker.swift
// dydxPresenters
//
// Created by Rui Huang on 13/09/2024.
//

import Foundation
import Combine
import dydxStateManager
import ParticlesKit
import RoutingKit
import Utilities

public final class dydxPushNotificationToggleWorker: BaseWorker {

public override func start() {
super.start()

// Sync the app settings value to the system notification settings
changeObservation(from: nil, to: NotificationService.shared, keyPath: #keyPath(NotificationHandler.permission)) { _, _, _, _ in
let pushNotificationEnabled = NotificationService.shared?.permission == .authorized
SettingsStore.shared?.setValue(pushNotificationEnabled, forKey: dydxSettingsStoreKey.shouldDisplayInAppNotifications.rawValue)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ public final class dydxGlobalWorkers: BaseWorker {
dydxCarteraConfigWorker(),
dydxUpdateWorker(),
dydxRatingsWorker(),
dydxGasTokenWorker()
dydxGasTokenWorker(),
dydxPushNotificationToggleWorker()
]

override public func start() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public enum dydxSettingsStoreKey: String, CaseIterable {
}
}

extension KeyValueStoreProtocol {
public extension KeyValueStoreProtocol {

var language: Bool {
SettingsStore.shared?.value(forKey: dydxSettingsStoreKey.language.rawValue) as? Bool
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// dydxNotificationPrimerViewPresenter.swift
// dydxPresenters
//
// Created by Rui Huang on 11/09/2024.
//

import Utilities
import dydxViews
import PlatformParticles
import RoutingKit
import ParticlesKit
import PlatformUI

public class dydxNotificationPrimerViewBuilder: NSObject, ObjectBuilderProtocol {
public func build<T>() -> T? {
let presenter = dydxNotificationPrimerViewPresenter()
let view = presenter.viewModel?.createView() ?? PlatformViewModel().createView()
return dydxNotificationPrimerViewController(presenter: presenter, view: view, configuration: .default) as? T
}
}

private class dydxNotificationPrimerViewController: HostingViewController<PlatformView, dydxNotificationPrimerViewModel> {
override public func arrive(to request: RoutingRequest?, animated: Bool) -> Bool {
if request?.path == "/primer/notification" {
return true
}
return false
}
}

private protocol dydxNotificationPrimerViewPresenterProtocol: HostedViewPresenterProtocol {
var viewModel: dydxNotificationPrimerViewModel? { get }
}

private class dydxNotificationPrimerViewPresenter: HostedViewPresenter<dydxNotificationPrimerViewModel>, dydxNotificationPrimerViewPresenterProtocol {
override init() {
super.init()

viewModel = dydxNotificationPrimerViewModel()
viewModel?.ctaAction = {
Router.shared?.navigate(to: RoutingRequest(path: "/action/dismiss", params: nil), animated: true) { _, _ in
let notificationPermission = NotificationService.shared?.authorization
if notificationPermission?.authorization == .notDetermined {
notificationPermission?.promptToAuthorize()
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import PlatformUI
import PlatformUIJedio
import SwiftUI
import dydxStateManager
import JedioKit

public class dydxNotificationsSettingsViewBuilder: NSObject, ObjectBuilderProtocol {
public func build<T>() -> T? {
Expand All @@ -38,4 +39,27 @@ private class dydxNotificationsSettingsViewPresenter: SettingsViewPresenter {
}
viewModel?.headerViewModel = header
}

override func onInputValueChanged(input: FieldInput) {
if input.fieldName == "should_display_in_app_notifications" {
promptToToggleNotification()
}
}

private func promptToToggleNotification() {
let notificationPermission = NotificationService.shared?.authorization
if notificationPermission?.authorization == .notDetermined {
Router.shared?.navigate(to: RoutingRequest(path: "/authorization/notification", params: nil), animated: true, completion: nil)
} else {
notificationPermission?.promptToSettings(requestTitle: nil,
requestMessage: DataLocalizer.shared?.localize(path: "APP.PUSH_NOTIFICATIONS.UPDATE_SETTINGS_MESSAGE", params: nil),
requestCTA: DataLocalizer.shared?.localize(path: "APP.EMAIL_NOTIFICATIONS.SETTINGS", params: nil) ?? "Settings",
cancelTitle: DataLocalizer.shared?.localize(path: "APP.GENERAL.CANCEL", params: nil) ?? "Cancel"
)
}
// sync the settings with the system permission
let pushNotificationEnabled = NotificationService.shared?.permission == .authorized
SettingsStore.shared?.setValue(pushNotificationEnabled, forKey: dydxSettingsStoreKey.shouldDisplayInAppNotifications.rawValue)
loadSettings()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,14 @@ private class dydxTradeStatusViewPresenter: HostedViewPresenter<dydxTradeStatusV
}()

private let doneAction: (() -> Void) = {
Router.shared?.navigate(to: RoutingRequest(path: "/action/dismiss"), animated: true, completion: nil)
let notificationPermission = NotificationService.shared?.authorization
if notificationPermission?.authorization == .notDetermined {
Router.shared?.navigate(to: RoutingRequest(path: "/action/dismiss"), animated: true, completion: { _, _ in
Router.shared?.navigate(to: RoutingRequest(path: "/authorization/notification", params: nil), animated: true, completion: nil)
})
} else {
Router.shared?.navigate(to: RoutingRequest(path: "/action/dismiss"), animated: true, completion: nil)
}
}

private lazy var tryAgainAction: (() -> Void) = { [weak self] in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ private class dydxUpdateViewPresenter: HostedViewPresenter<dydxUpdateViewModel>,
viewModel?.title = DataLocalizer.localize(path: ios.title ?? "FORCED_UPDATE.TITLE")
viewModel?.text = DataLocalizer.localize(path: ios.text ?? "FORCED_UPDATE.TEXT")
viewModel?.action = DataLocalizer.localize(path: ios.action ?? "FORCED_UPDATE.ACTION")
viewModel?.updateTapped = { [weak self] in
viewModel?.updateTapped = {
if let url = URL(string: ios.url) {
Router.shared?.navigate(to: url, completion: nil)
}
Expand Down
Loading

0 comments on commit 453dcd9

Please sign in to comment.