blob: 23a405ed035e8e3570218139ed9c8d9501c91027 [file] [log] [blame]
// Copyright 2017 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/ui/content_suggestions/content_suggestions_coordinator.h"
#import <string_view>
#import <vector>
#import "base/apple/foundation_util.h"
#import "base/ios/block_types.h"
#import "base/ios/ios_util.h"
#import "base/memory/raw_ptr.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/strings/sys_string_conversions.h"
#import "components/commerce/core/commerce_feature_list.h"
#import "components/feed/core/v2/public/ios/pref_names.h"
#import "components/image_fetcher/core/image_data_fetcher.h"
#import "components/ntp_tiles/most_visited_sites.h"
#import "components/password_manager/core/browser/ui/credential_ui_entry.h"
#import "components/password_manager/core/browser/ui/password_check_referrer.h"
#import "components/prefs/pref_service.h"
#import "components/search_engines/template_url.h"
#import "components/search_engines/template_url_prepopulate_data.h"
#import "components/search_engines/template_url_service.h"
#import "components/segmentation_platform/embedder/home_modules/constants.h"
#import "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h"
#import "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h"
#import "components/segmentation_platform/public/features.h"
#import "components/segmentation_platform/public/segmentation_platform_service.h"
#import "components/send_tab_to_self/features.h"
#import "ios/chrome/app/application_delegate/app_state.h"
#import "ios/chrome/app/profile/profile_state.h"
#import "ios/chrome/app/tests_hook.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin/signin_constants.h"
#import "ios/chrome/browser/bookmarks/model/bookmark_model_factory.h"
#import "ios/chrome/browser/commerce/model/push_notification/push_notification_feature.h"
#import "ios/chrome/browser/commerce/model/shopping_service_factory.h"
#import "ios/chrome/browser/discover_feed/model/discover_feed_service.h"
#import "ios/chrome/browser/discover_feed/model/discover_feed_service_factory.h"
#import "ios/chrome/browser/favicon/model/favicon_loader.h"
#import "ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h"
#import "ios/chrome/browser/favicon/model/ios_chrome_large_icon_cache_factory.h"
#import "ios/chrome/browser/favicon/model/ios_chrome_large_icon_service_factory.h"
#import "ios/chrome/browser/favicon/model/large_icon_cache.h"
#import "ios/chrome/browser/feature_engagement/model/tracker_factory.h"
#import "ios/chrome/browser/home_customization/coordinator/home_customization_delegate.h"
#import "ios/chrome/browser/lens/ui_bundled/lens_entrypoint.h"
#import "ios/chrome/browser/menu/ui_bundled/browser_action_factory.h"
#import "ios/chrome/browser/menu/ui_bundled/menu_histograms.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_tab_helper.h"
#import "ios/chrome/browser/ntp/model/set_up_list_item_type.h"
#import "ios/chrome/browser/ntp/model/set_up_list_prefs.h"
#import "ios/chrome/browser/ntp/shared/metrics/home_metrics.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_actions_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_constants.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_feature.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_header_constants.h"
#import "ios/chrome/browser/ntp_tiles/model/ios_most_visited_sites_factory.h"
#import "ios/chrome/browser/parcel_tracking/features.h"
#import "ios/chrome/browser/parcel_tracking/parcel_tracking_prefs.h"
#import "ios/chrome/browser/passwords/model/password_checkup_utils.h"
#import "ios/chrome/browser/policy/model/policy_util.h"
#import "ios/chrome/browser/promos_manager/model/promos_manager_factory.h"
#import "ios/chrome/browser/push_notification/model/constants.h"
#import "ios/chrome/browser/push_notification/model/provisional_push_notification_util.h"
#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
#import "ios/chrome/browser/push_notification/model/push_notification_service.h"
#import "ios/chrome/browser/push_notification/model/push_notification_settings_util.h"
#import "ios/chrome/browser/push_notification/model/push_notification_util.h"
#import "ios/chrome/browser/push_notification/ui_bundled/notifications_opt_in_alert_coordinator.h"
#import "ios/chrome/browser/push_notification/ui_bundled/notifications_opt_in_coordinator.h"
#import "ios/chrome/browser/push_notification/ui_bundled/notifications_opt_in_coordinator_delegate.h"
#import "ios/chrome/browser/reading_list/model/reading_list_model_factory.h"
#import "ios/chrome/browser/safety_check/model/ios_chrome_safety_check_manager.h"
#import "ios/chrome/browser/safety_check/model/ios_chrome_safety_check_manager_factory.h"
#import "ios/chrome/browser/safety_check_notifications/utils/constants.h"
#import "ios/chrome/browser/safety_check_notifications/utils/utils.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/segmentation_platform/model/segmentation_platform_service_factory.h"
#import "ios/chrome/browser/shared/coordinator/alert/action_sheet_coordinator.h"
#import "ios/chrome/browser/shared/coordinator/alert/alert_coordinator.h"
#import "ios/chrome/browser/shared/coordinator/layout_guide/layout_guide_util.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/prefs/pref_backed_boolean.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/credential_provider_promo_commands.h"
#import "ios/chrome/browser/shared/public/commands/docking_promo_commands.h"
#import "ios/chrome/browser/shared/public/commands/lens_commands.h"
#import "ios/chrome/browser/shared/public/commands/omnibox_commands.h"
#import "ios/chrome/browser/shared/public/commands/open_lens_input_selection_command.h"
#import "ios/chrome/browser/shared/public/commands/open_new_tab_command.h"
#import "ios/chrome/browser/shared/public/commands/parcel_tracking_opt_in_commands.h"
#import "ios/chrome/browser/shared/public/commands/search_image_with_lens_command.h"
#import "ios/chrome/browser/shared/public/commands/settings_commands.h"
#import "ios/chrome/browser/shared/public/commands/show_signin_command.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/shared/public/commands/whats_new_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service_factory.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/tips_manager/model/tips_manager_ios_factory.h"
#import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.h"
#import "ios/chrome/browser/ui/content_suggestions/cells/most_visited_tiles_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/cells/shortcuts_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_commands.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_delegate.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_image_data_source.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_recorder.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller_audience.h"
#import "ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_collection_view.h"
#import "ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_collection_view_audience.h"
#import "ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_module_container_delegate.h"
#import "ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_ranking_model.h"
#import "ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_utils.h"
#import "ios/chrome/browser/ui/content_suggestions/magic_stack_half_sheet_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/magic_stack_half_sheet_table_view_controller.h"
#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
#import "ios/chrome/browser/ui/content_suggestions/parcel_tracking/magic_stack_parcel_list_half_sheet_table_view_controller.h"
#import "ios/chrome/browser/ui/content_suggestions/parcel_tracking/parcel_tracking_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/price_tracking_promo/price_tracking_promo_action_delegate.h"
#import "ios/chrome/browser/ui/content_suggestions/price_tracking_promo/price_tracking_promo_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/safety_check/safety_check_magic_stack_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/safety_check/types.h"
#import "ios/chrome/browser/ui/content_suggestions/safety_check/utils.h"
#import "ios/chrome/browser/ui/content_suggestions/send_tab_to_self/send_tab_promo_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/set_up_list_content_notification_promo_coordinator.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/set_up_list_content_notification_promo_coordinator_delegate.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/set_up_list_default_browser_promo_coordinator.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/set_up_list_default_browser_promo_coordinator_delegate.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/set_up_list_item_view.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/set_up_list_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/set_up_list_show_more_view_controller.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/set_up_list_tap_delegate.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/utils.h"
#import "ios/chrome/browser/ui/content_suggestions/shop_card/shop_card_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/tips/tips_magic_stack_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/tips/tips_metrics.h"
#import "ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.h"
#import "ios/chrome/browser/ui/content_suggestions/tips/tips_passwords_coordinator.h"
#import "ios/chrome/browser/ui/content_suggestions/tips/tips_prefs.h"
#import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/web/public/web_state.h"
#import "services/network/public/cpp/shared_url_loader_factory.h"
#import "third_party/search_engines_data/resources/definitions/prepopulated_engines.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/l10n_util_mac.h"
#import "url/gurl.h"
namespace {
// Logs the user's decision to opt-in or opt-out of Safety Check notifications
// from the Magic Stack. Determines the source based on the `viaContextMenu`
// flag: if `true`, the action is logged as originating from the long-press
// menu; otherwise, it is logged as originating from the top-right action
// button.
void LogSafetyCheckNotificationOptIn(bool viaContextMenu) {
if (viaContextMenu) {
LogSafetyCheckNotificationOptInSource(
SafetyCheckNotificationsOptInSource::kMagicStackLongPressMenuOptIn,
SafetyCheckNotificationsOptInSource::kMagicStackLongPressMenuOptOut);
} else {
LogSafetyCheckNotificationOptInSource(
SafetyCheckNotificationsOptInSource::
kMagicStackTopRightActionButtonOptIn,
SafetyCheckNotificationsOptInSource::
kMagicStackTopRightActionButtonOptOut);
}
}
} // namespace
using segmentation_platform::TipIdentifier;
@interface ContentSuggestionsCoordinator () <
ContentSuggestionsCommands,
ContentSuggestionsViewControllerAudience,
MagicStackCollectionViewControllerAudience,
MagicStackHalfSheetTableViewControllerDelegate,
MagicStackModuleContainerDelegate,
MagicStackParcelListHalfSheetTableViewControllerDelegate,
TipsPasswordsCoordinatorDelegate,
NotificationsOptInAlertCoordinatorDelegate,
NotificationsOptInCoordinatorDelegate,
PriceTrackingPromoActionDelegate,
SetUpListContentNotificationPromoCoordinatorDelegate,
SetUpListDefaultBrowserPromoCoordinatorDelegate,
SetUpListTapDelegate>
@property(nonatomic, strong)
ContentSuggestionsViewController* contentSuggestionsViewController;
// Authentication Service for the user's signed-in state.
@property(nonatomic, assign) AuthenticationService* authService;
// The mediator used by this coordinator.
@property(nonatomic, strong)
ContentSuggestionsMediator* contentSuggestionsMediator;
// Metrics recorder for the content suggestions.
@property(nonatomic, strong)
ContentSuggestionsMetricsRecorder* contentSuggestionsMetricsRecorder;
// Parcel Tracking Mediator.
@property(nonatomic, strong) ParcelTrackingMediator* parcelTrackingMediator;
@property(nonatomic, strong) SetUpListMediator* setUpListMediator;
@end
@implementation ContentSuggestionsCoordinator {
// The coordinator that displays the Default Browser Promo for the Set Up
// List.
SetUpListDefaultBrowserPromoCoordinator* _defaultBrowserPromoCoordinator;
// The coordinator that displays the Content Notification Promo for the Set Up
// List.
SetUpListContentNotificationPromoCoordinator* _contentNotificationCoordinator;
// The coordinator that displays the opt-in notification settings view for the
// Set Up List.
NotificationsOptInCoordinator* _notificationsOptInCoordinator;
// The Show More Menu presented from the Set Up List in the Magic Stack.
SetUpListShowMoreViewController* _setUpListShowMoreViewController;
// The coordinator for displaying tips related to Passwords.
TipsPasswordsCoordinator* _tipsPasswordsCoordinator;
// The edit half sheet for toggling all Magic Stack modules.
MagicStackHalfSheetTableViewController*
_magicStackHalfSheetTableViewController;
MagicStackHalfSheetMediator* _magicStackHalfSheetMediator;
// The parcel list half sheet to see all tracked parcels.
MagicStackParcelListHalfSheetTableViewController*
_parcelListHalfSheetTableViewController;
// The coordinator used to present a modal alert for the parcel tracking
// module.
AlertCoordinator* _parcelTrackingAlertCoordinator;
// Displays alert giving the user the option to turn notifications
// on for the app. This is for the third opt in flow where notifications
// have previously been turned off.
AlertCoordinator* _priceTrackingPromoAlertCoordinator;
// The coordinator used to present an alert to enable Tips notifications.
NotificationsOptInAlertCoordinator* _notificationsOptInAlertCoordinator;
MagicStackRankingModel* _magicStackRankingModel;
// Module mediators.
ShortcutsMediator* _shortcutsMediator;
SafetyCheckMagicStackMediator* _safetyCheckMediator;
TipsMagicStackMediator* _tipsMediator;
MostVisitedTilesMediator* _mostVisitedTilesMediator;
TabResumptionMediator* _tabResumptionMediator;
PriceTrackingPromoMediator* _priceTrackingPromoMediator;
ShopCardMediator* _shopCardMediator;
SendTabPromoMediator* _sendTabPromoMediator;
MagicStackCollectionViewController* _magicStackCollectionView;
raw_ptr<segmentation_platform::SegmentationPlatformService>
_segmentationService;
raw_ptr<segmentation_platform::DeviceSwitcherResultDispatcher>
_deviceSwitcherResultDispatcher;
}
- (void)start {
DCHECK(self.browser);
DCHECK(self.NTPActionsDelegate);
if (self.started) {
// Prevent this coordinator from being started twice in a row
return;
}
_started = YES;
ProfileIOS* profile = self.browser->GetProfile();
PrefService* prefs = ProfileIOS::FromBrowserState(profile)->GetPrefs();
_segmentationService =
segmentation_platform::SegmentationPlatformServiceFactory::GetForProfile(
profile);
_deviceSwitcherResultDispatcher = segmentation_platform::
SegmentationPlatformServiceFactory::GetDispatcherForProfile(profile);
self.authService = AuthenticationServiceFactory::GetForProfile(profile);
// Conditionally register for provisional Safety Check notifications if the
// feature is enabled.
//
// TODO(crbug.com/366182129): Move Safety Check provisional notification
// enrollment to `SafetyCheckNotificationClient` once
// `ProvisionalPushNotificationUtil` circular dependencies are fixed.
if (IsSafetyCheckNotificationsEnabled()) {
[ProvisionalPushNotificationUtil
enrollUserToProvisionalNotificationsForClientIds:
{PushNotificationClientId::kSafetyCheck}
clientEnabledForProvisional:NO
withAuthService:self.authService
deviceInfoSyncService:nil];
}
favicon::LargeIconService* largeIconService =
IOSChromeLargeIconServiceFactory::GetForProfile(profile);
LargeIconCache* cache =
IOSChromeLargeIconCacheFactory::GetForProfile(profile);
std::unique_ptr<ntp_tiles::MostVisitedSites> mostVisitedFactory =
IOSMostVisitedSitesFactory::NewForBrowserState(profile);
ReadingListModel* readingListModel =
ReadingListModelFactory::GetForProfile(profile);
self.contentSuggestionsMetricsRecorder =
[[ContentSuggestionsMetricsRecorder alloc]
initWithLocalState:GetApplicationContext()->GetLocalState()];
syncer::SyncService* syncService = SyncServiceFactory::GetForProfile(profile);
AuthenticationService* authenticationService =
AuthenticationServiceFactory::GetForProfile(profile);
signin::IdentityManager* identityManager =
IdentityManagerFactory::GetForProfile(profile);
commerce::ShoppingService* shoppingService =
commerce::ShoppingServiceFactory::GetForProfile(profile);
self.contentSuggestionsMediator = [[ContentSuggestionsMediator alloc] init];
NSMutableArray* moduleMediators = [NSMutableArray array];
_mostVisitedTilesMediator = [[MostVisitedTilesMediator alloc]
initWithMostVisitedSite:std::move(mostVisitedFactory)
prefService:prefs
largeIconService:largeIconService
largeIconCache:cache
URLLoadingBrowserAgent:UrlLoadingBrowserAgent::FromBrowser(
self.browser)];
_mostVisitedTilesMediator.contentSuggestionsDelegate = self.delegate;
_mostVisitedTilesMediator.contentSuggestionsMetricsRecorder =
self.contentSuggestionsMetricsRecorder;
_mostVisitedTilesMediator.actionFactory = [[BrowserActionFactory alloc]
initWithBrowser:self.browser
scenario:kMenuScenarioHistogramMostVisitedEntry];
_mostVisitedTilesMediator.snackbarHandler =
static_cast<id<SnackbarCommands>>(self.browser->GetCommandDispatcher());
_mostVisitedTilesMediator.NTPActionsDelegate = self.NTPActionsDelegate;
[moduleMediators addObject:_mostVisitedTilesMediator];
self.contentSuggestionsMediator.mostVisitedTilesMediator =
_mostVisitedTilesMediator;
_shortcutsMediator = [[ShortcutsMediator alloc]
initWithReadingListModel:readingListModel
featureEngagementTracker:feature_engagement::TrackerFactory::
GetForProfile(profile)
authService:authenticationService];
_shortcutsMediator.contentSuggestionsMetricsRecorder =
self.contentSuggestionsMetricsRecorder;
_shortcutsMediator.NTPActionsDelegate = self.NTPActionsDelegate;
_shortcutsMediator.dispatcher = static_cast<
id<ApplicationCommands, BrowserCoordinatorCommands, WhatsNewCommands>>(
self.browser->GetCommandDispatcher());
[moduleMediators addObject:_shortcutsMediator];
self.contentSuggestionsMediator.shortcutsMediator = _shortcutsMediator;
if (IsTabResumptionEnabled()) {
_tabResumptionMediator = [[TabResumptionMediator alloc]
initWithLocalState:GetApplicationContext()->GetLocalState()
prefService:prefs
identityManager:identityManager
browser:self.browser
shoppingService:commerce::ShoppingServiceFactory::GetForProfile(
profile)];
_tabResumptionMediator.NTPActionsDelegate = self.NTPActionsDelegate;
_tabResumptionMediator.contentSuggestionsMetricsRecorder =
self.contentSuggestionsMetricsRecorder;
[moduleMediators addObject:_tabResumptionMediator];
}
if (IsPriceTrackingPromoCardEnabled(shoppingService, authenticationService,
prefs)) {
_priceTrackingPromoMediator = [[PriceTrackingPromoMediator alloc]
initWithShoppingService:commerce::ShoppingServiceFactory::GetForProfile(
profile)
bookmarkModel:ios::BookmarkModelFactory::GetForProfile(
profile)
imageFetcher:std::make_unique<
image_fetcher::ImageDataFetcher>(
profile->GetSharedURLLoaderFactory())
prefService:prefs
localState:GetApplicationContext()->GetLocalState()
pushNotificationService:GetApplicationContext()
->GetPushNotificationService()
authenticationService:self.authService
faviconLoader:IOSChromeFaviconLoaderFactory::GetForProfile(
profile)];
_priceTrackingPromoMediator.dispatcher =
static_cast<id<ApplicationCommands, SnackbarCommands>>(
self.browser->GetCommandDispatcher());
_priceTrackingPromoMediator.actionDelegate = self;
_priceTrackingPromoMediator.NTPActionsDelegate = self.NTPActionsDelegate;
[moduleMediators addObject:_priceTrackingPromoMediator];
}
if (base::FeatureList::IsEnabled(commerce::kShopCard) &&
(commerce::kShopCardVariation.Get() == commerce::kShopCardArm1 ||
commerce::kShopCardVariation.Get() == commerce::kShopCardArm2)) {
// If ShopCard experiment is on, create the ShopCard mediator.
// Note at this point we don't know which of the 4 variants will show.
_shopCardMediator = [[ShopCardMediator alloc]
initWithShoppingService:commerce::ShoppingServiceFactory::GetForProfile(
profile)];
[moduleMediators addObject:_shopCardMediator];
}
if (IsIOSParcelTrackingEnabled() &&
!IsParcelTrackingDisabled(
IsHomeCustomizationEnabled()
? prefs
: GetApplicationContext()->GetLocalState())) {
_parcelTrackingMediator = [[ParcelTrackingMediator alloc]
initWithShoppingService:shoppingService
URLLoadingBrowserAgent:UrlLoadingBrowserAgent::FromBrowser(
self.browser)
prefService:IsHomeCustomizationEnabled()
? prefs
: GetApplicationContext()->GetLocalState()];
_parcelTrackingMediator.NTPActionsDelegate = self.NTPActionsDelegate;
[moduleMediators addObject:_parcelTrackingMediator];
}
if (IsSafetyCheckMagicStackEnabled()) {
IOSChromeSafetyCheckManager* safetyCheckManager =
IOSChromeSafetyCheckManagerFactory::GetForProfile(profile);
_safetyCheckMediator = [[SafetyCheckMagicStackMediator alloc]
initWithSafetyCheckManager:safetyCheckManager
localState:GetApplicationContext()->GetLocalState()
userState:prefs
profileState:self.browser->GetSceneState().profileState];
_safetyCheckMediator.presentationAudience = self;
[moduleMediators addObject:_safetyCheckMediator];
}
if (send_tab_to_self::
IsSendTabIOSPushNotificationsEnabledWithMagicStackCard()) {
FaviconLoader* faviconLoader =
IOSChromeFaviconLoaderFactory::GetForProfile(profile);
_sendTabPromoMediator =
[[SendTabPromoMediator alloc] initWithFaviconLoader:faviconLoader
prefService:prefs];
_sendTabPromoMediator.notificationsDelegate = self;
[moduleMediators addObject:_sendTabPromoMediator];
}
if (IsTipsMagicStackEnabled() &&
!tips_prefs::IsTipsInMagicStackDisabled(prefs)) {
_tipsMediator = [[TipsMagicStackMediator alloc]
initWithIdentifier:TipIdentifier::kUnknown
profilePrefService:prefs
shoppingService:commerce::ShoppingServiceFactory::GetForProfile(
profile)
bookmarkModel:ios::BookmarkModelFactory::GetForProfile(profile)
imageFetcher:std::make_unique<image_fetcher::ImageDataFetcher>(
profile->GetSharedURLLoaderFactory())];
_tipsMediator.presentationAudience = self;
[moduleMediators addObject:_tipsMediator];
}
if (!_mostVisitedTilesMediator.inMagicStack) {
ContentSuggestionsViewController* viewController =
[[ContentSuggestionsViewController alloc] init];
viewController.audience = self;
viewController.urlLoadingBrowserAgent =
UrlLoadingBrowserAgent::FromBrowser(self.browser);
viewController.contentSuggestionsMetricsRecorder =
self.contentSuggestionsMetricsRecorder;
self.contentSuggestionsViewController = viewController;
}
BOOL isSetupListEnabled = set_up_list_utils::IsSetUpListActive(
GetApplicationContext()->GetLocalState(), prefs);
if (isSetupListEnabled) {
const TemplateURL* defaultSearchURLTemplate =
ios::TemplateURLServiceFactory::GetForProfile(profile)
->GetDefaultSearchProvider();
BOOL isDefaultSearchEngine = defaultSearchURLTemplate &&
defaultSearchURLTemplate->prepopulate_id() ==
TemplateURLPrepopulateData::google.id;
_setUpListMediator = [[SetUpListMediator alloc]
initWithPrefService:prefs
syncService:syncService
identityManager:identityManager
authenticationService:authenticationService
sceneState:self.browser->GetSceneState()
isDefaultSearchEngine:isDefaultSearchEngine
segmentationService:_segmentationService
deviceSwitcherResultDispatcher:_deviceSwitcherResultDispatcher];
if (IsSegmentedDefaultBrowserPromoEnabled()) {
[_setUpListMediator retrieveUserSegment];
}
_setUpListMediator.commandHandler = self;
_setUpListMediator.contentSuggestionsMetricsRecorder =
self.contentSuggestionsMetricsRecorder;
_setUpListMediator.delegate = self.delegate;
self.contentSuggestionsMediator.setUpListMediator = _setUpListMediator;
[moduleMediators addObject:_setUpListMediator];
}
_magicStackRankingModel = [[MagicStackRankingModel alloc]
initWithSegmentationService:_segmentationService
shoppingService:commerce::ShoppingServiceFactory::
GetForProfile(profile)
authService:self.authService
prefService:prefs
localState:GetApplicationContext()->GetLocalState()
moduleMediators:moduleMediators
tipsManager:TipsManagerIOSFactory::GetForProfile(
self.browser->GetProfile())
templateURLService:ios::TemplateURLServiceFactory::GetForProfile(
self.browser->GetProfile())];
_magicStackRankingModel.contentSuggestionsMetricsRecorder =
self.contentSuggestionsMetricsRecorder;
self.contentSuggestionsMediator.magicStackRankingModel =
_magicStackRankingModel;
_magicStackRankingModel.delegate = self.contentSuggestionsMediator;
_magicStackRankingModel.homeStartDataSource = self.homeStartDataSource;
_magicStackCollectionView = [[MagicStackCollectionViewController alloc] init];
_magicStackCollectionView.audience = self;
_mostVisitedTilesMediator.consumer = self.contentSuggestionsViewController;
self.contentSuggestionsMediator.magicStackConsumer =
_magicStackCollectionView;
self.contentSuggestionsMediator.consumer =
self.contentSuggestionsViewController;
[self.browser->GetCommandDispatcher()
startDispatchingToTarget:self
forProtocol:@protocol(ContentSuggestionsCommands)];
}
- (void)stop {
_segmentationService = nullptr;
_deviceSwitcherResultDispatcher = nullptr;
[self.parcelTrackingMediator disconnect];
self.parcelTrackingMediator = nil;
[_shortcutsMediator disconnect];
_shortcutsMediator = nil;
[_safetyCheckMediator disconnect];
_safetyCheckMediator = nil;
[_sendTabPromoMediator disconnect];
_sendTabPromoMediator = nil;
[_tipsMediator disconnect];
_tipsMediator = nil;
[_setUpListMediator disconnect];
_setUpListMediator = nil;
[_mostVisitedTilesMediator disconnect];
_mostVisitedTilesMediator = nil;
[_tabResumptionMediator disconnect];
_tabResumptionMediator = nil;
[_magicStackRankingModel disconnect];
_magicStackRankingModel = nil;
[self.contentSuggestionsMediator disconnect];
self.contentSuggestionsMediator = nil;
[self.contentSuggestionsMetricsRecorder disconnect];
self.contentSuggestionsMetricsRecorder = nil;
self.contentSuggestionsViewController.audience = nil;
self.contentSuggestionsViewController = nil;
[_defaultBrowserPromoCoordinator stop];
_defaultBrowserPromoCoordinator = nil;
[_magicStackHalfSheetMediator disconnect];
_magicStackHalfSheetMediator = nil;
[_magicStackHalfSheetTableViewController.presentingViewController
dismissViewControllerAnimated:NO
completion:nil];
_magicStackHalfSheetTableViewController = nil;
[self dismissParcelListHalfSheet];
[self dismissParcelTrackingAlertCoordinator];
[_notificationsOptInAlertCoordinator stop];
_notificationsOptInAlertCoordinator = nil;
[self.browser->GetCommandDispatcher()
stopDispatchingForProtocol:@protocol(ContentSuggestionsCommands)];
_started = NO;
}
- (ContentSuggestionsViewController*)viewController {
return self.contentSuggestionsViewController;
}
#pragma mark - Public methods
- (void)refresh {
[_magicStackCollectionView reset];
// Refresh in case there are new MVT to show.
[_mostVisitedTilesMediator refreshMostVisitedTiles];
[_safetyCheckMediator reset];
[_parcelTrackingMediator reset];
[_priceTrackingPromoMediator reset];
[_magicStackRankingModel fetchLatestMagicStackRanking];
// Fetch after resetting ranking since parcels could be returned
// synchronously.
[_parcelTrackingMediator fetchTrackedParcels];
[_priceTrackingPromoMediator fetchLatestSubscription];
}
#pragma mark - ContentSuggestionsCommands
- (void)showSetUpListSeeMoreMenuExpanded:(BOOL)expanded {
[_setUpListShowMoreViewController.presentingViewController
dismissViewControllerAnimated:NO
completion:nil];
NSArray<SetUpListItemViewData*>* items = [self.setUpListMediator allItems];
_setUpListShowMoreViewController =
[[SetUpListShowMoreViewController alloc] initWithItems:items
tapDelegate:self];
_setUpListShowMoreViewController.modalPresentationStyle =
UIModalPresentationPageSheet;
UISheetPresentationController* presentationController =
_setUpListShowMoreViewController.sheetPresentationController;
presentationController.prefersEdgeAttachedInCompactHeight = YES;
presentationController.widthFollowsPreferredContentSizeWhenEdgeAttached = YES;
presentationController.detents = @[
UISheetPresentationControllerDetent.mediumDetent,
UISheetPresentationControllerDetent.largeDetent
];
if (expanded) {
presentationController.selectedDetentIdentifier =
UISheetPresentationControllerDetentIdentifierLarge;
}
presentationController.preferredCornerRadius = 16;
[_magicStackCollectionView
presentViewController:_setUpListShowMoreViewController
animated:YES
completion:nil];
}
#pragma mark - ContentSuggestionsViewControllerAudience
- (void)viewWillDisappear {
DiscoverFeedServiceFactory::GetForProfile(self.browser->GetProfile())
->SetIsShownOnStartSurface(false);
}
- (void)didSelectTip:(segmentation_platform::TipIdentifier)tip {
CHECK(IsTipsMagicStackEnabled());
CHECK(_tipsMediator);
__weak __typeof(self) weakSelf = self;
ProceduralBlock completion = ^{
[weakSelf openTipDestination:tip];
};
[_tipsMediator removeModuleWithCompletion:completion];
}
- (void)openTipDestination:(segmentation_platform::TipIdentifier)tip {
CHECK(IsTipsMagicStackEnabled());
CHECK(_tipsMediator);
// Log the Tips (Magic Stack) Module that the user tapped on.
base::UmaHistogramEnumeration(kTipsMagicStackModuleTappedTypeHistogram, tip);
switch (tip) {
case TipIdentifier::kUnknown:
NOTREACHED();
case TipIdentifier::kLensShop:
case TipIdentifier::kLensSearch:
case TipIdentifier::kLensTranslate: {
LensEntrypoint entryPoint = tip == TipIdentifier::kLensTranslate
? LensEntrypoint::TranslateOnebox
: LensEntrypoint::NewTabPage;
if (tip == TipIdentifier::kLensShop &&
TipsLensShopExperimentTypeEnabled() ==
TipsLensShopExperimentType::kWithProductImage &&
_tipsMediator.state.productImageData.length > 0) {
UIImage* productImage =
[UIImage imageWithData:_tipsMediator.state.productImageData];
if (productImage) {
SearchImageWithLensCommand* command =
[[SearchImageWithLensCommand alloc] initWithImage:productImage
entryPoint:entryPoint];
[HandlerForProtocol(self.browser->GetCommandDispatcher(),
LensCommands) searchImageWithLens:command];
break;
}
}
OpenLensInputSelectionCommand* command =
[[OpenLensInputSelectionCommand alloc]
initWithEntryPoint:entryPoint
presentationStyle:LensInputSelectionPresentationStyle::
SlideFromRight
presentationCompletion:nil];
command.presentNTPLensIconBubbleOnDismiss = YES;
[HandlerForProtocol(self.browser->GetCommandDispatcher(), LensCommands)
openLensInputSelection:command];
break;
}
case TipIdentifier::kAddressBarPosition:
[HandlerForProtocol(self.browser->GetCommandDispatcher(),
BrowserCoordinatorCommands)
showOmniboxPositionChoice];
break;
case TipIdentifier::kEnhancedSafeBrowsing: {
if (TipsSafeBrowsingExperimentTypeEnabled() ==
TipsSafeBrowsingExperimentType::kShowSafeBrowsingSettingsPage) {
[HandlerForProtocol(self.browser->GetCommandDispatcher(),
SettingsCommands) showSafeBrowsingSettings];
} else {
[HandlerForProtocol(self.browser->GetCommandDispatcher(),
BrowserCoordinatorCommands)
showEnhancedSafeBrowsingPromo];
}
break;
}
case TipIdentifier::kSavePasswords:
case TipIdentifier::kAutofillPasswords: {
_tipsPasswordsCoordinator = [[TipsPasswordsCoordinator alloc]
initWithBaseViewController:self.magicStackCollectionView
browser:self.browser
identifier:_tipsMediator.state.identifier];
_tipsPasswordsCoordinator.delegate = self;
[_tipsPasswordsCoordinator start];
break;
}
}
[self.NTPActionsDelegate tipsOpened];
std::optional<std::string_view> name = OutputLabelForTipIdentifier(tip);
if (name.has_value()) {
segmentation_platform::home_modules::HomeModulesCardRegistry* registry =
segmentation_platform::SegmentationPlatformServiceFactory::
GetHomeCardRegistryForProfile(self.browser->GetProfile());
CHECK(registry);
registry->NotifyCardInteracted(std::string(name.value()).c_str());
}
}
#pragma mark - MagicStackCollectionViewAudience
- (void)didTapMagicStackEditButton {
base::RecordAction(base::UserMetricsAction("IOSMagicStackSettingsOpened"));
if (IsHomeCustomizationEnabled()) {
[self.delegate openMagicStackCustomizationMenu];
} else {
_magicStackHalfSheetTableViewController =
[[MagicStackHalfSheetTableViewController alloc] init];
_magicStackHalfSheetMediator = [[MagicStackHalfSheetMediator alloc]
initWithLocalState:GetApplicationContext()->GetLocalState()
profilePrefService:ProfileIOS::FromBrowserState(
self.browser->GetProfile())
->GetPrefs()];
_magicStackHalfSheetMediator.consumer =
_magicStackHalfSheetTableViewController;
_magicStackHalfSheetTableViewController.delegate = self;
_magicStackHalfSheetTableViewController.modelDelegate =
_magicStackHalfSheetMediator;
UINavigationController* navViewController = [[UINavigationController alloc]
initWithRootViewController:_magicStackHalfSheetTableViewController];
navViewController.modalPresentationStyle = UIModalPresentationPageSheet;
UISheetPresentationController* presentationController =
navViewController.sheetPresentationController;
presentationController.prefersEdgeAttachedInCompactHeight = YES;
presentationController.widthFollowsPreferredContentSizeWhenEdgeAttached =
YES;
presentationController.detents = @[
UISheetPresentationControllerDetent.mediumDetent,
UISheetPresentationControllerDetent.largeDetent
];
[_magicStackCollectionView presentViewController:navViewController
animated:YES
completion:nil];
}
}
- (void)logEphemeralCardVisibility:(ContentSuggestionsModuleType)card {
UMA_HISTOGRAM_ENUMERATION(kMagicStackTopModuleImpressionHistogram, card);
segmentation_platform::home_modules::HomeModulesCardRegistry* registry =
segmentation_platform::SegmentationPlatformServiceFactory::
GetHomeCardRegistryForProfile(self.browser->GetProfile());
switch (card) {
case ContentSuggestionsModuleType::kPriceTrackingPromo:
registry->NotifyCardShown(
segmentation_platform::kPriceTrackingNotificationPromo);
break;
case ContentSuggestionsModuleType::kSendTabPromo:
registry->NotifyCardShown(
segmentation_platform::kSendTabNotificationPromo);
break;
case ContentSuggestionsModuleType::kTipsWithProductImage:
case ContentSuggestionsModuleType::kTips: {
CHECK(_tipsMediator);
std::optional<std::string_view> name =
OutputLabelForTipIdentifier(_tipsMediator.state.identifier);
if (name.has_value()) {
registry->NotifyCardShown(std::string(name.value()).c_str());
// Log the Tips (Magic Stack) Module that was displayed to the user.
base::UmaHistogramEnumeration(
kTipsMagicStackModuleDisplayedTypeHistogram,
_tipsMediator.state.identifier);
break;
}
[[fallthrough]];
}
default:
NOTREACHED();
}
}
#pragma mark - MagicStackModuleContainerDelegate
- (void)seeMoreWasTappedForModuleType:(ContentSuggestionsModuleType)type {
[self.customizationDelegate dismissCustomizationMenu];
switch (type) {
case ContentSuggestionsModuleType::kSafetyCheck:
[self didSelectSafetyCheckItem:SafetyCheckItemType::kDefault];
break;
case ContentSuggestionsModuleType::kCompactedSetUpList:
[self showSetUpListSeeMoreMenuExpanded:NO];
break;
case ContentSuggestionsModuleType::kParcelTracking:
[self showMagicStackParcelList];
break;
case ContentSuggestionsModuleType::kTabResumption:
[self showMagicStackRecentTabs];
break;
default:
break;
}
}
- (void)neverShowModuleType:(ContentSuggestionsModuleType)type {
switch (type) {
case ContentSuggestionsModuleType::kMostVisited:
[_mostVisitedTilesMediator disableModule];
break;
case ContentSuggestionsModuleType::kTabResumption:
[_tabResumptionMediator disableModule];
break;
case ContentSuggestionsModuleType::kSafetyCheck:
[_safetyCheckMediator disableModule];
break;
case ContentSuggestionsModuleType::kSetUpListSync:
case ContentSuggestionsModuleType::kSetUpListDefaultBrowser:
case ContentSuggestionsModuleType::kSetUpListAutofill:
case ContentSuggestionsModuleType::kSetUpListNotifications:
case ContentSuggestionsModuleType::kSetUpListDocking:
case ContentSuggestionsModuleType::kSetUpListAddressBar:
case ContentSuggestionsModuleType::kCompactedSetUpList:
[_setUpListMediator disableModule];
break;
case ContentSuggestionsModuleType::kParcelTracking: {
[self presentParcelTrackingAlertCoordinator];
break;
}
case ContentSuggestionsModuleType::kPriceTrackingPromo: {
base::RecordAction(base::UserMetricsAction(
"Commerce.PriceTracking.MagicStackPromo.Hidden"));
[_priceTrackingPromoMediator disableModule];
break;
}
case ContentSuggestionsModuleType::kSendTabPromo: {
// The Send Tab Promo has an impression limit of 1, so it's sufficient to
// just hide the module.
[_sendTabPromoMediator dismissModule];
break;
}
case ContentSuggestionsModuleType::kTipsWithProductImage:
case ContentSuggestionsModuleType::kTips: {
[_tipsMediator disableModule];
break;
}
default:
break;
}
}
// Returns the `PushNotificationClientId` associated with the specified `type`.
// Currently, push notifications are exclusively supported by the Set Up List
// and Safety Check modules.
- (PushNotificationClientId)pushNotificationClientId:
(ContentSuggestionsModuleType)type {
// This is only supported for Set Up List, Tips, Send Tab, and Safety Check
// modules.
CHECK(IsSetUpListModuleType(type) || IsTipsModuleType(type) ||
type == ContentSuggestionsModuleType::kSafetyCheck ||
type == ContentSuggestionsModuleType::kSendTabPromo);
if (type == ContentSuggestionsModuleType::kSafetyCheck) {
return PushNotificationClientId::kSafetyCheck;
}
if (IsSetUpListModuleType(type) || IsTipsModuleType(type)) {
return PushNotificationClientId::kTips;
}
if (type == ContentSuggestionsModuleType::kSendTabPromo) {
return PushNotificationClientId::kSendTab;
}
NOTREACHED();
}
// Retrieves the message ID for the push notification feature title associated
// with the specified `ContentSuggestionsModuleType`. Currently, push
// notifications are exclusively supported by the Set Up List, Send Tab, and
// Safety Check modules.
- (int)pushNotificationTitleMessageId:(ContentSuggestionsModuleType)type {
// This is only supported for Set Up List, Tips, Send Tab, and Safety Check
// modules.
CHECK(IsSetUpListModuleType(type) || IsTipsModuleType(type) ||
type == ContentSuggestionsModuleType::kSafetyCheck ||
type == ContentSuggestionsModuleType::kSendTabPromo);
if (type == ContentSuggestionsModuleType::kSafetyCheck) {
return IDS_IOS_SAFETY_CHECK_TITLE;
}
if (IsSetUpListModuleType(type)) {
return content_suggestions::SetUpListTitleStringID();
}
if (IsTipsModuleType(type)) {
return IDS_IOS_MAGIC_STACK_TIP_TITLE;
}
if (type == ContentSuggestionsModuleType::kSendTabPromo) {
return IDS_IOS_SEND_TAB_PROMO_FEATURE_NAME_FOR_SNACKBAR;
}
NOTREACHED();
}
- (NotificationOptInAccessPoint)convertModuleTypeToNotificationOptInAccessPoint:
(ContentSuggestionsModuleType)type {
switch (type) {
case ContentSuggestionsModuleType::kSafetyCheck:
return NotificationOptInAccessPoint::kSafetyCheck;
case ContentSuggestionsModuleType::kSendTabPromo:
return NotificationOptInAccessPoint::kSendTabMagicStackPromo;
case ContentSuggestionsModuleType::kSetUpListSync:
case ContentSuggestionsModuleType::kSetUpListDefaultBrowser:
case ContentSuggestionsModuleType::kSetUpListAutofill:
case ContentSuggestionsModuleType::kSetUpListNotifications:
case ContentSuggestionsModuleType::kSetUpListDocking:
case ContentSuggestionsModuleType::kSetUpListAddressBar:
case ContentSuggestionsModuleType::kCompactedSetUpList:
case ContentSuggestionsModuleType::kSetUpListAllSet:
return NotificationOptInAccessPoint::kSetUpList;
case ContentSuggestionsModuleType::kTipsWithProductImage:
case ContentSuggestionsModuleType::kTips:
return NotificationOptInAccessPoint::kTips;
default:
NOTREACHED();
}
}
- (void)enableNotifications:(ContentSuggestionsModuleType)type
viaContextMenu:(BOOL)viaContextMenu {
// This is only supported for Set Up List, Tips, Send Tab, and Safety Check
// modules.
CHECK(IsSetUpListModuleType(type) || IsTipsModuleType(type) ||
type == ContentSuggestionsModuleType::kSafetyCheck ||
type == ContentSuggestionsModuleType::kSendTabPromo);
LogSafetyCheckNotificationOptIn(viaContextMenu);
// Ask user for permission to opt-in to notifications.
[_notificationsOptInAlertCoordinator stop];
_notificationsOptInAlertCoordinator =
[[NotificationsOptInAlertCoordinator alloc]
initWithBaseViewController:self.magicStackCollectionView
browser:self.browser];
_notificationsOptInAlertCoordinator.accessPoint =
[self convertModuleTypeToNotificationOptInAccessPoint:type];
_notificationsOptInAlertCoordinator.delegate = self;
const PushNotificationClientId clientId =
[self pushNotificationClientId:type];
_notificationsOptInAlertCoordinator.clientIds = std::vector{clientId};
int featureTitle = [self pushNotificationTitleMessageId:type];
_notificationsOptInAlertCoordinator.confirmationMessage =
l10n_util::GetNSStringF(IDS_IOS_NOTIFICATIONS_CONFIRMATION_MESSAGE,
l10n_util::GetStringUTF16(featureTitle));
[_notificationsOptInAlertCoordinator start];
}
- (void)disableNotifications:(ContentSuggestionsModuleType)type
viaContextMenu:(BOOL)viaContextMenu {
// This is only supported for Set Up List, Tips, Send Tab, and Safety Check
// modules.
CHECK(IsSetUpListModuleType(type) || IsTipsModuleType(type) ||
type == ContentSuggestionsModuleType::kSafetyCheck ||
type == ContentSuggestionsModuleType::kSendTabPromo);
LogSafetyCheckNotificationOptIn(viaContextMenu);
id<SystemIdentity> identity =
self.authService->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
const PushNotificationClientId clientId =
[self pushNotificationClientId:type];
GetApplicationContext()->GetPushNotificationService()->SetPreference(
identity.gaiaID, clientId, false);
// Show confirmation snackbar.
NSString* buttonText =
l10n_util::GetNSString(IDS_IOS_NOTIFICATIONS_MANAGE_SETTINGS);
int featureTitle = [self pushNotificationTitleMessageId:type];
NSString* message =
l10n_util::GetNSStringF(IDS_IOS_NOTIFICATIONS_CONFIRMATION_MESSAGE_OFF,
l10n_util::GetStringUTF16(featureTitle));
CommandDispatcher* dispatcher = self.browser->GetCommandDispatcher();
id<SnackbarCommands> snackbarHandler =
HandlerForProtocol(dispatcher, SnackbarCommands);
__weak id<SettingsCommands> weakSettingsHandler =
HandlerForProtocol(dispatcher, SettingsCommands);
[snackbarHandler showSnackbarWithMessage:message
buttonText:buttonText
messageAction:^{
[weakSettingsHandler showNotificationsSettings];
}
completionAction:nil];
}
- (void)customizeCardsWasTapped {
[self didTapMagicStackEditButton];
}
#pragma mark - MagicStackHalfSheetTableViewControllerDelegate
- (void)dismissMagicStackHalfSheet {
[_magicStackHalfSheetMediator disconnect];
_magicStackHalfSheetMediator = nil;
[_magicStackHalfSheetTableViewController.presentingViewController
dismissViewControllerAnimated:YES
completion:nil];
_magicStackHalfSheetTableViewController = nil;
}
#pragma mark - MagicStackParcelListHalfSheetTableViewControllerDelegate
- (void)dismissParcelListHalfSheet {
[_parcelListHalfSheetTableViewController.presentingViewController
dismissViewControllerAnimated:YES
completion:nil];
_parcelListHalfSheetTableViewController = nil;
}
- (void)untrackParcel:(NSString*)parcelID carrier:(ParcelType)carrier {
[self.parcelTrackingMediator untrackParcel:parcelID];
id<SnackbarCommands> snackbarHandler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), SnackbarCommands);
__weak __typeof(self) weakSelf = self;
[snackbarHandler
showSnackbarWithMessage:
l10n_util::GetNSString(IDS_IOS_PARCEL_TRACKING_UNTRACK_SNACKBAR_TITLE)
buttonText:l10n_util::GetNSString(
IDS_IOS_SNACKBAR_ACTION_UNDO)
messageAction:^{
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[weakSelf.parcelTrackingMediator trackParcel:parcelID
carrier:carrier];
}
completionAction:nil];
}
#pragma mark - TipsPasswordsCoordinatorDelegate
- (void)tipsPasswordsCoordinatorDidFinish:
(TipsPasswordsCoordinator*)coordinator {
CHECK_EQ(coordinator, _tipsPasswordsCoordinator);
[_tipsPasswordsCoordinator stop];
_tipsPasswordsCoordinator = nil;
}
#pragma mark - SafetyCheckViewDelegate
// Called when a Safety Check item is selected by the user. Depending on the
// Safety Check item `type`, this method fires a UI command to present the
// Update Chrome page, Password Checkup, or Safety Check half sheet.
- (void)didSelectSafetyCheckItem:(SafetyCheckItemType)type {
CHECK(IsSafetyCheckMagicStackEnabled());
[self.NTPActionsDelegate safetyCheckOpened];
Browser* browser = self.browser;
[_magicStackRankingModel logMagicStackEngagementForType:
ContentSuggestionsModuleType::kSafetyCheck];
IOSChromeSafetyCheckManager* safetyCheckManager =
IOSChromeSafetyCheckManagerFactory::GetForProfile(browser->GetProfile());
id<ApplicationCommands> applicationHandler =
HandlerForProtocol(browser->GetCommandDispatcher(), ApplicationCommands);
id<SettingsCommands> settingsHandler =
HandlerForProtocol(browser->GetCommandDispatcher(), SettingsCommands);
switch (type) {
case SafetyCheckItemType::kUpdateChrome: {
const GURL& chrome_upgrade_url =
safetyCheckManager->GetChromeAppUpgradeUrl();
HandleSafetyCheckUpdateChromeTap(chrome_upgrade_url, applicationHandler);
break;
}
case SafetyCheckItemType::kPassword: {
std::vector<password_manager::CredentialUIEntry> insecure_credentials =
safetyCheckManager->GetInsecureCredentials();
password_manager::InsecurePasswordCounts insecure_password_counts =
safetyCheckManager->GetInsecurePasswordCounts();
HandleSafetyCheckPasswordTap(insecure_credentials,
insecure_password_counts, applicationHandler,
settingsHandler);
break;
}
case SafetyCheckItemType::kSafeBrowsing:
[settingsHandler showSafeBrowsingSettings];
break;
case SafetyCheckItemType::kAllSafe:
case SafetyCheckItemType::kRunning:
case SafetyCheckItemType::kDefault:
password_manager::PasswordCheckReferrer referrer =
password_manager::PasswordCheckReferrer::kSafetyCheckMagicStack;
[settingsHandler showAndStartSafetyCheckForReferrer:referrer];
break;
}
}
#pragma mark - SetUpListTapDelegate
- (void)didTapSetUpListItemView:(SetUpListItemView*)view {
[self didSelectSetUpListItem:view.type];
}
- (void)didSelectSetUpListItem:(SetUpListItemType)type {
if (set_up_list_utils::ShouldShowCompactedSetUpListModule()) {
[_magicStackRankingModel
logMagicStackEngagementForType:ContentSuggestionsModuleType::
kCompactedSetUpList];
} else {
[_magicStackRankingModel
logMagicStackEngagementForType:SetUpListModuleTypeForSetUpListType(
type)];
}
[self.contentSuggestionsMetricsRecorder recordSetUpListItemSelected:type];
[self.NTPActionsDelegate setUpListItemOpened];
PrefService* localState = GetApplicationContext()->GetLocalState();
set_up_list_prefs::RecordInteraction(localState);
__weak ContentSuggestionsCoordinator* weakSelf = self;
ProceduralBlock completionBlock = ^{
[weakSelf showUIForSelectedSetUpListItem:type];
};
if (_setUpListShowMoreViewController) {
[_setUpListShowMoreViewController.presentingViewController
dismissViewControllerAnimated:YES
completion:completionBlock];
_setUpListShowMoreViewController = nil;
} else {
completionBlock();
}
}
- (void)dismissSeeMoreViewController {
DCHECK(_setUpListShowMoreViewController);
[_setUpListShowMoreViewController.presentingViewController
dismissViewControllerAnimated:YES
completion:nil];
_setUpListShowMoreViewController = nil;
}
#pragma mark - SetUpList Helpers
// Displays the UI for the given SetUpListItemType.
- (void)showUIForSelectedSetUpListItem:(SetUpListItemType)type {
switch (type) {
case SetUpListItemType::kSignInSync:
[self showSignIn];
break;
case SetUpListItemType::kDefaultBrowser:
[self showDefaultBrowserPromo];
break;
case SetUpListItemType::kAutofill:
[self showCredentialProviderPromo];
break;
case SetUpListItemType::kNotifications:
if (IsIOSTipsNotificationsEnabled()) {
[self showNotificationsOptInView:NotificationOptInAccessPoint::
kSetUpList];
} else {
[self showContentNotificationBottomSheet];
}
break;
case SetUpListItemType::kDocking:
[self showDockingPromo];
break;
case SetUpListItemType::kAddressBar:
[self showAddressBarPromo];
break;
case SetUpListItemType::kFollow:
case SetUpListItemType::kAllSet:
// TODO(crbug.com/40262090): Add a Follow item to the Set Up List.
NOTREACHED();
}
}
// Shows the Default Browser Promo.
- (void)showDefaultBrowserPromo {
// Stop the coordinator if it is already running. If the user swipes to
// dismiss a previous instance and then clicks the item again the
// previous instance may not have been stopped yet due to the animation.
[_defaultBrowserPromoCoordinator stop];
_defaultBrowserPromoCoordinator =
[[SetUpListDefaultBrowserPromoCoordinator alloc]
initWithBaseViewController:self.magicStackCollectionView
browser:self.browser
application:[UIApplication sharedApplication]
segmentationService:_segmentationService
deviceSwitcherResultDispatcher:_deviceSwitcherResultDispatcher];
_defaultBrowserPromoCoordinator.delegate = self;
[_defaultBrowserPromoCoordinator start];
}
// Shows the SigninSync UI with the SetUpList access point.
- (void)showSignIn {
SigninCoordinatorCompletionCallback completion =
^(SigninCoordinatorResult result, id<SystemIdentity> completionIdentity) {
if (result == SigninCoordinatorResultSuccess ||
result == SigninCoordinatorResultCanceledByUser) {
PrefService* localState = GetApplicationContext()->GetLocalState();
set_up_list_prefs::MarkItemComplete(localState,
SetUpListItemType::kSignInSync);
}
};
// If there are 0 identities, kInstantSignin requires less taps.
AuthenticationOperation operation =
[self hasIdentitiesOnDevice] ? AuthenticationOperation::kSigninOnly
: AuthenticationOperation::kInstantSignin;
ShowSigninCommand* command = [[ShowSigninCommand alloc]
initWithOperation:operation
identity:nil
accessPoint:signin_metrics::AccessPoint::kSetUpList
promoAction:signin_metrics::PromoAction::
PROMO_ACTION_NO_SIGNIN_PROMO
completion:completion];
[HandlerForProtocol(self.browser->GetCommandDispatcher(), ApplicationCommands)
showSignin:command
baseViewController:self.magicStackCollectionView];
}
// Shows the Credential Provider Promo using the SetUpList trigger.
- (void)showCredentialProviderPromo {
[HandlerForProtocol(self.browser->GetCommandDispatcher(),
CredentialProviderPromoCommands)
showCredentialProviderPromoWithTrigger:CredentialProviderPromoTrigger::
SetUpList];
}
- (void)showContentNotificationBottomSheet {
// Stop the coordinator if it is already running. If the user swipes to
// dismiss a previous instance and then clicks the item again the
// previous instance may not have been stopped yet due to the animation.
[_contentNotificationCoordinator stop];
_contentNotificationCoordinator =
[[SetUpListContentNotificationPromoCoordinator alloc]
initWithBaseViewController:self.magicStackCollectionView
browser:self.browser
application:[UIApplication sharedApplication]];
_contentNotificationCoordinator.delegate = self;
[_contentNotificationCoordinator start];
}
- (void)showNotificationsOptInView:(NotificationOptInAccessPoint)accessPoint {
[_notificationsOptInCoordinator stop];
_notificationsOptInCoordinator = [[NotificationsOptInCoordinator alloc]
initWithBaseViewController:self.magicStackCollectionView
browser:self.browser];
_notificationsOptInCoordinator.accessPoint = accessPoint;
_notificationsOptInCoordinator.delegate = self;
[_notificationsOptInCoordinator start];
}
// Shows the Docking promo.
- (void)showDockingPromo {
[HandlerForProtocol(self.browser->GetCommandDispatcher(),
DockingPromoCommands)
showDockingPromoWithTrigger:DockingPromoTrigger::kSetUpList];
}
// Shows the Address Bar promo.
- (void)showAddressBarPromo {
[HandlerForProtocol(self.browser->GetCommandDispatcher(),
BrowserCoordinatorCommands) showOmniboxPositionChoice];
}
#pragma mark - NotificationsOptInAlertCoordinatorDelegate
- (void)notificationsOptInAlertCoordinator:
(NotificationsOptInAlertCoordinator*)alertCoordinator
result:
(NotificationsOptInAlertResult)result {
CHECK_EQ(_notificationsOptInAlertCoordinator, alertCoordinator);
std::vector<PushNotificationClientId> clientIds =
alertCoordinator.clientIds.value();
if (result != NotificationsOptInAlertResult::kOpenedSettings) {
[_notificationsOptInAlertCoordinator stop];
_notificationsOptInAlertCoordinator = nil;
if (std::find(clientIds.begin(), clientIds.end(),
PushNotificationClientId::kSendTab) != clientIds.end()) {
[_sendTabPromoMediator dismissModule];
}
}
}
- (void)notificationsOptInAlertCoordinatorReturnedFromSettings:
(NotificationsOptInAlertCoordinator*)alertCoordinator {
CHECK_EQ(_notificationsOptInAlertCoordinator, alertCoordinator);
std::vector<PushNotificationClientId> clientIds =
alertCoordinator.clientIds.value();
[_notificationsOptInAlertCoordinator stop];
_notificationsOptInAlertCoordinator = nil;
[PushNotificationUtil getPermissionSettings:^(
UNNotificationSettings* settings) {
if (settings.authorizationStatus == UNAuthorizationStatusAuthorized) {
for (PushNotificationClientId clientId : clientIds) {
[self enableNotifications:[self contentSuggestionsModuleType:clientId]
viaContextMenu:NO];
}
}
}];
}
#pragma mark - NotificationsOptInCoordinatorDelegate
- (void)notificationsOptInScreenDidFinish:
(NotificationsOptInCoordinator*)coordinator {
CHECK_EQ(coordinator, _notificationsOptInCoordinator);
[_notificationsOptInCoordinator stop];
_notificationsOptInCoordinator = nil;
}
#pragma mark - PriceTrackingPromoActionDelegate
// TODO(crbug.com/378554727): Integrate Price Tracking with
// NotificationsOptInAlertCoordinatorDelegate.
- (void)showPriceTrackingPromoAlertCoordinator {
__weak ContentSuggestionsCoordinator* weakSelf = self;
_priceTrackingPromoAlertCoordinator = [[AlertCoordinator alloc]
initWithBaseViewController:self.magicStackCollectionView
browser:self.browser
title:
l10n_util::GetNSString(
IDS_IOS_CONTENT_SUGGESTIONS_PRICE_TRACKING_PROMO_SETTINGS_TURN_ON_NOTIFICATIONS_TITLE)
message:
l10n_util::GetNSString(
IDS_IOS_CONTENT_SUGGESTIONS_PRICE_TRACKING_PROMO_SETTINGS_TURN_ON_NOTIFICATIONS_TEXT)];
[_priceTrackingPromoAlertCoordinator
addItemWithTitle:
l10n_util::GetNSString(
IDS_IOS_CONTENT_SUGGESTIONS_PRICE_TRACKING_PROMO_SETTINGS_TURN_ON_NOTIFICATIONS_ACCEPT)
action:^{
base::RecordAction(base::UserMetricsAction(
"Commerce.PriceTracking.MagicStackPromo.Reenable.Allow"));
NSString* settingURL = UIApplicationOpenSettingsURLString;
if (@available(iOS 15.4, *)) {
settingURL = UIApplicationOpenNotificationSettingsURLString;
}
[[UIApplication sharedApplication]
openURL:[NSURL URLWithString:settingURL]
options:{}
completionHandler:^(BOOL res) {
[NSNotificationCenter.defaultCenter
addObserver:weakSelf
selector:@selector(onReturnFromSettings:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}];
[weakSelf dismissAlertCoordinator];
}
style:UIAlertActionStyleDefault];
[_priceTrackingPromoAlertCoordinator
addItemWithTitle:
l10n_util::GetNSString(
IDS_IOS_CONTENT_SUGGESTIONS_PRICE_TRACKING_PROMO_SETTINGS_TURN_ON_NOTIFICATIONS_DENY)
action:^{
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
base::RecordAction(base::UserMetricsAction(
"Commerce.PriceTracking.MagicStackPromo.Reenable.Deny"));
[strongSelf dismissAlertCoordinator];
[strongSelf->_priceTrackingPromoMediator
removePriceTrackingPromo];
}
style:UIAlertActionStyleCancel];
[_priceTrackingPromoAlertCoordinator start];
}
// TODO(crbug.com/378554727): Integrate Price Tracking with
// NotificationsOptInAlertCoordinatorDelegate.
- (void)onReturnFromSettings:(NSNotification*)notification {
[PushNotificationUtil
getPermissionSettings:^(UNNotificationSettings* settings) {
if (settings.authorizationStatus == UNAuthorizationStatusAuthorized) {
[self->_priceTrackingPromoMediator
enablePriceTrackingSettingsAndShowSnackbar];
}
[self->_priceTrackingPromoMediator removePriceTrackingPromo];
}];
}
- (void)dismissAlertCoordinator {
[_priceTrackingPromoAlertCoordinator stop];
_priceTrackingPromoAlertCoordinator = nil;
}
#pragma mark - SetUpListDefaultBrowserPromoCoordinatorDelegate
- (void)setUpListDefaultBrowserPromoDidFinish:(BOOL)success {
[_defaultBrowserPromoCoordinator stop];
_defaultBrowserPromoCoordinator = nil;
}
#pragma mark - SetUpListContentNotificationPromoCoordinatorDelegate
- (void)setUpListContentNotificationPromoDidFinish {
[_contentNotificationCoordinator stop];
_contentNotificationCoordinator = nil;
}
#pragma mark - Helpers
- (bool)hasIdentitiesOnDevice {
ProfileIOS* profile = self.browser->GetProfile();
if (IsUseAccountListFromIdentityManagerEnabled()) {
return !IdentityManagerFactory::GetForProfile(profile)
->GetAccountsOnDevice()
.empty();
} else {
return ChromeAccountManagerServiceFactory::GetForProfile(profile)
->HasIdentities();
}
}
- (void)showMagicStackParcelList {
_parcelListHalfSheetTableViewController =
[[MagicStackParcelListHalfSheetTableViewController alloc]
initWithParcels:[self.parcelTrackingMediator allParcelTrackingItems]];
_parcelListHalfSheetTableViewController.delegate = self;
UINavigationController* navViewController = [[UINavigationController alloc]
initWithRootViewController:_parcelListHalfSheetTableViewController];
navViewController.modalPresentationStyle = UIModalPresentationPageSheet;
UISheetPresentationController* presentationController =
navViewController.sheetPresentationController;
presentationController.prefersEdgeAttachedInCompactHeight = YES;
presentationController.widthFollowsPreferredContentSizeWhenEdgeAttached = YES;
presentationController.detents = @[
UISheetPresentationControllerDetent.mediumDetent,
UISheetPresentationControllerDetent.largeDetent
];
[_magicStackCollectionView presentViewController:navViewController
animated:YES
completion:nil];
}
- (void)showMagicStackRecentTabs {
CommandDispatcher* dispatcher = self.browser->GetCommandDispatcher();
id<BrowserCoordinatorCommands> browserCoordinatorCommands =
HandlerForProtocol(dispatcher, BrowserCoordinatorCommands);
[browserCoordinatorCommands showRecentTabs];
}
// Presents the parcel tracking alert modal.
- (void)presentParcelTrackingAlertCoordinator {
_parcelTrackingAlertCoordinator = [[AlertCoordinator alloc]
initWithBaseViewController:self.magicStackCollectionView
browser:self.browser
title:
l10n_util::GetNSString(
IDS_IOS_PARCEL_TRACKING_MODULE_HIDE_ALERT_TITLE)
message:
l10n_util::GetNSStringF(
IDS_IOS_PARCEL_TRACKING_MODULE_HIDE_ALERT_DESCRIPTION,
base::SysNSStringToUTF16(l10n_util::GetNSString(
IDS_IOS_CONTENT_SUGGESTIONS_PARCEL_TRACKING_MODULE_TITLE)))];
__weak ContentSuggestionsCoordinator* weakSelf = self;
[_parcelTrackingAlertCoordinator
addItemWithTitle:
l10n_util::GetNSStringF(
IDS_IOS_PARCEL_TRACKING_CONTEXT_MENU_DESCRIPTION,
base::SysNSStringToUTF16(l10n_util::GetNSString(
IDS_IOS_CONTENT_SUGGESTIONS_PARCEL_TRACKING_MODULE_TITLE)))
action:^{
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[weakSelf.parcelTrackingMediator disableModule];
[weakSelf dismissParcelTrackingAlertCoordinator];
}
style:UIAlertActionStyleDefault];
[_parcelTrackingAlertCoordinator
addItemWithTitle:l10n_util::GetNSString(
IDS_IOS_PARCEL_TRACKING_MODULE_HIDE_ALERT_CANCEL)
action:^{
[weakSelf dismissParcelTrackingAlertCoordinator];
}
style:UIAlertActionStyleCancel];
[_parcelTrackingAlertCoordinator start];
}
// Display the notification settings.
- (void)showNotificationSettings {
[self.contentSuggestionsMetricsRecorder
recordContentNotificationSnackbarEvent:ContentNotificationSnackbarEvent::
kActionButtonTapped];
id<SettingsCommands> settingsHandler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), SettingsCommands);
[settingsHandler showNotificationsSettings];
}
// Dismisses the parcel tracking alert modal.
- (void)dismissParcelTrackingAlertCoordinator {
[_parcelTrackingAlertCoordinator stop];
_parcelTrackingAlertCoordinator = nil;
}
// Returns the ContentSuggestionsModuleType associated with `clientId`.
- (ContentSuggestionsModuleType)contentSuggestionsModuleType:
(PushNotificationClientId)clientId {
switch (clientId) {
case PushNotificationClientId::kCommerce:
return ContentSuggestionsModuleType::kPriceTrackingPromo;
case PushNotificationClientId::kTips:
return ContentSuggestionsModuleType::kTips;
case PushNotificationClientId::kSafetyCheck:
return ContentSuggestionsModuleType::kSafetyCheck;
case PushNotificationClientId::kSendTab:
return ContentSuggestionsModuleType::kSendTabPromo;
case PushNotificationClientId::kContent:
case PushNotificationClientId::kSports:
case PushNotificationClientId::kReminders:
NOTREACHED();
}
}
@end