blob: 64d0017064d4f2ad32ad2336a6b97e95a63e00a7 [file] [log] [blame]
// Copyright 2018 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.
#import "ios/chrome/browser/ui/bookmarks/bookmark_home_mediator.h"
#include "base/logging.h"
#include "base/mac/foundation_util.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#import "ios/chrome/browser/sync/synced_sessions_bridge.h"
#import "ios/chrome/browser/ui/bookmarks/bookmark_home_consumer.h"
#import "ios/chrome/browser/ui/bookmarks/bookmark_home_shared_state.h"
#include "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h"
#import "ios/chrome/browser/ui/bookmarks/cells/bookmark_home_node_item.h"
#import "ios/chrome/browser/ui/table_view/table_view_model.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using bookmarks::BookmarkNode;
@interface BookmarkHomeMediator ()<BookmarkModelBridgeObserver,
SyncedSessionsObserver> {
// Bridge to register for bookmark changes.
std::unique_ptr<bookmarks::BookmarkModelBridge> _modelBridge;
// Observer to keep track of the signin and syncing status.
std::unique_ptr<synced_sessions::SyncedSessionsObserverBridge>
_syncedSessionsObserver;
}
@property(nonatomic, strong) BookmarkHomeSharedState* sharedState;
@property(nonatomic, assign) ios::ChromeBrowserState* browserState;
@end
@implementation BookmarkHomeMediator
@synthesize browserState = _browserState;
@synthesize consumer = _consumer;
@synthesize sharedState = _sharedState;
- (instancetype)initWithSharedState:(BookmarkHomeSharedState*)sharedState
browserState:(ios::ChromeBrowserState*)browserState {
if ((self = [super init])) {
_sharedState = sharedState;
_browserState = browserState;
}
return self;
}
- (void)startMediating {
DCHECK(self.consumer);
DCHECK(self.sharedState);
// Set up observers.
_modelBridge = std::make_unique<bookmarks::BookmarkModelBridge>(
self, self.sharedState.bookmarkModel);
_syncedSessionsObserver =
std::make_unique<synced_sessions::SyncedSessionsObserverBridge>(
self, self.browserState);
[self computeBookmarkTableViewData];
}
- (void)disconnect {
_modelBridge = nullptr;
_syncedSessionsObserver = nullptr;
self.browserState = nullptr;
self.consumer = nil;
self.sharedState = nil;
}
#pragma mark - Initial Model Setup
// Computes the bookmarks table view based on the current root node.
- (void)computeBookmarkTableViewData {
// Regenerate the list of all bookmarks.
if ([self.sharedState.tableViewModel
hasSectionForSectionIdentifier:
BookmarkHomeSectionIdentifierBookmarks]) {
[self.sharedState.tableViewModel
removeSectionWithIdentifier:BookmarkHomeSectionIdentifierBookmarks];
}
[self.sharedState.tableViewModel
addSectionWithIdentifier:BookmarkHomeSectionIdentifierBookmarks];
if (!self.sharedState.bookmarkModel->loaded() ||
!self.sharedState.tableViewDisplayedRootNode) {
[self updateTableViewBackground];
return;
}
if (self.sharedState.tableViewDisplayedRootNode ==
self.sharedState.bookmarkModel->root_node()) {
[self generateTableViewDataForRootNode];
[self updateTableViewBackground];
return;
}
[self generateTableViewData];
[self updateTableViewBackground];
}
// Generate the table view data when the current root node is a child node.
- (void)generateTableViewData {
if (!self.sharedState.tableViewDisplayedRootNode) {
return;
}
// Add all bookmarks and folders of the current root node to the table.
int childCount = self.sharedState.tableViewDisplayedRootNode->child_count();
for (int i = 0; i < childCount; ++i) {
const BookmarkNode* node =
self.sharedState.tableViewDisplayedRootNode->GetChild(i);
BookmarkHomeNodeItem* nodeItem =
[[BookmarkHomeNodeItem alloc] initWithType:BookmarkHomeItemTypeBookmark
bookmarkNode:node];
[self.sharedState.tableViewModel
addItem:nodeItem
toSectionWithIdentifier:BookmarkHomeSectionIdentifierBookmarks];
}
}
// Generate the table view data when the current root node is the outermost
// root.
- (void)generateTableViewDataForRootNode {
// Add "Mobile Bookmarks" to the table.
const BookmarkNode* mobileNode =
self.sharedState.bookmarkModel->mobile_node();
BookmarkHomeNodeItem* mobileItem =
[[BookmarkHomeNodeItem alloc] initWithType:BookmarkHomeItemTypeBookmark
bookmarkNode:mobileNode];
[self.sharedState.tableViewModel
addItem:mobileItem
toSectionWithIdentifier:BookmarkHomeSectionIdentifierBookmarks];
// Add "Bookmarks Bar" and "Other Bookmarks" only when they are not empty.
const BookmarkNode* bookmarkBar =
self.sharedState.bookmarkModel->bookmark_bar_node();
if (!bookmarkBar->empty()) {
BookmarkHomeNodeItem* barItem =
[[BookmarkHomeNodeItem alloc] initWithType:BookmarkHomeItemTypeBookmark
bookmarkNode:bookmarkBar];
[self.sharedState.tableViewModel
addItem:barItem
toSectionWithIdentifier:BookmarkHomeSectionIdentifierBookmarks];
}
const BookmarkNode* otherBookmarks =
self.sharedState.bookmarkModel->other_node();
if (!otherBookmarks->empty()) {
BookmarkHomeNodeItem* otherItem =
[[BookmarkHomeNodeItem alloc] initWithType:BookmarkHomeItemTypeBookmark
bookmarkNode:otherBookmarks];
[self.sharedState.tableViewModel
addItem:otherItem
toSectionWithIdentifier:BookmarkHomeSectionIdentifierBookmarks];
}
}
- (void)updateTableViewBackground {
// If the current root node is the outermost root, check if we need to show
// the spinner backgound. Otherwise, check if we need to show the empty
// background.
if (self.sharedState.tableViewDisplayedRootNode ==
self.sharedState.bookmarkModel->root_node()) {
if (self.sharedState.bookmarkModel->HasNoUserCreatedBookmarksOrFolders() &&
_syncedSessionsObserver->IsSyncing()) {
[self.consumer
updateTableViewBackgroundStyle:BookmarkHomeBackgroundStyleLoading];
} else {
[self.consumer
updateTableViewBackgroundStyle:BookmarkHomeBackgroundStyleDefault];
}
return;
}
if (![self hasBookmarksOrFolders]) {
[self.consumer
updateTableViewBackgroundStyle:BookmarkHomeBackgroundStyleEmpty];
} else {
[self.consumer
updateTableViewBackgroundStyle:BookmarkHomeBackgroundStyleDefault];
}
}
#pragma mark - BookmarkModelBridgeObserver Callbacks
// BookmarkModelBridgeObserver Callbacks
// Instances of this class automatically observe the bookmark model.
// The bookmark model has loaded.
- (void)bookmarkModelLoaded {
[self.consumer refreshContents];
}
// The node has changed, but not its children.
- (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
// The root folder changed. Do nothing.
if (bookmarkNode == self.sharedState.tableViewDisplayedRootNode) {
return;
}
// A specific cell changed. Reload, if currently shown.
if ([self itemForNode:bookmarkNode] != nil) {
[self.consumer refreshContents];
}
}
// The node has not changed, but its children have.
- (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
// The current root folder's children changed. Reload everything.
// (When adding new folder, table is already been updated. So no need to
// reload here.)
if (bookmarkNode == self.sharedState.tableViewDisplayedRootNode &&
!self.sharedState.addingNewFolder) {
[self.consumer refreshContents];
return;
}
}
// The node has moved to a new parent folder.
- (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
movedFromParent:(const BookmarkNode*)oldParent
toParent:(const BookmarkNode*)newParent {
if (oldParent == self.sharedState.tableViewDisplayedRootNode ||
newParent == self.sharedState.tableViewDisplayedRootNode) {
// A folder was added or removed from the current root folder.
[self.consumer refreshContents];
}
}
// |node| was deleted from |folder|.
- (void)bookmarkNodeDeleted:(const BookmarkNode*)node
fromFolder:(const BookmarkNode*)folder {
if (self.sharedState.tableViewDisplayedRootNode == node) {
self.sharedState.tableViewDisplayedRootNode = NULL;
[self.consumer refreshContents];
}
}
// All non-permanent nodes have been removed.
- (void)bookmarkModelRemovedAllNodes {
// TODO(crbug.com/695749) Check if this case is applicable in the new UI.
}
- (void)bookmarkNodeFaviconChanged:
(const bookmarks::BookmarkNode*)bookmarkNode {
// Only urls have favicons.
DCHECK(bookmarkNode->is_url());
// Update image of corresponding cell.
BookmarkHomeNodeItem* nodeItem = [self itemForNode:bookmarkNode];
if (!nodeItem) {
return;
}
// Check that this cell is visible.
NSIndexPath* indexPath =
[self.sharedState.tableViewModel indexPathForItem:nodeItem];
NSArray* visiblePaths = [self.sharedState.tableView indexPathsForVisibleRows];
if (![visiblePaths containsObject:indexPath]) {
return;
}
// Get the favicon from cache directly. (no need to fetch from server)
[self.consumer loadFaviconAtIndexPath:indexPath continueToGoogleServer:NO];
}
- (BookmarkHomeNodeItem*)itemForNode:
(const bookmarks::BookmarkNode*)bookmarkNode {
NSArray<TableViewItem*>* items = [self.sharedState.tableViewModel
itemsInSectionWithIdentifier:BookmarkHomeSectionIdentifierBookmarks];
for (TableViewItem* item in items) {
if (item.type == BookmarkHomeItemTypeBookmark) {
BookmarkHomeNodeItem* nodeItem =
base::mac::ObjCCastStrict<BookmarkHomeNodeItem>(item);
if (nodeItem.bookmarkNode == bookmarkNode) {
return nodeItem;
}
}
}
return nil;
}
#pragma mark - SyncedSessionsObserver
- (void)reloadSessions {
// Nothing to do.
}
- (void)onSyncStateChanged {
// Permanent nodes ("Bookmarks Bar", "Other Bookmarks") at the root node might
// be added after syncing. So we need to refresh here.
if (self.sharedState.tableViewDisplayedRootNode ==
self.sharedState.bookmarkModel->root_node()) {
[self.consumer refreshContents];
return;
}
[self updateTableViewBackground];
}
#pragma mark - Private Helpers
- (BOOL)hasBookmarksOrFolders {
return self.sharedState.tableViewDisplayedRootNode &&
!self.sharedState.tableViewDisplayedRootNode->empty();
}
@end