// Copyright 2022 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/follow/model/follow_util.h"
#import <UIKit/UIKit.h>
#import "components/prefs/pref_service.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_util.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.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/public/features/features.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/web/public/web_client.h"
#import "ios/web/public/web_state.h"
#import "url/gurl.h"
namespace {
// Set the Follow IPH apperance threshold to 15 minutes.
NSTimeInterval const kFollowIPHAppearanceThresholdInSeconds = 15 * 60;
// Set the Follow IPH apperance threshold for site to 1 day.
NSTimeInterval const kFollowIPHAppearanceThresholdForSiteInSeconds =
24 * 60 * 60;
} // namespace
NSString* const kFollowIPHPreviousDisplayEvents =
@"FollowIPHPreviousDisplayEvents";
NSString* const kFollowIPHHost = @"host";
NSString* const kFollowIPHDate = @"date";
FollowActionState GetFollowActionState(web::WebState* webState) {
// TODO(crbug.com/40251372): Hide Follow action in safe mode.
if (!webState || !IsWebChannelsEnabled() || IsFeedAblationEnabled()) {
return FollowActionStateHidden;
}
ChromeBrowserState* browserState =
ChromeBrowserState::FromBrowserState(webState->GetBrowserState());
// Don't show follow option when feed is hidden due to DSE choice.
if (ShouldHideFeedWithSearchChoice(
ios::TemplateURLServiceFactory::GetForBrowserState(browserState))) {
return FollowActionStateHidden;
}
// Don't show follow option when following feed is disabled by enterprise
// policy.
if (!browserState->GetPrefs()->GetBoolean(
prefs::kNTPContentSuggestionsEnabled)) {
return FollowActionStateHidden;
}
// Don't show follow option if the user is not signed in.
AuthenticationService* authenticationService =
AuthenticationServiceFactory::GetForBrowserState(browserState);
// Hide the follow action when users are in incognito mode or when users have
// not signed in.
if (browserState->IsOffTheRecord() || !authenticationService ||
!authenticationService->GetPrimaryIdentity(
signin::ConsentLevel::kSignin)) {
return FollowActionStateHidden;
}
const GURL& URL = webState->GetLastCommittedURL();
// Show the follow action when:
// 1. The page url is valid;
// 2. Users are not on NTP or Chrome internal pages;
if (URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL)) {
DCHECK(!browserState->IsOffTheRecord());
return FollowActionStateEnabled;
}
return FollowActionStateHidden;
}
#pragma mark - For Follow IPH
bool IsFollowIPHShownFrequencyEligible(NSString* host) {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSArray<NSDictionary*>* followIPHPreviousDisplayEvents =
[defaults objectForKey:kFollowIPHPreviousDisplayEvents];
NSDate* lastIPHDate;
for (NSDictionary* event in followIPHPreviousDisplayEvents) {
if ([[event objectForKey:kFollowIPHHost] isEqualToString:host]) {
lastIPHDate = [event objectForKey:kFollowIPHDate];
break;
}
}
// Return false if its too soon to show another IPH.
if (lastIPHDate &&
[[NSDate
dateWithTimeIntervalSinceNow:-kFollowIPHAppearanceThresholdInSeconds]
compare:lastIPHDate] == NSOrderedAscending) {
return false;
}
// Return true if it is long enough to show another IPH for this specific
// site.
if (!lastIPHDate ||
[[NSDate dateWithTimeIntervalSinceNow:
-kFollowIPHAppearanceThresholdForSiteInSeconds]
compare:lastIPHDate] != NSOrderedAscending) {
return true;
}
return false;
}
void StoreFollowIPHDisplayEvent(NSString* host) {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSArray<NSDictionary*>* followIPHPreviousDisplayEvents =
[defaults objectForKey:kFollowIPHPreviousDisplayEvents];
NSMutableArray<NSDictionary*>* updatedDisplayEvents =
[[NSMutableArray alloc] init];
// Add object to the the recentEvents while cleaning up dictionary that has a
// date older than 1 day ago. Since the follow iph will show in a specific
// regularity, this clean up logic will not execute often
NSDate* uselessDate =
[NSDate dateWithTimeIntervalSinceNow:
-kFollowIPHAppearanceThresholdForSiteInSeconds];
for (NSDictionary* event in followIPHPreviousDisplayEvents) {
if ([[event objectForKey:kFollowIPHDate] compare:uselessDate] !=
NSOrderedAscending) {
[updatedDisplayEvents addObject:event];
}
}
// Add the last follow IPH event.
NSDictionary* IPHPresentingEvent =
@{kFollowIPHHost : host, kFollowIPHDate : [NSDate date]};
[updatedDisplayEvents addObject:IPHPresentingEvent];
[defaults setObject:updatedDisplayEvents
forKey:kFollowIPHPreviousDisplayEvents];
[defaults synchronize];
}
void RemoveLastFollowIPHDisplayEvent() {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSArray<NSDictionary*>* followIPHPreviousDisplayEvents =
[defaults objectForKey:kFollowIPHPreviousDisplayEvents];
DCHECK(followIPHPreviousDisplayEvents);
NSMutableArray<NSDictionary*>* updatedDisplayEvents =
[followIPHPreviousDisplayEvents mutableCopy];
[updatedDisplayEvents removeLastObject];
[defaults setObject:updatedDisplayEvents
forKey:kFollowIPHPreviousDisplayEvents];
[defaults synchronize];
}