Commit f2420b1e authored by Nazerke's avatar Nazerke Committed by Commit Bot

[ios] Favicons in the TabStrip Cells.

This CL updates the favicon image in the tabstrip cell by pushing the
data from Mediator to the UI via tabFaviconDataSource by the model
items for the cell - GridItems (re-used from TabGrid).

Bug: 1136834,1128249
Change-Id: I5008df41819b7770e05d933145d26e7f40d8369b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2513213
Commit-Queue: Nazerke Kalidolda <nazerke@google.com>
Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Cr-Commit-Position: refs/heads/master@{#826760}
parent e784fea1
......@@ -22,6 +22,7 @@ source_set("tab_strip") {
source_set("tab_strip_ui") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"tab_favicon_data_source.h",
"tab_strip_cell.h",
"tab_strip_cell.mm",
"tab_strip_consumer.h",
......@@ -33,8 +34,16 @@ source_set("tab_strip_ui") {
"tab_strip_view_layout.mm",
]
deps = [
"//components/favicon/ios",
"//ios/chrome/browser",
"//ios/chrome/browser/browser_state",
"//ios/chrome/browser/tabs",
"//ios/chrome/browser/ui/tab_switcher/tab_grid/grid:grid_ui",
"//ios/chrome/browser/web:tab_id_tab_helper",
"//ios/chrome/browser/web_state_list",
"//ios/chrome/common/ui/colors",
"//ios/web/public",
"//ui/gfx",
]
frameworks = [ "UIKit.framework" ]
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_STRIP_TAB_FAVICON_DATA_SOURCE_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_STRIP_TAB_FAVICON_DATA_SOURCE_H_
#import <UIKit/UIKit.h>
// Protocol that the tabstrip UI uses to asynchronously pull favicons for cells
// in the tabstrip.
@protocol TabFaviconDataSource
// Requests the receiver to provide a favicon image corresponding to
// |identifier|. |completion| is called with the image if it exists.
- (void)faviconForIdentifier:(NSString*)identifier
completion:(void (^)(UIImage*))completion;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_STRIP_TAB_FAVICON_DATA_SOURCE_H_
......@@ -12,6 +12,11 @@
@interface TabStripCell : UICollectionViewCell
@property(nonatomic, strong) UILabel* titleLabel;
// View for displaying the favicon.
@property(nonatomic, strong) UIImageView* faviconView;
// Unique identifier for the cell's contents. This is used to ensure that
// updates in an asynchronous callback are only made if the item is the same.
@property(nonatomic, copy) NSString* itemIdentifier;
@end
......
......@@ -79,6 +79,7 @@ const CGFloat kTitleInset = 10.0;
- (void)prepareForReuse {
[super prepareForReuse];
self.titleLabel.text = nil;
self.itemIdentifier = nil;
}
@end
......@@ -7,11 +7,16 @@
#import <Foundation/Foundation.h>
@class GridItem;
// TabStripConsumer sets the current appearance of the TabStrip.
@protocol TabStripConsumer
// Sets the number to tabs currently opened.
- (void)setTabsCount:(NSUInteger)tabsCount;
// Tells the consumer to replace its current set of items with |items| and
// update the selected item ID to be |selectedItemID|. It's an error to pass
// an |items| array containing items without unique IDs.
- (void)populateItems:(NSArray<GridItem*>*)items
selectedItemID:(NSString*)selectedItemID;
@end
......
......@@ -50,6 +50,8 @@
self.mediator =
[[TabStripMediator alloc] initWithConsumer:self.tabStripViewController];
self.mediator.webStateList = self.browser->GetWebStateList();
self.tabStripViewController.faviconDataSource = self.mediator;
}
- (void)stop {
......
......@@ -7,11 +7,13 @@
#import <Foundation/Foundation.h>
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/tab_favicon_data_source.h"
@protocol TabStripConsumer;
class WebStateList;
// This mediator used to manage model interaction for its consumer.
@interface TabStripMediator : NSObject
@interface TabStripMediator : NSObject <TabFaviconDataSource>
// The WebStateList that this mediator listens for any changes on the total
// number of Webstates.
......
......@@ -4,14 +4,72 @@
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_mediator.h"
#import "components/favicon/ios/web_favicon_driver.h"
#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
#import "ios/chrome/browser/chrome_url_util.h"
#import "ios/chrome/browser/tabs/tab_title_util.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_item.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_consumer.h"
#import "ios/chrome/browser/web/tab_id_tab_helper.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
#import "ios/web/public/web_state.h"
#import "ui/gfx/image/image.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Constructs a GridItem from a |web_state|.
GridItem* CreateItem(web::WebState* web_state) {
TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
GridItem* item = [[GridItem alloc] initWithIdentifier:tab_helper->tab_id()];
// chrome://newtab (NTP) tabs have no title.
if (IsURLNtp(web_state->GetVisibleURL())) {
item.hidesTitle = YES;
}
item.title = tab_util::GetTabTitle(web_state);
return item;
}
// Constructs an array of GridItems from a |web_state_list|.
NSArray* CreateItems(WebStateList* web_state_list) {
NSMutableArray* items = [[NSMutableArray alloc] init];
for (int i = 0; i < web_state_list->count(); i++) {
web::WebState* web_state = web_state_list->GetWebStateAt(i);
[items addObject:CreateItem(web_state)];
}
return [items copy];
}
// Returns the ID of the active tab in |web_state_list|.
NSString* GetActiveTabId(WebStateList* web_state_list) {
if (!web_state_list)
return nil;
web::WebState* web_state = web_state_list->GetActiveWebState();
if (!web_state)
return nil;
TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
return tab_helper->tab_id();
}
// Returns the WebState with |identifier| in |web_state_list|. Returns |nullptr|
// if not found.
web::WebState* GetWebStateWithId(WebStateList* web_state_list,
NSString* identifier) {
for (int i = 0; i < web_state_list->count(); i++) {
web::WebState* web_state = web_state_list->GetWebStateAt(i);
TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
if ([identifier isEqualToString:tab_helper->tab_id()])
return web_state;
}
return nullptr;
}
} // namespace
@interface TabStripMediator () <WebStateListObserving> {
// Bridge C++ WebStateListObserver methods to this TabStripController.
std::unique_ptr<WebStateListObserverBridge> _webStateListObserver;
......@@ -50,10 +108,10 @@
if (_webStateList) {
DCHECK_GE(_webStateList->count(), 0);
[_consumer setTabsCount:static_cast<NSUInteger>(_webStateList->count())];
_webStateListObserver = std::make_unique<WebStateListObserverBridge>(self);
_webStateList->AddObserver(_webStateListObserver.get());
}
[self populateConsumerItems];
}
#pragma mark - WebStateListObserving
......@@ -61,14 +119,53 @@
- (void)webStateList:(WebStateList*)webStateList
didDetachWebState:(web::WebState*)webState
atIndex:(int)atIndex {
[_consumer setTabsCount:static_cast<NSUInteger>(_webStateList->count())];
[self populateConsumerItems];
}
- (void)webStateList:(WebStateList*)webStateList
didInsertWebState:(web::WebState*)webState
atIndex:(int)index
activating:(BOOL)activating {
[_consumer setTabsCount:static_cast<NSUInteger>(_webStateList->count())];
[self populateConsumerItems];
}
#pragma mark - TabFaviconDataSource
- (void)faviconForIdentifier:(NSString*)identifier
completion:(void (^)(UIImage*))completion {
web::WebState* webState = GetWebStateWithId(_webStateList, identifier);
if (!webState) {
return;
}
// NTP tabs get no favicon.
if (IsURLNtp(webState->GetVisibleURL())) {
return;
}
UIImage* defaultFavicon =
webState->GetBrowserState()->IsOffTheRecord()
? [UIImage imageNamed:@"default_world_favicon_incognito"]
: [UIImage imageNamed:@"default_world_favicon_regular"];
completion(defaultFavicon);
favicon::FaviconDriver* faviconDriver =
favicon::WebFaviconDriver::FromWebState(webState);
if (faviconDriver) {
gfx::Image favicon = faviconDriver->GetFavicon();
if (!favicon.IsEmpty())
completion(favicon.ToUIImage());
}
}
#pragma mark - Private
// Calls |-populateItems:selectedItemID:| on the consumer.
- (void)populateConsumerItems {
if (!self.webStateList)
return;
if (self.webStateList->count() > 0) {
[self.consumer populateItems:CreateItems(self.webStateList)
selectedItemID:GetActiveTabId(self.webStateList)];
}
}
@end
......@@ -10,6 +10,7 @@
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_consumer.h"
@class TabStripMediator;
@protocol TabFaviconDataSource;
// ViewController for the TabStrip. This ViewController is contained by
// BrowserViewController. This TabStripViewController is responsible for
......@@ -17,6 +18,8 @@
@interface TabStripViewController
: UICollectionViewController <TabStripConsumer>
@property(nonatomic, weak) id<TabFaviconDataSource> faviconDataSource;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNibName:(NSString*)nibNameOrNil
......
......@@ -5,6 +5,7 @@
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_view_controller.h"
#import "base/allocator/partition_allocator/partition_alloc.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_item.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_cell.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_mediator.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_view_layout.h"
......@@ -18,9 +19,15 @@ static NSString* const kReuseIdentifier = @"TabView";
} // namespace
@interface TabStripViewController ()
// Returns the number of tabs, the value is taken from the count() of
// the WebStateList.
@property(nonatomic, assign) NSUInteger tabsCount;
// The local model backing the collection view.
@property(nonatomic, strong) NSMutableArray<GridItem*>* items;
// Identifier of the selected item. This value is disregarded if |self.items| is
// empty.
@property(nonatomic, copy) NSString* selectedItemID;
// Index of the selected item in |items|.
@property(nonatomic, readonly) NSUInteger selectedIndex;
@end
@implementation TabStripViewController
......@@ -47,25 +54,61 @@ static NSString* const kReuseIdentifier = @"TabView";
- (NSInteger)collectionView:(UICollectionView*)collectionView
numberOfItemsInSection:(NSInteger)section {
return _tabsCount;
return _items.count;
}
- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
cellForItemAtIndexPath:(NSIndexPath*)indexPath {
NSUInteger itemIndex = base::checked_cast<NSUInteger>(indexPath.item);
if (itemIndex >= self.items.count)
itemIndex = self.items.count - 1;
GridItem* item = self.items[itemIndex];
TabStripCell* cell = (TabStripCell*)[collectionView
dequeueReusableCellWithReuseIdentifier:kReuseIdentifier
forIndexPath:indexPath];
cell.titleLabel.text = nil;
[self configureCell:cell withItem:item];
return cell;
}
#pragma mark - TabStripConsumer
- (void)setTabsCount:(NSUInteger)tabsCount {
if (_tabsCount == tabsCount)
return;
_tabsCount = tabsCount;
- (void)populateItems:(NSArray<GridItem*>*)items
selectedItemID:(NSString*)selectedItemID {
#ifndef NDEBUG
// Consistency check: ensure no IDs are duplicated.
NSMutableSet<NSString*>* identifiers = [[NSMutableSet alloc] init];
for (GridItem* item in items) {
[identifiers addObject:item.identifier];
}
CHECK_EQ(identifiers.count, items.count);
#endif
self.items = [items mutableCopy];
self.selectedItemID = selectedItemID;
[self.collectionView reloadData];
}
#pragma mark - Private
// Configures |cell|'s title synchronously, and favicon asynchronously with
// information from |item|. Updates the |cell|'s theme to this view controller's
// theme.
- (void)configureCell:(TabStripCell*)cell withItem:(GridItem*)item {
if (item) {
cell.itemIdentifier = item.identifier;
cell.titleLabel.text = item.title;
NSString* itemIdentifier = item.identifier;
[self.faviconDataSource
faviconForIdentifier:itemIdentifier
completion:^(UIImage* icon) {
// Only update the icon if the cell is not
// already reused for another item.
if (cell.itemIdentifier == itemIdentifier)
cell.faviconView.image = icon;
}];
}
}
@end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment