diff --git a/BraveRewards/BraveRewards.framework/BraveRewards b/BraveRewards/BraveRewards.framework/BraveRewards index f89dbfa1a85..89e13b5f8b9 100755 Binary files a/BraveRewards/BraveRewards.framework/BraveRewards and b/BraveRewards/BraveRewards.framework/BraveRewards differ diff --git a/BraveRewards/BraveRewards.framework/Headers/BATBraveLedger.h b/BraveRewards/BraveRewards.framework/Headers/BATBraveLedger.h index b2c81daf940..2ac0b078e71 100644 --- a/BraveRewards/BraveRewards.framework/Headers/BATBraveLedger.h +++ b/BraveRewards/BraveRewards.framework/Headers/BATBraveLedger.h @@ -7,6 +7,7 @@ #import "ledger.mojom.objc.h" #import "BATRewardsNotification.h" #import "BATBraveLedgerObserver.h" +#import "BATPromotionSolution.h" @class BATBraveAds; @@ -166,23 +167,20 @@ NS_SWIFT_NAME(BraveLedger) completion:(void (^)(BATResult result))completion; -#pragma mark - Grants +#pragma mark - Promotions -@property (nonatomic, readonly) NSArray *pendingGrants; +@property (nonatomic, readonly) NSArray *pendingPromotions; -- (void)fetchAvailableGrantsForLanguage:(NSString *)language - paymentId:(NSString *)paymentId; +@property (nonatomic, readonly) NSArray *finishedPromotions; -- (void)fetchAvailableGrantsForLanguage:(NSString *)language - paymentId:(NSString *)paymentId - completion:(nullable void (^)(NSArray *grants))completion; +- (void)fetchPromotions:(nullable void (^)(NSArray *grants))completion; -- (void)grantCaptchaForPromotionId:(NSString *)promoID - promotionType:(NSString *)promotionType - completion:(void (^)(NSString *image, NSString *hint))completion; +- (void)claimPromotion:(NSString *)deviceCheckPublicKey + completion:(void (^)(BATResult result, NSString * _Nonnull json))completion; -- (void)solveGrantCaptchWithPromotionId:(NSString *)promotionId - solution:(NSString *)solution; +- (void)attestPromotion:(NSString *)promotionId + solution:(BATPromotionSolution *)solution + completion:(nullable void (^)(BATResult result, BATPromotion * _Nullable promotion))completion; #pragma mark - History diff --git a/BraveRewards/BraveRewards.framework/Headers/BATBraveLedgerObserver.h b/BraveRewards/BraveRewards.framework/Headers/BATBraveLedgerObserver.h index 3ae87955fe2..e05006cd029 100644 --- a/BraveRewards/BraveRewards.framework/Headers/BATBraveLedgerObserver.h +++ b/BraveRewards/BraveRewards.framework/Headers/BATBraveLedgerObserver.h @@ -33,11 +33,14 @@ NS_SWIFT_NAME(LedgerObserver) @property (nonatomic, copy, nullable) void (^publisherListUpdated)(); +/// +@property (nonatomic, copy, nullable) void (^finishedPromotionsAdded)(NSArray *promotions); + /// Eligable grants were added to the wallet -@property (nonatomic, copy, nullable) void (^grantsAdded)(NSArray *grants); +@property (nonatomic, copy, nullable) void (^promotionsAdded)(NSArray *promotions); /// A grant was claimed -@property (nonatomic, copy, nullable) void (^grantClaimed)(BATGrant *grant); +@property (nonatomic, copy, nullable) void (^promotionClaimed)(BATPromotion *promotion); /// A reconcile transaction completed and the user may have an updated balance /// and likely an updated balance report diff --git a/BraveRewards/BraveRewards.framework/Headers/BATPromotionSolution.h b/BraveRewards/BraveRewards.framework/Headers/BATPromotionSolution.h new file mode 100644 index 00000000000..e2df7f816da --- /dev/null +++ b/BraveRewards/BraveRewards.framework/Headers/BATPromotionSolution.h @@ -0,0 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// The solution to claiming a promotion on iOS. Obtain the `noonce` through +/// `[BATBraveLedger claimPromotion:completion:]` method, and obtain the +/// blob and signature from the users keychain +NS_SWIFT_NAME(PromotionSolution) +@interface BATPromotionSolution : NSObject + +@property (nonatomic, copy) NSString *noonce; +@property (nonatomic, copy) NSString *blob; +@property (nonatomic, copy) NSString *signature; + +- (NSString *)JSONPayload; + +@end + +NS_ASSUME_NONNULL_END diff --git a/BraveRewards/BraveRewards.framework/Headers/BraveRewards.h b/BraveRewards/BraveRewards.framework/Headers/BraveRewards.h index c89c02820c3..047ecf2a151 100644 --- a/BraveRewards/BraveRewards.framework/Headers/BraveRewards.h +++ b/BraveRewards/BraveRewards.framework/Headers/BraveRewards.h @@ -21,3 +21,4 @@ FOUNDATION_EXPORT const unsigned char BraveRewardsVersionString[]; #import #import #import +#import diff --git a/BraveRewards/BraveRewards.framework/Headers/ledger.mojom.objc.h b/BraveRewards/BraveRewards.framework/Headers/ledger.mojom.objc.h index c247c00bd30..eb40d9a31f6 100644 --- a/BraveRewards/BraveRewards.framework/Headers/ledger.mojom.objc.h +++ b/BraveRewards/BraveRewards.framework/Headers/ledger.mojom.objc.h @@ -49,12 +49,10 @@ typedef NS_ENUM(NSInteger, BATResult) { BATResultRegistrationVerificationFailed = 10, BATResultBadRegistrationResponse = 11, BATResultWalletCreated = 12, - BATResultGrantNotFound = 13, BATResultAcTableEmpty = 14, BATResultNotEnoughFunds = 15, BATResultTipError = 16, BATResultCorruptedWallet = 17, - BATResultGrantAlreadyClaimed = 18, BATResultContributionAmountTooLow = 19, BATResultVerifiedPublisher = 20, BATResultPendingPublisherRemoved = 21, @@ -141,8 +139,39 @@ typedef NS_ENUM(NSInteger, BATEnvironment) { } NS_SWIFT_NAME(Environment); +typedef NS_ENUM(NSInteger, BATPromotionType) { + BATPromotionTypeUgp = 0, + BATPromotionTypeAds = 1, +} NS_SWIFT_NAME(PromotionType); -@class BATContributionInfo, BATPublisherInfo, BATPublisherBanner, BATPendingContribution, BATPendingContributionInfo, BATVisitData, BATGrant, BATWalletProperties, BATBalance, BATAutoContributeProps, BATMediaEventInfo, BATExternalWallet, BATBalanceReportInfo, BATActivityInfoFilterOrderPair, BATActivityInfoFilter, BATReconcileInfo, BATRewardsInternalsInfo, BATServerPublisherInfo, BATTransferFee, BATContributionQueue, BATContributionQueuePublisher; + +typedef NS_ENUM(NSInteger, BATPromotionStatus) { + BATPromotionStatusActive = 0, + BATPromotionStatusAttested = 1, + BATPromotionStatusClaimed = 2, + BATPromotionStatusSignedTokens = 3, + BATPromotionStatusFinished = 4, + BATPromotionStatusOver = 5, +} NS_SWIFT_NAME(PromotionStatus); + + +typedef NS_ENUM(NSInteger, BATPlatform) { + BATPlatformDesktop = 0, + BATPlatformAndroid = 1, + BATPlatformIos = 2, +} NS_SWIFT_NAME(Platform); + + +typedef NS_ENUM(NSInteger, BATOperatingSystem) { + BATOperatingSystemWindows = 0, + BATOperatingSystemMacos = 1, + BATOperatingSystemLinux = 2, + BATOperatingSystemUndefined = 3, +} NS_SWIFT_NAME(OperatingSystem); + + + +@class BATContributionInfo, BATPublisherInfo, BATPublisherBanner, BATPendingContribution, BATPendingContributionInfo, BATVisitData, BATWalletProperties, BATBalance, BATAutoContributeProps, BATMediaEventInfo, BATExternalWallet, BATBalanceReportInfo, BATActivityInfoFilterOrderPair, BATActivityInfoFilter, BATReconcileInfo, BATRewardsInternalsInfo, BATServerPublisherInfo, BATTransferFee, BATContributionQueue, BATContributionQueuePublisher, BATPromotion, BATPromotionCreds, BATUnblindedToken, BATClientInfo; NS_ASSUME_NONNULL_BEGIN @@ -222,20 +251,10 @@ NS_SWIFT_NAME(VisitData) @property (nonatomic, copy) NSString * faviconUrl; @end -NS_SWIFT_NAME(Grant) -@interface BATGrant : NSObject -@property (nonatomic, copy) NSString * altcurrency; -@property (nonatomic, copy) NSString * probi; -@property (nonatomic, copy) NSString * promotionId; -@property (nonatomic) uint64_t expiryTime; -@property (nonatomic, copy) NSString * type; -@end - NS_SWIFT_NAME(WalletProperties) @interface BATWalletProperties : NSObject @property (nonatomic) double feeAmount; @property (nonatomic, copy) NSArray * parametersChoices; -@property (nonatomic, copy) NSArray * grants; @end NS_SWIFT_NAME(Balance) @@ -358,4 +377,43 @@ NS_SWIFT_NAME(ContributionQueuePublisher) @property (nonatomic) double amountPercent; @end +NS_SWIFT_NAME(Promotion) +@interface BATPromotion : NSObject +@property (nonatomic, copy) NSString * id; +@property (nonatomic) uint32_t version; +@property (nonatomic) BATPromotionType type; +@property (nonatomic, copy) NSString * publicKeys; +@property (nonatomic) uint32_t suggestions; +@property (nonatomic) double approximateValue; +@property (nonatomic) bool claimed; +@property (nonatomic) BATPromotionStatus status; +@property (nonatomic) uint64_t expiresAt; +@property (nonatomic, copy, nullable) BATPromotionCreds * credentials; +@end + +NS_SWIFT_NAME(PromotionCreds) +@interface BATPromotionCreds : NSObject +@property (nonatomic, copy) NSString * tokens; +@property (nonatomic, copy) NSString * blindedCreds; +@property (nonatomic, copy) NSString * signedCreds; +@property (nonatomic, copy) NSString * publicKey; +@property (nonatomic, copy) NSString * batchProof; +@property (nonatomic, copy) NSString * claimId; +@end + +NS_SWIFT_NAME(UnblindedToken) +@interface BATUnblindedToken : NSObject +@property (nonatomic) uint64_t id; +@property (nonatomic, copy) NSString * tokenValue; +@property (nonatomic, copy) NSString * publicKey; +@property (nonatomic) double value; +@property (nonatomic, copy) NSString * promotionId; +@end + +NS_SWIFT_NAME(ClientInfo) +@interface BATClientInfo : NSObject +@property (nonatomic) BATPlatform platform; +@property (nonatomic) BATOperatingSystem os; +@end + NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/BraveRewards/BraveRewards.framework/Info.plist b/BraveRewards/BraveRewards.framework/Info.plist index fe8122bf374..aab22fdf94f 100644 Binary files a/BraveRewards/BraveRewards.framework/Info.plist and b/BraveRewards/BraveRewards.framework/Info.plist differ diff --git a/BraveRewards/BraveRewards.framework/Model.momd/Model.mom b/BraveRewards/BraveRewards.framework/Model.momd/Model.mom index 704dad2a8bc..a04a981d19d 100644 Binary files a/BraveRewards/BraveRewards.framework/Model.momd/Model.mom and b/BraveRewards/BraveRewards.framework/Model.momd/Model.mom differ diff --git a/BraveRewards/BraveRewards.framework/Model.momd/VersionInfo.plist b/BraveRewards/BraveRewards.framework/Model.momd/VersionInfo.plist index 5ac6148e377..90fe886eab6 100644 Binary files a/BraveRewards/BraveRewards.framework/Model.momd/VersionInfo.plist and b/BraveRewards/BraveRewards.framework/Model.momd/VersionInfo.plist differ diff --git a/BraveRewardsUI/Extensions/BraveLedgerExtensions.swift b/BraveRewardsUI/Extensions/BraveLedgerExtensions.swift index 3a98f7fe8ee..d4bfd7486ba 100644 --- a/BraveRewardsUI/Extensions/BraveLedgerExtensions.swift +++ b/BraveRewardsUI/Extensions/BraveLedgerExtensions.swift @@ -4,6 +4,9 @@ import Foundation import BraveRewards +import Shared + +private let log = Logger.rewardsLogger extension BraveLedger { @@ -125,6 +128,92 @@ extension BraveLedger { } return id } + + public func setupDeviceCheckEnrollment(_ client: DeviceCheckClient, completion: @escaping () -> Void) { + // Enroll in DeviceCheck + client.generateToken { [weak self] (token, error) in + guard let self = self else { return } + if let error = error { + log.error("Failed to generate DeviceCheck token: \(error)") + completion() + return + } + let paymentId = self.paymentId ?? "" + client.generateEnrollment(paymentId: paymentId, token: token) { registration, error in + if let error = error { + log.error("Failed to enroll in DeviceCheck: \(error)") + completion() + return + } + guard let registration = registration else { return } + client.registerDevice(enrollment: registration) { error in + if let error = error { + log.error("Failed to register device with mobile attestation server: \(error)") + completion() + return + } + } + } + } + } + + public func claimPromotion(_ promotion: Promotion, completion: @escaping (_ success: Bool) -> Void) { + guard let paymentId = self.paymentId else { return } + let deviceCheck = DeviceCheckClient(environment: BraveLedger.environment) + let group = DispatchGroup() + if !DeviceCheckClient.isDeviceEnrolled() { + group.enter() + setupDeviceCheckEnrollment(deviceCheck) { + if !DeviceCheckClient.isDeviceEnrolled() { + DispatchQueue.main.async { + completion(false) + } + return + } + group.leave() + } + } + group.notify(queue: .main) { + deviceCheck.generateAttestation(paymentId: paymentId) { (attestation, error) in + guard let attestation = attestation else { + completion(false) + return + } + self.claimPromotion(attestation.publicKeyHash) { result, noonce in + if result != .ledgerOk { + completion(false) + return + } + + deviceCheck.generateAttestationVerification(nonce: noonce) { verification, error in + guard let verification = verification else { + completion(false) + return + } + + let solution = PromotionSolution() + solution.noonce = noonce + solution.signature = verification.signature + do { + solution.blob = try verification.attestationBlob.bsonData().base64EncodedString() + } catch { + log.error("Couldn't serialize attestation blob. The attest promotion will fail") + } + + self.attestPromotion(promotion.id, solution: solution) { result, promotion in + if result == .ledgerOk { + self.fetchPromotions { _ in + completion(true) + } + } else { + completion(false) + } + } + } + } + } + } + } } extension PublisherInfo { diff --git a/BraveRewardsUI/Grants/GrantsListViewController.swift b/BraveRewardsUI/Grants/GrantsListViewController.swift index b33ac664f71..6bbd0f10146 100644 --- a/BraveRewardsUI/Grants/GrantsListViewController.swift +++ b/BraveRewardsUI/Grants/GrantsListViewController.swift @@ -32,19 +32,16 @@ class GrantsListViewController: UIViewController { super.viewDidLoad() title = Strings.Grants - - if let grants = ledger.walletInfo?.grants, !grants.isEmpty { - grants.forEach { - if let value = BATValue(probi: $0.probi) { - let isAd = $0.type == "ads" - grantsView.stackView.addArrangedSubview( - GrantsItemView( - amount: value.displayString, - expirationDate: isAd ? nil : Date(timeIntervalSince1970: TimeInterval($0.expiryTime)) - ) - ) - } - } + + ledger.finishedPromotions.forEach { + let value = BATValue($0.approximateValue) + let isAd = $0.type == .ads + grantsView.stackView.addArrangedSubview( + GrantsItemView( + amount: value.displayString, + expirationDate: isAd ? nil : Date(timeIntervalSince1970: TimeInterval($0.expiresAt)) + ) + ) } } } diff --git a/BraveRewardsUI/README.md b/BraveRewardsUI/README.md index 5a499d50621..e08c13a6fd3 100644 --- a/BraveRewardsUI/README.md +++ b/BraveRewardsUI/README.md @@ -5,6 +5,6 @@ A UI framework for consuming Brave Rewards. The core logic around BraveRewards r The latest BraveRewards.framework was built on: ``` -brave-browser/373314e05e8e0abe41c21d5aee6314371e4154ad -brave-core/6544304acdc2641b4e9d6aa02cf711194a64876e +brave-browser/5d5f18be463abaf9b5f23d5b7ec14be51f7fd412 +brave-core/c201526020483db2cb7125d2e412481ca11979f9 ``` diff --git a/BraveRewardsUI/Settings/Grants/GrantClaimedViewController.swift b/BraveRewardsUI/Settings/Grants/GrantClaimedViewController.swift index 2dede57c97d..ab63e8b956b 100644 --- a/BraveRewardsUI/Settings/Grants/GrantClaimedViewController.swift +++ b/BraveRewardsUI/Settings/Grants/GrantClaimedViewController.swift @@ -12,13 +12,15 @@ private let claimGrantDateFormatter = DateFormatter().then { class GrantClaimedViewController: UIViewController { let grantAmount: String - let expirationDate: Date + let expirationDate: Date? - init(grantAmount: String, expirationDate: Date) { + init(grantAmount: String, expirationDate: Date?) { self.grantAmount = grantAmount self.expirationDate = expirationDate super.init(nibName: nil, bundle: nil) + + self.modalPresentationStyle = .currentContext } @available(*, unavailable) @@ -38,7 +40,13 @@ class GrantClaimedViewController: UIViewController { super.viewDidLoad() grantView.infoView.freeTokenAmountLabel.text = grantAmount + " " + Strings.BAT - grantView.infoView.expirationDateLabel.text = claimGrantDateFormatter.string(from: expirationDate) + if let expirationDate = expirationDate { + grantView.infoView.expirationDateLabel.text = claimGrantDateFormatter.string(from: expirationDate) + } else { + // No expiration + grantView.infoView.expirationDateTitleLabel.isHidden = true + grantView.infoView.expirationDateLabel.isHidden = true + } grantView.okButton.addTarget(self, action: #selector(dismissController), for: .touchUpInside) } @@ -136,6 +144,12 @@ extension GrantClaimedViewController { let freeTokenAmountLabel: UILabel let expirationDateLabel: UILabel + let expirationDateTitleLabel = UILabel().then { + $0.text = Strings.GrantsClaimedExpirationDateTitle + $0.appearanceTextColor = SettingsUX.subtitleTextColor + $0.font = .systemFont(ofSize: 13.0) + } + override init(frame: CGRect) { let infoLabelConfig: (UILabel) -> Void = { $0.appearanceTextColor = UX.infoAccentTextColor @@ -161,17 +175,12 @@ extension GrantClaimedViewController { $0.appearanceTextColor = SettingsUX.subtitleTextColor $0.font = .systemFont(ofSize: 13.0) } - let expirdationDateTitleLabel = UILabel().then { - $0.text = Strings.GrantsClaimedExpirationDateTitle - $0.appearanceTextColor = SettingsUX.subtitleTextColor - $0.font = .systemFont(ofSize: 13.0) - } addSubview(stackView) stackView.addArrangedSubview(amountTitleLabel) stackView.addArrangedSubview(freeTokenAmountLabel) stackView.setCustomSpacing(8.0, after: freeTokenAmountLabel) - stackView.addArrangedSubview(expirdationDateTitleLabel) + stackView.addArrangedSubview(expirationDateTitleLabel) stackView.addArrangedSubview(expirationDateLabel) stackView.snp.makeConstraints { diff --git a/BraveRewardsUI/Settings/Grants/SettingsGrantSectionView.swift b/BraveRewardsUI/Settings/Grants/SettingsGrantSectionView.swift index 9a7b5a9ac08..ddb7945c756 100644 --- a/BraveRewardsUI/Settings/Grants/SettingsGrantSectionView.swift +++ b/BraveRewardsUI/Settings/Grants/SettingsGrantSectionView.swift @@ -3,11 +3,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import UIKit +import BraveRewards class SettingsGrantSectionView: SettingsSectionView { /// The grant type - enum GrantType { + private enum PromotionViewType { /// A regular UGP grant, which does not show the user what the amount is case ugp /// An ads grant. The amount optionally should be a `BATValue`'s @@ -15,6 +16,9 @@ class SettingsGrantSectionView: SettingsSectionView { case ads(amount: String?) } + /// Reference to the promotion this section is referring to + let promotion: Promotion + var claimGrantTapped: ((SettingsGrantSectionView) -> Void)? let claimGrantButton = Button().then { @@ -28,7 +32,17 @@ class SettingsGrantSectionView: SettingsSectionView { $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) } - init(type: GrantType) { + private var type: PromotionViewType { + if promotion.type == .ads { + return .ads(amount: BATValue(promotion.approximateValue).displayString) + } else { + return .ugp + } + } + + init(promotion: Promotion) { + self.promotion = promotion + super.init(frame: .zero) claimGrantButton.addTarget(self, action: #selector(tappedClaimGrantButton), for: .touchUpInside) diff --git a/BraveRewardsUI/Settings/SettingsViewController.swift b/BraveRewardsUI/Settings/SettingsViewController.swift index 05dcc174be4..6295b850a2f 100644 --- a/BraveRewardsUI/Settings/SettingsViewController.swift +++ b/BraveRewardsUI/Settings/SettingsViewController.swift @@ -44,25 +44,11 @@ class SettingsViewController: UIViewController { settingsView.do { $0.rewardsToggleSection.toggleSwitch.addTarget(self, action: #selector(rewardsSwitchValueChanged), for: .valueChanged) - $0.grantsSections = state.ledger.pendingGrants.map { - var type: SettingsGrantSectionView.GrantType - if $0.type == "ads" { - type = .ads(amount: BATValue(probi: $0.probi)?.displayString) - } else { - type = .ugp - } - let section = SettingsGrantSectionView(type: type) + $0.grantsSections = state.ledger.pendingPromotions.map { + let section = SettingsGrantSectionView(promotion: $0) section.claimGrantTapped = { [weak self] section in guard let self = self else { return } - // FIXME: Remove fake values - let controller = GrantClaimedViewController(grantAmount: "30.0 BAT", expirationDate: Date().addingTimeInterval(30*24*60*60)) - let container = PopoverNavigationController(rootViewController: controller) - - section.claimGrantButton.isLoading = true - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in - self?.present(container, animated: true) - section.claimGrantButton.isLoading = false - } + self.tappedClaimButton(section) } return section } @@ -169,4 +155,28 @@ class SettingsViewController: UIViewController { ) } } + + private func tappedClaimButton(_ section: SettingsGrantSectionView) { + let promotion = section.promotion + + section.claimGrantButton.isLoading = true + + state.ledger.claimPromotion(promotion) { [weak self] success in + guard let self = self else { return } + section.claimGrantButton.isLoading = false + if !success { + // Show error? + return + } + + let amount = BATValue(promotion.approximateValue).displayString + let isAdGrant = promotion.type == .ads + + let claimedVC = GrantClaimedViewController( + grantAmount: amount, + expirationDate: isAdGrant ? nil : Date(timeIntervalSince1970: TimeInterval(promotion.expiresAt)) + ) + self.present(claimedVC, animated: true) + } + } } diff --git a/BraveRewardsUI/Wallet/WalletViewController.swift b/BraveRewardsUI/Wallet/WalletViewController.swift index d4363dc1027..ca06bb4a7b3 100644 --- a/BraveRewardsUI/Wallet/WalletViewController.swift +++ b/BraveRewardsUI/Wallet/WalletViewController.swift @@ -79,11 +79,7 @@ class WalletViewController: UIViewController, RewardsSummaryProtocol { // Not actually visible from this controller title = Strings.PanelTitle - if let grants = state.ledger.walletInfo?.grants, !grants.isEmpty { - walletView.headerView.grantsButton.isHidden = false - } else { - walletView.headerView.grantsButton.isHidden = true - } + walletView.headerView.grantsButton.isHidden = state.ledger.finishedPromotions.isEmpty navigationController?.setNavigationBarHidden(true, animated: false) @@ -319,23 +315,31 @@ class WalletViewController: UIViewController, RewardsSummaryProtocol { // MARK: - Actions @objc private func tappedClaimGrantButton(_ sender: ActionButton) { + guard let promotion = state.ledger.pendingPromotions.first else { return } sender.loaderView = LoaderView(size: .small) sender.loaderPlacement = .replacesContent sender.isLoading = true - ledgerObserver.grantClaimed = { [weak self] grant in - guard let self = self, let grantAmount = BATValue(probi: grant.probi)?.displayString else { return } + sender.isEnabled = false + state.ledger.claimPromotion(promotion) { success in + sender.isEnabled = true sender.isLoading = false + if !success { + // Show error? + let alert = UIAlertController(title: Strings.WalletCreationErrorTitle, message: Strings.WalletCreationErrorBody, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: Strings.OK, style: .default, handler: nil)) + self.present(alert, animated: true) + return + } + let grantAmount = BATValue(promotion.approximateValue).displayString + let isAdGrant = promotion.type == .ads let claimedVC = GrantClaimedViewController( grantAmount: grantAmount, - expirationDate: Date(timeIntervalSince1970: TimeInterval(grant.expiryTime)) + expirationDate: isAdGrant ? nil : Date(timeIntervalSince1970: TimeInterval(promotion.expiresAt)) ) self.present(claimedVC, animated: true) { self.tappedNotificationClose() } } - if let grant = state.ledger.pendingGrants.first { - state.ledger.solveGrantCaptch(withPromotionId: grant.promotionId, solution: "") - } } @objc private func tappedGrantsButton() { @@ -555,6 +559,9 @@ extension WalletViewController { } self.reloadPublisherDetails() } + ledgerObserver.finishedPromotionsAdded = { [weak self] promotions in + self?.walletView.headerView.grantsButton.isHidden = promotions.isEmpty + } ledgerObserver.publisherListUpdated = { [weak self] in guard let self = self else { return } self.publisherSummaryView.publisherView.setCheckAgainIsLoading(self.state.ledger.isLoadingPublisherList) diff --git a/Client.xcodeproj/project.pbxproj b/Client.xcodeproj/project.pbxproj index 73fc370655d..24554516170 100644 --- a/Client.xcodeproj/project.pbxproj +++ b/Client.xcodeproj/project.pbxproj @@ -209,7 +209,6 @@ 271DECF3234CC7EF009DAC37 /* OptionsSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DEC7D234CC7EF009DAC37 /* OptionsSelectionViewController.swift */; }; 271DECF4234CC7EF009DAC37 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DEC7E234CC7EF009DAC37 /* SettingsView.swift */; }; 271DECF5234CC7EF009DAC37 /* SettingsGrantSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DEC80234CC7EF009DAC37 /* SettingsGrantSectionView.swift */; }; - 271DECF6234CC7EF009DAC37 /* GrantClaimedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DEC81234CC7EF009DAC37 /* GrantClaimedViewController.swift */; }; 271DECF7234CC7EF009DAC37 /* SettingsViewDetailsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DEC82234CC7EF009DAC37 /* SettingsViewDetailsButton.swift */; }; 271DECF8234CC7EF009DAC37 /* SettingsSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DEC83234CC7EF009DAC37 /* SettingsSectionView.swift */; }; 271DECF9234CC7EF009DAC37 /* TipsDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DEC86234CC7EF009DAC37 /* TipsDetailViewController.swift */; }; @@ -264,6 +263,7 @@ 271DED2A234CC7EF009DAC37 /* UIImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DECBF234CC7EF009DAC37 /* UIImageExtensions.swift */; }; 271DED2B234CC7EF009DAC37 /* BundleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DECC0234CC7EF009DAC37 /* BundleExtensions.swift */; }; 271DED2C234CC7EF009DAC37 /* DateExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DECC1234CC7EF009DAC37 /* DateExtensions.swift */; }; + 27253973236CE6B100D06EF1 /* GrantClaimedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DEC81234CC7EF009DAC37 /* GrantClaimedViewController.swift */; }; 272FCAA0225CF8F00091E645 /* OnePasswordExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 272FCA98225CF8F00091E645 /* OnePasswordExtension.framework */; }; 27353FF2235F4E7300E42EBB /* WalletDisclaimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27353FF1235F4E7300E42EBB /* WalletDisclaimerView.swift */; }; 2755AB7D23107BC600F0721F /* AdsReporting.js in Resources */ = {isa = PBXBuildFile; fileRef = 2755AB7C23107BC600F0721F /* AdsReporting.js */; }; @@ -5532,6 +5532,7 @@ 271DED2B234CC7EF009DAC37 /* BundleExtensions.swift in Sources */, 271DED08234CC7EF009DAC37 /* SettingsViewController.swift in Sources */, 271DECC4234CC7EF009DAC37 /* PublisherAttentionView.swift in Sources */, + 27253973236CE6B100D06EF1 /* GrantClaimedViewController.swift in Sources */, 271DED23234CC7EF009DAC37 /* UITableViewExtensions.swift in Sources */, 271DED02234CC7EF009DAC37 /* AutoContributeSettingsViewController.swift in Sources */, 271DECD1234CC7EF009DAC37 /* AdView.swift in Sources */, @@ -5569,7 +5570,6 @@ 271DECD6234CC7EF009DAC37 /* QASettingsViewController.swift in Sources */, 271DED17234CC7EF009DAC37 /* Then.swift in Sources */, 271DED14234CC7EF009DAC37 /* CreateWalletButton.swift in Sources */, - 271DECF6234CC7EF009DAC37 /* GrantClaimedViewController.swift in Sources */, 271DECCF234CC7EF009DAC37 /* AdsViewController.swift in Sources */, 271DED25234CC7EF009DAC37 /* UIStackViewExtensions.swift in Sources */, 271DECFC234CC7EF009DAC37 /* SettingsTipsSectionView.swift in Sources */, diff --git a/Client/Frontend/Browser/BrowserViewController.swift b/Client/Frontend/Browser/BrowserViewController.swift index f7535ad1a1f..5706654e159 100644 --- a/Client/Frontend/Browser/BrowserViewController.swift +++ b/Client/Frontend/Browser/BrowserViewController.swift @@ -174,6 +174,7 @@ class BrowserViewController: UIViewController { } rewards = BraveRewards(configuration: configuration) rewardsObserver = LedgerObserver(ledger: rewards!.ledger) + deviceCheckClient = DeviceCheckClient(environment: configuration.environment) #endif super.init(nibName: nil, bundle: nil) @@ -252,9 +253,17 @@ class BrowserViewController: UIViewController { } } + let deviceCheckClient: DeviceCheckClient? + private func setupRewardsObservers() { guard let rewards = rewards, let observer = rewardsObserver else { return } rewards.ledger.add(observer) + observer.walletInitalized = { [weak self] result in + guard let self = self, let rewards = self.rewards, let client = self.deviceCheckClient else { return } + if result == .walletCreated { + rewards.ledger.setupDeviceCheckEnrollment(client) { } + } + } observer.fetchedPanelPublisher = { [weak self] publisher, tabId in guard let self = self, self.isViewLoaded, let tab = self.tabManager.selectedTab, tab.rewardsId == tabId else { return } self.publisher = publisher