Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TRCL-3689 Add primer screen to prompt the user to enable notification #251

Merged
merged 10 commits into from
Sep 17, 2024
Merged
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
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) {

}
Comment on lines +162 to +164
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So subclass can overwrite with custom behavior.

}
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()
}
Comment on lines +44 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if forget, is notDetermined the only time the prompt will actually display? Is it also displayed for .ephemeral? If not, we can maybe try it in all cases and skip the == notdetermined check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we should only show the primer when the state is .notDetermined, since it's the only time the app can prompt for the system notification toggle. In other cases, user has to manually toggle through the device's Settings.

.ephemeral is only available for app clips.

}
}
}
}
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
Loading