// Copyright 2024 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/omnibox/popup/row/omnibox_popup_row_content_configuration.h"
#import "base/check.h"
#import "base/ios/ios_util.h"
#import "base/strings/sys_string_conversions.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/util/layout_guide_names.h"
#import "ios/chrome/browser/shared/ui/util/rtl_geometry.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/shared/ui/util/util_swift.h"
#import "ios/chrome/browser/ui/omnibox/omnibox_ui_features.h"
#import "ios/chrome/browser/ui/omnibox/popup/autocomplete_suggestion.h"
#import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_accessibility_identifier_constants.h"
#import "ios/chrome/browser/ui/omnibox/popup/row/omnibox_popup_row_content_view.h"
#import "ios/chrome/browser/ui/omnibox/popup/row/omnibox_popup_row_delegate.h"
#import "ios/chrome/browser/ui/omnibox/popup/row/omnibox_popup_row_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/util/pointer_interaction_util.h"
#import "ios/chrome/common/ui/util/ui_util.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/device_form_factor.h"
#import "ui/base/l10n/l10n_util.h"
namespace {
/// Size of the trailing button.
const CGFloat kTrailingButtonPointSize = 17.0f;
/// Maximum number of lines displayed for search suggestions.
const NSInteger kWrappingSuggestNumberOfLines = 2;
} // namespace
NSString* const OmniboxPopupRowCellReuseIdentifier = @"OmniboxPopupRowCell";
const CGFloat kOmniboxPopupCellMinimumHeight = 58;
/// Redefines "Content View interface" as readwrite.
@interface OmniboxPopupRowContentConfiguration ()
// Background.
@property(nonatomic, assign, readwrite) BOOL isBackgroundHighlighted;
// Leading Icon.
@property(nonatomic, strong, readwrite) id<OmniboxIcon> leadingIcon;
@property(nonatomic, assign, readwrite) BOOL leadingIconHighlighted;
// Primary text.
@property(nonatomic, strong, readwrite) NSAttributedString* primaryText;
@property(nonatomic, assign, readwrite) NSInteger primaryTextNumberOfLines;
// Secondary Text.
@property(nonatomic, strong, readwrite) NSAttributedString* secondaryText;
@property(nonatomic, assign, readwrite) NSInteger secondaryTextNumberOfLines;
@property(nonatomic, assign, readwrite) BOOL secondaryTextFading;
@property(nonatomic, assign, readwrite) BOOL secondaryTextDisplayAsURL;
// Trailing Icon.
@property(nonatomic, strong, readwrite) UIImage* trailingIcon;
@property(nonatomic, strong, readwrite) UIColor* trailingIconTintColor;
@property(nonatomic, strong, readwrite)
NSString* trailingButtonAccessibilityIdentifier;
// Margins.
@property(nonatomic, assign, readwrite)
NSDirectionalEdgeInsets directionalLayoutMargin;
@property(nonatomic, assign, readwrite) BOOL isPopoutOmnibox;
@end
@implementation OmniboxPopupRowContentConfiguration
/// Layout this cell with the given data before displaying.
+ (instancetype)cellConfiguration {
return [[OmniboxPopupRowContentConfiguration alloc] init];
}
+ (NSAttributedString*)highlightedAttributedStringWithString:
(NSAttributedString*)string {
if (!string.length) {
return nil;
}
NSMutableAttributedString* mutableString =
[[NSMutableAttributedString alloc] initWithAttributedString:string];
[mutableString addAttribute:NSForegroundColorAttributeName
value:[UIColor whiteColor]
range:NSMakeRange(0, string.length)];
return mutableString;
}
- (void)setSuggestion:(id<AutocompleteSuggestion>)suggestion {
_suggestion = suggestion;
// Leading Icon.
_leadingIcon = _suggestion.icon;
// Primary Text.
_primaryText = _suggestion.text;
if (_suggestion.isWrapping) {
_primaryTextNumberOfLines = kWrappingSuggestNumberOfLines;
} else {
_primaryTextNumberOfLines = 1;
}
// Secondary Text.
_secondaryText = _suggestion.detailText;
_secondaryTextNumberOfLines =
_suggestion.hasAnswer ? _suggestion.numberOfLines : 1;
_secondaryTextFading = !_suggestion.hasAnswer;
_secondaryTextDisplayAsURL = _suggestion.isURL;
// Trailing Button.
NSString* trailingButtonActionName = nil;
if (_suggestion.isTabMatch) {
_trailingIcon = DefaultSymbolWithPointSize(kNavigateToTabSymbol,
kTrailingButtonPointSize);
_trailingButtonAccessibilityIdentifier =
kOmniboxPopupRowSwitchTabAccessibilityIdentifier;
trailingButtonActionName =
l10n_util::GetNSString(IDS_IOS_OMNIBOX_POPUP_SWITCH_TO_OPEN_TAB);
} else if (_suggestion.isAppendable) {
_trailingIcon = DefaultSymbolWithPointSize(kRefineQuerySymbol,
kTrailingButtonPointSize);
_trailingButtonAccessibilityIdentifier =
kOmniboxPopupRowAppendAccessibilityIdentifier;
trailingButtonActionName =
l10n_util::GetNSString(IDS_IOS_OMNIBOX_POPUP_APPEND);
}
if (_trailingIcon) {
// `imageWithHorizontallyFlippedOrientation` is flipping the icon
// automatically when the UI is RTL/LTR.
_trailingIcon = [_trailingIcon imageWithHorizontallyFlippedOrientation];
_trailingIcon = [_trailingIcon
imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
// Accessibility actions.
NSMutableArray<UIAccessibilityCustomAction*>* customActions =
[[NSMutableArray alloc] init];
if (trailingButtonActionName) {
[customActions addObject:[[UIAccessibilityCustomAction alloc]
initWithName:trailingButtonActionName
target:self
selector:@selector(trailingButtonTapped)]];
}
self.accessibilityCustomActions = customActions;
}
#pragma mark - UIContentConfiguration
- (id)copyWithZone:(NSZone*)zone {
__typeof__(self) configuration = [[self.class alloc] init];
configuration.suggestion = self.suggestion;
configuration.delegate = self.delegate;
configuration.indexPath = self.indexPath;
configuration.showSeparator = self.showSeparator;
configuration.semanticContentAttribute = self.semanticContentAttribute;
configuration.faviconRetriever = self.faviconRetriever;
configuration.imageRetriever = self.imageRetriever;
// Setting `suggestion` already sets some properties in "Content View
// interface". Update the properties that can change with
// `updatedConfigurationForState`.
configuration.isBackgroundHighlighted = self.isBackgroundHighlighted;
configuration.leadingIconHighlighted = self.leadingIconHighlighted;
configuration.primaryText = self.primaryText;
configuration.secondaryText = self.secondaryText;
configuration.trailingIconTintColor = self.trailingIconTintColor;
configuration.directionalLayoutMargin = self.directionalLayoutMargin;
configuration.isPopoutOmnibox = self.isPopoutOmnibox;
return configuration;
}
- (UIView<UIContentView>*)makeContentView {
return [[OmniboxPopupRowContentView alloc] initWithConfiguration:self];
}
/// Updates the configuration for different state of the view. This contains
/// everything that can change in the configuration's lifetime.
- (instancetype)updatedConfigurationForState:(id<UIConfigurationState>)state {
if (![state isKindOfClass:[UIViewConfigurationState class]]) {
return self;
}
OmniboxPopupRowContentConfiguration* configuration = [self copy];
UIViewConfigurationState* viewState = state;
// Highlight.
const BOOL allowHighlight = viewState.highlighted || viewState.selected;
configuration.isBackgroundHighlighted = allowHighlight;
configuration.leadingIconHighlighted = allowHighlight;
configuration.primaryText =
allowHighlight
? [self.class highlightedAttributedStringWithString:_suggestion.text]
: _suggestion.text;
configuration.secondaryText =
allowHighlight
? [self.class highlightedAttributedStringWithString:_suggestion
.detailText]
: _suggestion.detailText;
configuration.trailingIconTintColor =
allowHighlight ? UIColor.whiteColor : [UIColor colorNamed:kBlueColor];
// Update margins for popout omnibox. Popout omnibox is only available on
// regular size class.
configuration.isPopoutOmnibox =
ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET &&
IsRegularXRegularSizeClass(state.traitCollection);
return configuration;
}
#pragma mark - Private
/// Handles tap on the trailing button.
- (void)trailingButtonTapped {
CHECK(_indexPath);
[self.delegate omniboxPopupRowWithConfiguration:self
didTapTrailingButtonAtIndexPath:_indexPath];
}
@end