[iOS] Create BestFeaturesScreenMediator
This CL creates the mediator for the Best Features Screen. The mediator is used to create the list of BestFeaturesItems and send it to the consumer. Unit tests will be added in a later CL, after the consumer is set. go/blings-best-features-dd Bug: 396481431 Change-Id: I11aa6ff72034fef88cca6fc16f6de761bf5a633f Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6309931 Commit-Queue: Hira Mahmood <hiramahmood@google.com> Reviewed-by: Benjamin Williams <bwwilliams@google.com> Cr-Commit-Position: refs/heads/main@{#1427327}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
bec29d401d
commit
ca59c7afca
ios/chrome/browser/first_run/ui_bundled/best_features
@ -6,11 +6,25 @@ source_set("coordinator") {
|
||||
sources = [
|
||||
"best_features_screen_coordinator.h",
|
||||
"best_features_screen_coordinator.mm",
|
||||
"best_features_screen_mediator.h",
|
||||
"best_features_screen_mediator.mm",
|
||||
]
|
||||
deps = [
|
||||
"//base",
|
||||
"//components/commerce/core:shopping_service",
|
||||
"//components/password_manager/core/browser",
|
||||
"//components/segmentation_platform/public",
|
||||
"//ios/chrome/browser/commerce/model:shopping_service",
|
||||
"//ios/chrome/browser/first_run/ui_bundled:features",
|
||||
"//ios/chrome/browser/first_run/ui_bundled:screen_delegate",
|
||||
"//ios/chrome/browser/first_run/ui_bundled/best_features/ui",
|
||||
"//ios/chrome/browser/segmentation_platform/model",
|
||||
"//ios/chrome/browser/shared/coordinator/chrome_coordinator",
|
||||
"//ios/chrome/browser/shared/model/application_context",
|
||||
"//ios/chrome/browser/shared/model/browser",
|
||||
"//ios/chrome/browser/shared/model/profile",
|
||||
"//ios/chrome/browser/shared/public/features:system_flags",
|
||||
"//ios/chrome/browser/signin/model",
|
||||
]
|
||||
frameworks = [ "UIKit.framework" ]
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
include_rules = [
|
||||
"+ios/chrome/browser/commerce/model",
|
||||
]
|
@ -4,12 +4,26 @@
|
||||
|
||||
#import "ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_coordinator.h"
|
||||
|
||||
#import "components/segmentation_platform/public/segmentation_platform_service.h"
|
||||
#import "components/signin/public/base/consent_level.h"
|
||||
#import "components/signin/public/identity_manager/identity_manager.h"
|
||||
#import "ios/chrome/browser/commerce/model/shopping_service_factory.h"
|
||||
#import "ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_mediator.h"
|
||||
#import "ios/chrome/browser/first_run/ui_bundled/features.h"
|
||||
#import "ios/chrome/browser/first_run/ui_bundled/first_run_screen_delegate.h"
|
||||
#import "ios/chrome/browser/segmentation_platform/model/segmentation_platform_service_factory.h"
|
||||
#import "ios/chrome/browser/shared/model/browser/browser.h"
|
||||
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
|
||||
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
|
||||
|
||||
@implementation BestFeaturesScreenCoordinator {
|
||||
// First run screen delegate.
|
||||
__weak id<FirstRunScreenDelegate> _delegate;
|
||||
// Best Features Screen mediator.
|
||||
BestFeaturesScreenMediator* _mediator;
|
||||
// Transparent view used to block user interaction before the Best Features
|
||||
// Screen presents.
|
||||
UIView* _transparentView;
|
||||
}
|
||||
@synthesize baseNavigationController = _baseNavigationController;
|
||||
|
||||
@ -30,11 +44,68 @@
|
||||
|
||||
- (void)start {
|
||||
[super start];
|
||||
ProfileIOS* profile = self.browser->GetProfile();
|
||||
first_run::BestFeaturesScreenVariationType variation =
|
||||
first_run::GetBestFeaturesScreenVariationType();
|
||||
|
||||
if (variation == first_run::BestFeaturesScreenVariationType::
|
||||
kSignedInUsersOnlyAfterDBPromo) {
|
||||
signin::IdentityManager* identityManager =
|
||||
IdentityManagerFactory::GetForProfile(profile);
|
||||
if (!identityManager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
|
||||
// Skip the Best Features Screen if the "signed in users only" arm is
|
||||
// enabled and the user is not signed in.
|
||||
[_delegate screenWillFinishPresenting];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
segmentation_platform::SegmentationPlatformService* segmentationService =
|
||||
segmentation_platform::SegmentationPlatformServiceFactory::GetForProfile(
|
||||
profile);
|
||||
commerce::ShoppingService* shoppingService =
|
||||
commerce::ShoppingServiceFactory::GetForProfile(profile);
|
||||
_mediator = [[BestFeaturesScreenMediator alloc]
|
||||
initWithSegmentationService:segmentationService
|
||||
shoppingService:shoppingService];
|
||||
|
||||
// Retrieve the user's segmentation status before presenting the view if the
|
||||
// "shopping" arm is enabled. Otherwise, present the view.
|
||||
if (variation == first_run::BestFeaturesScreenVariationType::
|
||||
kShoppingUsersWithFallbackBeforeDBPromo) {
|
||||
// Present a transparent view to block UI interaction until screen presents.
|
||||
// TODO(crbug.com/396480750): This is a temporary solution. If the feature
|
||||
// becomes a full launch candidate, consider more polished solutions, like a
|
||||
// loading screen.
|
||||
_transparentView =
|
||||
[[UIView alloc] initWithFrame:self.baseViewController.view.bounds];
|
||||
_transparentView.backgroundColor = [UIColor clearColor];
|
||||
[self.baseViewController.view addSubview:_transparentView];
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
[_mediator retrieveShoppingUserSegmentWithCompletion:^{
|
||||
[weakSelf presentScreen];
|
||||
}];
|
||||
} else {
|
||||
[self presentScreen];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
_delegate = nil;
|
||||
[_mediator disconnect];
|
||||
_mediator = nil;
|
||||
_transparentView = nil;
|
||||
[super stop];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
// Presents the Best Features Screen.
|
||||
- (void)presentScreen {
|
||||
[_transparentView removeFromSuperview];
|
||||
_transparentView = nil;
|
||||
// TODO(crbug.com/396480750): Set consumer and present
|
||||
// BestFeaturesScreenViewController.
|
||||
}
|
||||
|
||||
@end
|
||||
|
46
ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_mediator.h
Normal file
46
ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_mediator.h
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_BEST_FEATURES_COORDINATOR_BEST_FEATURES_SCREEN_MEDIATOR_H_
|
||||
#define IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_BEST_FEATURES_COORDINATOR_BEST_FEATURES_SCREEN_MEDIATOR_H_
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "base/ios/block_types.h"
|
||||
|
||||
namespace commerce {
|
||||
class ShoppingService;
|
||||
}
|
||||
|
||||
namespace segmentation_platform {
|
||||
class SegmentationPlatformService;
|
||||
}
|
||||
|
||||
@protocol BestFeaturesScreenConsumer;
|
||||
|
||||
// Mediator for the Best Feature's Screen.
|
||||
@interface BestFeaturesScreenMediator : NSObject
|
||||
|
||||
// The consumer for this object. Setting it will update the consumer with
|
||||
// current data.
|
||||
@property(nonatomic, weak) id<BestFeaturesScreenConsumer> consumer;
|
||||
|
||||
// Designated initializer for this mediator.
|
||||
- (instancetype)
|
||||
initWithSegmentationService:
|
||||
(segmentation_platform::SegmentationPlatformService*)segmentationService
|
||||
shoppingService:(commerce::ShoppingService*)shoppingService
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
// Retrieves the status of the user's Shopping segment.
|
||||
- (void)retrieveShoppingUserSegmentWithCompletion:(ProceduralBlock)completion;
|
||||
|
||||
// Disconnects the mediator.
|
||||
- (void)disconnect;
|
||||
|
||||
@end
|
||||
|
||||
#endif // IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_BEST_FEATURES_COORDINATOR_BEST_FEATURES_SCREEN_MEDIATOR_H_
|
159
ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_mediator.mm
Normal file
159
ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_mediator.mm
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_mediator.h"
|
||||
|
||||
#import "base/memory/raw_ptr.h"
|
||||
#import "components/commerce/core/shopping_service.h"
|
||||
#import "components/password_manager/core/browser/password_manager_util.h"
|
||||
#import "components/segmentation_platform/public/constants.h"
|
||||
#import "components/segmentation_platform/public/result.h"
|
||||
#import "components/segmentation_platform/public/segmentation_platform_service.h"
|
||||
#import "ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_item.h"
|
||||
#import "ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_screen_consumer.h"
|
||||
#import "ios/chrome/browser/first_run/ui_bundled/features.h"
|
||||
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
|
||||
#import "ios/chrome/browser/shared/public/features/system_flags.h"
|
||||
|
||||
@implementation BestFeaturesScreenMediator {
|
||||
// Segmentation platform service used to retrieve the user's shopper status.
|
||||
raw_ptr<segmentation_platform::SegmentationPlatformService>
|
||||
_segmentationService;
|
||||
// Shopping service used to retrieve the user's Price Tracking eligibility.
|
||||
raw_ptr<commerce::ShoppingService> _shoppingService;
|
||||
// Whether the user has been classified as a shopping user.
|
||||
BOOL _shoppingUser;
|
||||
}
|
||||
|
||||
- (instancetype)
|
||||
initWithSegmentationService:
|
||||
(segmentation_platform::SegmentationPlatformService*)segmentationService
|
||||
shoppingService:(commerce::ShoppingService*)shoppingService {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_segmentationService = segmentationService;
|
||||
_shoppingService = shoppingService;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)disconnect {
|
||||
_segmentationService = nullptr;
|
||||
_shoppingService = nullptr;
|
||||
}
|
||||
|
||||
- (void)retrieveShoppingUserSegmentWithCompletion:(ProceduralBlock)completion {
|
||||
CHECK(_segmentationService);
|
||||
segmentation_platform::PredictionOptions options =
|
||||
segmentation_platform::PredictionOptions::ForCached();
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
auto classificationResultCallback = base::BindOnce(
|
||||
[](__typeof(self) strongSelf, ProceduralBlock completion,
|
||||
const segmentation_platform::ClassificationResult& shopper_result) {
|
||||
[strongSelf didReceiveShopperSegmentationResult:shopper_result];
|
||||
if (completion) {
|
||||
completion();
|
||||
}
|
||||
},
|
||||
weakSelf, completion);
|
||||
|
||||
_segmentationService->GetClassificationResult(
|
||||
segmentation_platform::kShoppingUserSegmentationKey, options, nullptr,
|
||||
std::move(classificationResultCallback));
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
- (void)setConsumer:(id<BestFeaturesScreenConsumer>)consumer {
|
||||
if (_consumer == consumer) {
|
||||
return;
|
||||
}
|
||||
_consumer = consumer;
|
||||
[_consumer setBestFeaturesItems:[self bestFeatureItems]];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
// Returns the list of BestFeaturesItems to be shown, based off the status of
|
||||
// the feature flag and the user's eligibility.
|
||||
- (NSArray<BestFeaturesItem*>*)bestFeatureItems {
|
||||
NSMutableArray<BestFeaturesItem*>* items = [NSMutableArray array];
|
||||
using enum first_run::BestFeaturesScreenVariationType;
|
||||
using enum BestFeaturesItemType;
|
||||
|
||||
first_run::BestFeaturesScreenVariationType variation =
|
||||
first_run::GetBestFeaturesScreenVariationType();
|
||||
switch (variation) {
|
||||
case kGeneralScreenAfterDBPromo:
|
||||
case kGeneralScreenBeforeDBPromo:
|
||||
[items addObject:[BestFeaturesItem itemForType:kLensSearch]];
|
||||
[items addObject:[BestFeaturesItem itemForType:kEnhancedSafeBrowsing]];
|
||||
[items addObject:[BestFeaturesItem itemForType:kLockedIncognitoTabs]];
|
||||
break;
|
||||
case kGeneralScreenWithPasswordItemAfterDBPromo:
|
||||
[items addObject:[BestFeaturesItem itemForType:kLensSearch]];
|
||||
[items addObject:[BestFeaturesItem itemForType:kEnhancedSafeBrowsing]];
|
||||
[items
|
||||
addObject:[BestFeaturesItem itemForType:kSaveAndAutofillPasswords]];
|
||||
break;
|
||||
case kShoppingUsersWithFallbackBeforeDBPromo:
|
||||
if (_shoppingUser && _shoppingService->IsShoppingListEligible()) {
|
||||
[items addObject:[BestFeaturesItem itemForType:kTabGroups]];
|
||||
[items addObject:[BestFeaturesItem itemForType:kLockedIncognitoTabs]];
|
||||
[items
|
||||
addObject:[BestFeaturesItem itemForType:kPriceTrackingAndInsights]];
|
||||
} else {
|
||||
// If the user isn't a shopping user or Price Tracking is not available
|
||||
// for them, fallback to other items.
|
||||
[items addObject:[BestFeaturesItem itemForType:kLensSearch]];
|
||||
[items addObject:[BestFeaturesItem itemForType:kEnhancedSafeBrowsing]];
|
||||
[items
|
||||
addObject:[BestFeaturesItem itemForType:kSaveAndAutofillPasswords]];
|
||||
}
|
||||
break;
|
||||
case kSignedInUsersOnlyAfterDBPromo:
|
||||
[items addObject:[BestFeaturesItem itemForType:kLensSearch]];
|
||||
[items addObject:[BestFeaturesItem itemForType:kEnhancedSafeBrowsing]];
|
||||
if (password_manager_util::IsCredentialProviderEnabledOnStartup(
|
||||
GetApplicationContext()->GetLocalState())) {
|
||||
[items
|
||||
addObject:[BestFeaturesItem itemForType:kSharePasswordsWithFamily]];
|
||||
} else {
|
||||
[items addObject:[BestFeaturesItem
|
||||
itemForType:kAutofillPasswordsInOtherApps]];
|
||||
}
|
||||
break;
|
||||
case kDisabled:
|
||||
case kAddressBarPromoInsteadOfDBPromo:
|
||||
NOTREACHED();
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
// Sets the user's shopper segmentation result.
|
||||
- (void)didReceiveShopperSegmentationResult:
|
||||
(const segmentation_platform::ClassificationResult&)shopper_result {
|
||||
if (experimental_flags::GetSegmentForForcedShopperExperience() ==
|
||||
segmentation_platform::kShoppingUserUmaName) {
|
||||
_shoppingUser = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
if (shopper_result.status ==
|
||||
segmentation_platform::PredictionStatus::kSucceeded) {
|
||||
// A shopper segment classification result is binary, `ordered_labels`
|
||||
// should only have one label.
|
||||
if (std::find(shopper_result.ordered_labels.begin(),
|
||||
shopper_result.ordered_labels.end(),
|
||||
segmentation_platform::kShoppingUserUmaName) !=
|
||||
shopper_result.ordered_labels.end()) {
|
||||
_shoppingUser = YES;
|
||||
return;
|
||||
}
|
||||
}
|
||||
_shoppingUser = NO;
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,17 @@
|
||||
# Copyright 2025 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
source_set("ui") {
|
||||
sources = [
|
||||
"best_features_item.h",
|
||||
"best_features_item.mm",
|
||||
"best_features_screen_consumer.h",
|
||||
]
|
||||
deps = [
|
||||
"//base",
|
||||
"//ios/chrome/app/strings",
|
||||
"//ios/chrome/browser/shared/ui/symbols",
|
||||
"//ui/base",
|
||||
]
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_BEST_FEATURES_UI_BEST_FEATURES_ITEM_H_
|
||||
#define IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_BEST_FEATURES_UI_BEST_FEATURES_ITEM_H_
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
// An enum representing the different features promoted by Bling's Best
|
||||
// Features.
|
||||
enum class BestFeaturesItemType {
|
||||
kLensSearch = 0,
|
||||
kEnhancedSafeBrowsing = 1,
|
||||
kLockedIncognitoTabs = 2,
|
||||
kSaveAndAutofillPasswords = 3,
|
||||
kTabGroups = 4,
|
||||
kPriceTrackingAndInsights = 5,
|
||||
kAutofillPasswordsInOtherApps = 6,
|
||||
kSharePasswordsWithFamily = 7
|
||||
};
|
||||
|
||||
// Holds properties and values needed to configure the items in the Best
|
||||
// Features Screen.
|
||||
@interface BestFeaturesItem : NSObject
|
||||
|
||||
// Best Features type.
|
||||
@property(nonatomic, assign) BestFeaturesItemType type;
|
||||
// Best Features item title.
|
||||
@property(nonatomic, copy) NSString* title;
|
||||
// Best Features item subtitle.
|
||||
@property(nonatomic, copy) NSString* subtitle;
|
||||
// Best Features item icon image.
|
||||
@property(nonatomic, copy) UIImage* iconImage;
|
||||
// Best Features item icon background color.
|
||||
@property(nonatomic, copy) UIColor* iconBackgroundColor;
|
||||
// Best Features item animation name.
|
||||
@property(nonatomic, copy) NSString* animationName;
|
||||
// Best Features item instruction steps.
|
||||
@property(nonatomic, copy) NSArray<NSString*>* instructionSteps;
|
||||
|
||||
// Returns a configured item for the given `itemType`.
|
||||
+ (BestFeaturesItem*)itemForType:(BestFeaturesItemType)itemType;
|
||||
|
||||
@end
|
||||
|
||||
#endif // IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_BEST_FEATURES_UI_BEST_FEATURES_ITEM_H_
|
@ -0,0 +1,19 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_item.h"
|
||||
|
||||
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
|
||||
#import "ios/chrome/grit/ios_strings.h"
|
||||
#import "ui/base/l10n/l10n_util_mac.h"
|
||||
|
||||
@implementation BestFeaturesItem
|
||||
|
||||
+ (BestFeaturesItem*)itemForType:(BestFeaturesItemType)itemType {
|
||||
BestFeaturesItem* item = [[BestFeaturesItem alloc] init];
|
||||
// TODO(crbug.com/396480750): Set the properties for the item.
|
||||
return item;
|
||||
}
|
||||
|
||||
@end
|
20
ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_screen_consumer.h
Normal file
20
ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_screen_consumer.h
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_BEST_FEATURES_UI_BEST_FEATURES_SCREEN_CONSUMER_H_
|
||||
#define IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_BEST_FEATURES_UI_BEST_FEATURES_SCREEN_CONSUMER_H_
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class BestFeaturesItem;
|
||||
|
||||
// Defines methods to set the contents of the Best Features Screen.
|
||||
@protocol BestFeaturesScreenConsumer <NSObject>
|
||||
|
||||
// Sets the list of items for the Best Features Screen.
|
||||
- (void)setBestFeaturesItems:(NSArray<BestFeaturesItem*>*)items;
|
||||
|
||||
@end
|
||||
|
||||
#endif // IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_BEST_FEATURES_UI_BEST_FEATURES_SCREEN_CONSUMER_H_
|
Reference in New Issue
Block a user