| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collectio
n_updater.h" | 5 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collectio
n_updater.h" |
| 6 | 6 |
| 7 #include "base/logging.h" | 7 #include "base/logging.h" |
| 8 #include "base/mac/foundation_util.h" | 8 #include "base/mac/foundation_util.h" |
| 9 #include "base/strings/sys_string_conversions.h" | 9 #include "base/strings/sys_string_conversions.h" |
| 10 #include "base/time/time.h" | 10 #include "base/time/time.h" |
| 11 #include "components/strings/grit/components_strings.h" | 11 #include "components/strings/grit/components_strings.h" |
| 12 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_item+collec
tion_view_controller.h" |
| 12 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_text_item.h
" | 13 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_text_item.h
" |
| 13 #import "ios/chrome/browser/ui/collection_view/collection_view_controller.h" | 14 #import "ios/chrome/browser/ui/collection_view/collection_view_controller.h" |
| 14 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h" | 15 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h" |
| 15 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_foo
ter_item.h" | 16 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_foo
ter_item.h" |
| 16 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_ite
m.h" | 17 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_ite
m.h" |
| 17 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_mos
t_visited_item.h" | 18 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_mos
t_visited_item.h" |
| 18 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_tex
t_item.h" | 19 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_tex
t_item.h" |
| 19 #import "ios/chrome/browser/ui/content_suggestions/content_suggestion.h" | 20 #import "ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h" |
| 20 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_data_sink
.h" | 21 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_data_sink
.h" |
| 21 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_data_sour
ce.h" | 22 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_data_sour
ce.h" |
| 22 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_image_fet
cher.h" | 23 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_image_fet
cher.h" |
| 23 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_view_cont
roller.h" | 24 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_view_cont
roller.h" |
| 24 #import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion
_identifier.h" | 25 #import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion
_identifier.h" |
| 25 #import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion
s_section_information.h" | 26 #import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion
s_section_information.h" |
| 26 #import "ios/chrome/browser/ui/favicon/favicon_attributes.h" | 27 #import "ios/chrome/browser/ui/favicon/favicon_attributes.h" |
| 27 #include "ui/base/l10n/l10n_util.h" | 28 #include "ui/base/l10n/l10n_util.h" |
| 28 #include "url/gurl.h" | 29 #include "url/gurl.h" |
| 29 | 30 |
| 30 #if !defined(__has_feature) || !__has_feature(objc_arc) | 31 #if !defined(__has_feature) || !__has_feature(objc_arc) |
| 31 #error "This file requires ARC support." | 32 #error "This file requires ARC support." |
| 32 #endif | 33 #endif |
| 33 | 34 |
| 34 namespace { | 35 namespace { |
| 35 | 36 |
| 36 using CSCollectionViewItem = | 37 using CSCollectionViewItem = CollectionViewItem<SuggestedContent>; |
| 37 CollectionViewItem<ContentSuggestionIdentification>; | |
| 38 using CSCollectionViewModel = CollectionViewModel<CSCollectionViewItem*>; | 38 using CSCollectionViewModel = CollectionViewModel<CSCollectionViewItem*>; |
| 39 | 39 |
| 40 // Enum defining the ItemType of this ContentSuggestionsCollectionUpdater. | 40 // Enum defining the ItemType of this ContentSuggestionsCollectionUpdater. |
| 41 typedef NS_ENUM(NSInteger, ItemType) { | 41 typedef NS_ENUM(NSInteger, ItemType) { |
| 42 ItemTypeArticle = kItemTypeEnumZero, | 42 ItemTypeArticle = kItemTypeEnumZero, |
| 43 ItemTypeFooter, | 43 ItemTypeFooter, |
| 44 ItemTypeHeader, | 44 ItemTypeHeader, |
| 45 ItemTypeEmpty, | 45 ItemTypeEmpty, |
| 46 ItemTypeReadingList, | 46 ItemTypeReadingList, |
| 47 ItemTypeMostVisited, | 47 ItemTypeMostVisited, |
| 48 ItemTypeUnknown, |
| 48 }; | 49 }; |
| 49 | 50 |
| 51 // Enum defining the SectionIdentifier of this |
| 52 // ContentSuggestionsCollectionUpdater. |
| 50 typedef NS_ENUM(NSInteger, SectionIdentifier) { | 53 typedef NS_ENUM(NSInteger, SectionIdentifier) { |
| 51 SectionIdentifierArticles = kSectionIdentifierEnumZero, | 54 SectionIdentifierArticles = kSectionIdentifierEnumZero, |
| 52 SectionIdentifierReadingList, | 55 SectionIdentifierReadingList, |
| 53 SectionIdentifierMostVisited, | 56 SectionIdentifierMostVisited, |
| 54 SectionIdentifierDefault, | 57 SectionIdentifierDefault, |
| 55 }; | 58 }; |
| 56 | 59 |
| 57 // Update ContentSuggestionTypeForItemType if you update this function. | 60 // Returns the ContentSuggestionType associated with an ItemType |type|. |
| 58 ItemType ItemTypeForContentSuggestionType(ContentSuggestionType type) { | |
| 59 switch (type) { | |
| 60 case ContentSuggestionTypeArticle: | |
| 61 return ItemTypeArticle; | |
| 62 case ContentSuggestionTypeEmpty: | |
| 63 return ItemTypeEmpty; | |
| 64 case ContentSuggestionTypeReadingList: | |
| 65 return ItemTypeReadingList; | |
| 66 case ContentSuggestionTypeMostVisited: | |
| 67 return ItemTypeMostVisited; | |
| 68 } | |
| 69 } | |
| 70 | |
| 71 ContentSuggestionType ContentSuggestionTypeForItemType(NSInteger type) { | 61 ContentSuggestionType ContentSuggestionTypeForItemType(NSInteger type) { |
| 72 if (type == ItemTypeArticle) | 62 if (type == ItemTypeArticle) |
| 73 return ContentSuggestionTypeArticle; | 63 return ContentSuggestionTypeArticle; |
| 74 if (type == ItemTypeEmpty) | 64 if (type == ItemTypeEmpty) |
| 75 return ContentSuggestionTypeEmpty; | 65 return ContentSuggestionTypeEmpty; |
| 76 if (type == ItemTypeReadingList) | 66 if (type == ItemTypeReadingList) |
| 77 return ContentSuggestionTypeReadingList; | 67 return ContentSuggestionTypeReadingList; |
| 78 if (type == ItemTypeMostVisited) | 68 if (type == ItemTypeMostVisited) |
| 79 return ContentSuggestionTypeMostVisited; | 69 return ContentSuggestionTypeMostVisited; |
| 80 // Add new type here | 70 // Add new type here |
| 81 | 71 |
| 82 // Default type. | 72 // Default type. |
| 83 return ContentSuggestionTypeEmpty; | 73 return ContentSuggestionTypeEmpty; |
| 84 } | 74 } |
| 85 | 75 |
| 86 // Returns the section identifier corresponding to the section |info|. | 76 // Returns the section identifier corresponding to the section |info|. |
| 77 ItemType ItemTypeForInfo(ContentSuggestionsSectionInformation* info) { |
| 78 switch (info.sectionID) { |
| 79 case ContentSuggestionsSectionArticles: |
| 80 return ItemTypeArticle; |
| 81 case ContentSuggestionsSectionReadingList: |
| 82 return ItemTypeReadingList; |
| 83 case ContentSuggestionsSectionMostVisited: |
| 84 return ItemTypeMostVisited; |
| 85 |
| 86 case ContentSuggestionsSectionUnknown: |
| 87 return ItemTypeUnknown; |
| 88 } |
| 89 } |
| 90 |
| 91 // Returns the section identifier corresponding to the section |info|. |
| 87 SectionIdentifier SectionIdentifierForInfo( | 92 SectionIdentifier SectionIdentifierForInfo( |
| 88 ContentSuggestionsSectionInformation* info) { | 93 ContentSuggestionsSectionInformation* info) { |
| 89 switch (info.sectionID) { | 94 switch (info.sectionID) { |
| 90 case ContentSuggestionsSectionArticles: | 95 case ContentSuggestionsSectionArticles: |
| 91 return SectionIdentifierArticles; | 96 return SectionIdentifierArticles; |
| 92 | |
| 93 case ContentSuggestionsSectionReadingList: | 97 case ContentSuggestionsSectionReadingList: |
| 94 return SectionIdentifierReadingList; | 98 return SectionIdentifierReadingList; |
| 95 | |
| 96 case ContentSuggestionsSectionMostVisited: | 99 case ContentSuggestionsSectionMostVisited: |
| 97 return SectionIdentifierMostVisited; | 100 return SectionIdentifierMostVisited; |
| 98 | 101 |
| 99 case ContentSuggestionsSectionUnknown: | 102 case ContentSuggestionsSectionUnknown: |
| 100 return SectionIdentifierDefault; | 103 return SectionIdentifierDefault; |
| 101 } | 104 } |
| 102 } | 105 } |
| 103 | 106 |
| 104 } // namespace | 107 } // namespace |
| 105 | 108 |
| 106 @interface ContentSuggestionsCollectionUpdater ()< | 109 @interface ContentSuggestionsCollectionUpdater ()<ContentSuggestionsDataSink, |
| 107 ContentSuggestionsItemDelegate, | 110 SuggestedContentDelegate> |
| 108 ContentSuggestionsDataSink> | |
| 109 | 111 |
| 110 @property(nonatomic, weak) id<ContentSuggestionsDataSource> dataSource; | 112 @property(nonatomic, weak) id<ContentSuggestionsDataSource> dataSource; |
| 111 @property(nonatomic, strong) | 113 @property(nonatomic, strong) |
| 112 NSMutableDictionary<NSNumber*, ContentSuggestionsSectionInformation*>* | 114 NSMutableDictionary<NSNumber*, ContentSuggestionsSectionInformation*>* |
| 113 sectionInfoBySectionIdentifier; | 115 sectionInfoBySectionIdentifier; |
| 114 | 116 |
| 115 @end | 117 @end |
| 116 | 118 |
| 117 @implementation ContentSuggestionsCollectionUpdater | 119 @implementation ContentSuggestionsCollectionUpdater |
| 118 | 120 |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 150 if ([model hasSectionForSectionIdentifier:sectionIdentifier]) { | 152 if ([model hasSectionForSectionIdentifier:sectionIdentifier]) { |
| 151 NSArray<CSCollectionViewItem*>* items = | 153 NSArray<CSCollectionViewItem*>* items = |
| 152 [model itemsInSectionWithIdentifier:sectionIdentifier]; | 154 [model itemsInSectionWithIdentifier:sectionIdentifier]; |
| 153 if (items.count > 0 && items[0].type != ItemTypeEmpty) { | 155 if (items.count > 0 && items[0].type != ItemTypeEmpty) { |
| 154 // Do not dismiss the presented items. | 156 // Do not dismiss the presented items. |
| 155 return; | 157 return; |
| 156 } | 158 } |
| 157 } | 159 } |
| 158 | 160 |
| 159 [self.collectionViewController | 161 [self.collectionViewController |
| 160 addSuggestions:[self.dataSource suggestionsForSection:sectionInfo]]; | 162 addSuggestions:[self.dataSource itemsForSectionInfo:sectionInfo] |
| 163 toSectionInfo:sectionInfo]; |
| 161 } | 164 } |
| 162 | 165 |
| 163 - (void)clearSuggestion:(ContentSuggestionIdentifier*)suggestionIdentifier { | 166 - (void)clearSuggestion:(ContentSuggestionIdentifier*)suggestionIdentifier { |
| 164 SectionIdentifier sectionIdentifier = | 167 SectionIdentifier sectionIdentifier = |
| 165 SectionIdentifierForInfo(suggestionIdentifier.sectionInfo); | 168 SectionIdentifierForInfo(suggestionIdentifier.sectionInfo); |
| 166 if (![self.collectionViewController.collectionViewModel | 169 if (![self.collectionViewController.collectionViewModel |
| 167 hasSectionForSectionIdentifier:sectionIdentifier]) { | 170 hasSectionForSectionIdentifier:sectionIdentifier]) { |
| 168 return; | 171 return; |
| 169 } | 172 } |
| 170 | 173 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 186 NSIndexPath* indexPath = [self.collectionViewController.collectionViewModel | 189 NSIndexPath* indexPath = [self.collectionViewController.collectionViewModel |
| 187 indexPathForItem:correspondingItem]; | 190 indexPathForItem:correspondingItem]; |
| 188 [self.collectionViewController dismissEntryAtIndexPath:indexPath]; | 191 [self.collectionViewController dismissEntryAtIndexPath:indexPath]; |
| 189 } | 192 } |
| 190 | 193 |
| 191 - (void)reloadAllData { | 194 - (void)reloadAllData { |
| 192 [self resetModels]; | 195 [self resetModels]; |
| 193 | 196 |
| 194 // The data is reset, add the new data directly in the model then reload the | 197 // The data is reset, add the new data directly in the model then reload the |
| 195 // collection. | 198 // collection. |
| 196 NSArray<ContentSuggestion*>* suggestions = [self.dataSource allSuggestions]; | 199 NSArray<ContentSuggestionsSectionInformation*>* sectionInfos = |
| 197 [self addSectionsForSuggestionsToModel:suggestions]; | 200 [self.dataSource sectionsInfo]; |
| 198 [self addSuggestionsToModel:suggestions]; | 201 [self addSectionsForSectionInfoToModel:sectionInfos]; |
| 202 for (ContentSuggestionsSectionInformation* sectionInfo in sectionInfos) { |
| 203 [self |
| 204 addSuggestionsToModel:[self.dataSource itemsForSectionInfo:sectionInfo] |
| 205 withSectionInfo:sectionInfo]; |
| 206 } |
| 199 [self.collectionViewController.collectionView reloadData]; | 207 [self.collectionViewController.collectionView reloadData]; |
| 200 } | 208 } |
| 201 | 209 |
| 202 - (void)clearSection:(ContentSuggestionsSectionInformation*)sectionInfo { | 210 - (void)clearSection:(ContentSuggestionsSectionInformation*)sectionInfo { |
| 203 SectionIdentifier sectionIdentifier = SectionIdentifierForInfo(sectionInfo); | 211 SectionIdentifier sectionIdentifier = SectionIdentifierForInfo(sectionInfo); |
| 204 NSInteger section = [self.collectionViewController.collectionViewModel | 212 NSInteger section = [self.collectionViewController.collectionViewModel |
| 205 sectionIdentifierForSection:sectionIdentifier]; | 213 sectionIdentifierForSection:sectionIdentifier]; |
| 206 | 214 |
| 207 [self.collectionViewController dismissSection:section]; | 215 [self.collectionViewController dismissSection:section]; |
| 208 } | 216 } |
| (...skipping 14 matching lines...) Expand all Loading... |
| 223 ContentSuggestionsSectionInformation* sectionInformation = | 231 ContentSuggestionsSectionInformation* sectionInformation = |
| 224 self.sectionInfoBySectionIdentifier[identifier]; | 232 self.sectionInfoBySectionIdentifier[identifier]; |
| 225 return sectionInformation.layout == ContentSuggestionsSectionLayoutCustom; | 233 return sectionInformation.layout == ContentSuggestionsSectionLayoutCustom; |
| 226 } | 234 } |
| 227 | 235 |
| 228 - (ContentSuggestionType)contentSuggestionTypeForItem: | 236 - (ContentSuggestionType)contentSuggestionTypeForItem: |
| 229 (CollectionViewItem*)item { | 237 (CollectionViewItem*)item { |
| 230 return ContentSuggestionTypeForItemType(item.type); | 238 return ContentSuggestionTypeForItemType(item.type); |
| 231 } | 239 } |
| 232 | 240 |
| 233 - (NSArray<NSIndexPath*>*)addSuggestionsToModel: | 241 - (NSArray<NSIndexPath*>*) |
| 234 (NSArray<ContentSuggestion*>*)suggestions { | 242 addSuggestionsToModel:(NSArray<CSCollectionViewItem*>*)suggestions |
| 235 if (suggestions.count == 0) { | 243 withSectionInfo:(ContentSuggestionsSectionInformation*)sectionInfo { |
| 236 return [NSArray array]; | 244 NSMutableArray<NSIndexPath*>* indexPaths = [NSMutableArray array]; |
| 237 } | |
| 238 | 245 |
| 239 CSCollectionViewModel* model = | 246 CSCollectionViewModel* model = |
| 240 self.collectionViewController.collectionViewModel; | 247 self.collectionViewController.collectionViewModel; |
| 241 NSMutableArray<NSIndexPath*>* indexPaths = [NSMutableArray array]; | 248 NSInteger sectionIdentifier = SectionIdentifierForInfo(sectionInfo); |
| 242 for (ContentSuggestion* suggestion in suggestions) { | |
| 243 ContentSuggestionsSectionInformation* sectionInfo = | |
| 244 suggestion.suggestionIdentifier.sectionInfo; | |
| 245 NSInteger sectionIdentifier = SectionIdentifierForInfo(sectionInfo); | |
| 246 | 249 |
| 247 if (![model hasSectionForSectionIdentifier:sectionIdentifier]) | 250 if (suggestions.count == 0) { |
| 248 continue; | 251 if ([model hasSectionForSectionIdentifier:sectionIdentifier] && |
| 252 [model numberOfItemsInSection:[model sectionForSectionIdentifier: |
| 253 sectionIdentifier]] == 0) { |
| 254 [indexPaths |
| 255 addObject:[self |
| 256 addEmptyItemForSection:[model |
| 257 sectionForSectionIdentifier: |
| 258 sectionIdentifier]]]; |
| 259 } |
| 260 return indexPaths; |
| 261 } |
| 249 | 262 |
| 250 NSInteger section = [model sectionForSectionIdentifier:sectionIdentifier]; | 263 BOOL emptyItemRemoved = NO; |
| 251 NSIndexPath* indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; | 264 NSArray<CSCollectionViewItem*>* existingItems = |
| 265 [model itemsInSectionWithIdentifier:sectionIdentifier]; |
| 266 if (existingItems.count > 0 && existingItems[0].type == ItemTypeEmpty) { |
| 267 [model removeItemWithType:ItemTypeEmpty |
| 268 fromSectionWithIdentifier:sectionIdentifier]; |
| 269 emptyItemRemoved = YES; |
| 270 } |
| 252 | 271 |
| 253 if (suggestion.type != ContentSuggestionTypeEmpty && | 272 [suggestions enumerateObjectsUsingBlock:^(CSCollectionViewItem* item, |
| 254 [model hasItemAtIndexPath:indexPath] && | 273 NSUInteger index, BOOL* stop) { |
| 255 [model itemAtIndexPath:indexPath].type == ItemTypeEmpty) { | 274 ItemType type = ItemTypeForInfo(sectionInfo); |
| 256 [self.collectionViewController dismissEntryAtIndexPath:indexPath]; | 275 item.type = type; |
| 276 NSIndexPath* addedIndexPath = |
| 277 [self addItem:item toSectionWithIdentifier:sectionIdentifier]; |
| 278 [self fetchFaviconForItem:item]; |
| 279 item.delegate = self; |
| 280 |
| 281 if (!emptyItemRemoved || index > 0) { |
| 282 [indexPaths addObject:addedIndexPath]; |
| 283 } else { |
| 284 [self.collectionViewController.collectionViewLayout invalidateLayout]; |
| 257 } | 285 } |
| 258 | 286 }]; |
| 259 switch (suggestion.type) { | |
| 260 case ContentSuggestionTypeEmpty: { | |
| 261 if ([model hasSectionForSectionIdentifier:sectionIdentifier] && | |
| 262 [model numberOfItemsInSection:[model sectionForSectionIdentifier: | |
| 263 sectionIdentifier]] == 0) { | |
| 264 CSCollectionViewItem* item = | |
| 265 [self emptyItemForSectionInfo:sectionInfo]; | |
| 266 NSIndexPath* addedIndexPath = | |
| 267 [self addItem:item toSectionWithIdentifier:sectionIdentifier]; | |
| 268 [indexPaths addObject:addedIndexPath]; | |
| 269 } | |
| 270 break; | |
| 271 } | |
| 272 case ContentSuggestionTypeArticle: { | |
| 273 ContentSuggestionsItem* articleItem = | |
| 274 [self suggestionItemForSuggestion:suggestion]; | |
| 275 | |
| 276 articleItem.hasImage = YES; | |
| 277 | |
| 278 __weak ContentSuggestionsItem* weakItem = articleItem; | |
| 279 [self fetchFaviconForItem:articleItem | |
| 280 withURL:articleItem.URL | |
| 281 callback:^void(FaviconAttributes* attributes) { | |
| 282 weakItem.attributes = attributes; | |
| 283 }]; | |
| 284 | |
| 285 __weak ContentSuggestionsCollectionUpdater* weakSelf = self; | |
| 286 [self.dataSource | |
| 287 fetchFaviconImageForSuggestion:articleItem.suggestionIdentifier | |
| 288 completion:^void(UIImage* favicon) { | |
| 289 ContentSuggestionsCollectionUpdater* | |
| 290 strongSelf = weakSelf; | |
| 291 ContentSuggestionsItem* strongItem = weakItem; | |
| 292 if (!strongItem || !strongSelf) | |
| 293 return; | |
| 294 | |
| 295 strongItem.attributes = [FaviconAttributes | |
| 296 attributesWithImage:favicon]; | |
| 297 [strongSelf.collectionViewController | |
| 298 reconfigureCellsForItems:@[ strongItem ]]; | |
| 299 }]; | |
| 300 | |
| 301 NSIndexPath* addedIndexPath = [self addItem:articleItem | |
| 302 toSectionWithIdentifier:sectionIdentifier]; | |
| 303 [indexPaths addObject:addedIndexPath]; | |
| 304 break; | |
| 305 } | |
| 306 case ContentSuggestionTypeReadingList: { | |
| 307 ContentSuggestionsItem* readingListItem = | |
| 308 [self suggestionItemForSuggestion:suggestion]; | |
| 309 | |
| 310 __weak ContentSuggestionsItem* weakItem = readingListItem; | |
| 311 [self fetchFaviconForItem:readingListItem | |
| 312 withURL:readingListItem.URL | |
| 313 callback:^void(FaviconAttributes* attributes) { | |
| 314 weakItem.attributes = attributes; | |
| 315 }]; | |
| 316 | |
| 317 NSIndexPath* addedIndexPath = [self addItem:readingListItem | |
| 318 toSectionWithIdentifier:sectionIdentifier]; | |
| 319 [indexPaths addObject:addedIndexPath]; | |
| 320 break; | |
| 321 } | |
| 322 case ContentSuggestionTypeMostVisited: { | |
| 323 ContentSuggestionsMostVisitedItem* mostVisitedItem = | |
| 324 [[ContentSuggestionsMostVisitedItem alloc] | |
| 325 initWithType:ItemTypeMostVisited]; | |
| 326 mostVisitedItem.title = suggestion.title; | |
| 327 [model addItem:mostVisitedItem | |
| 328 toSectionWithIdentifier:SectionIdentifierMostVisited]; | |
| 329 [indexPaths addObject:indexPath]; | |
| 330 break; | |
| 331 } | |
| 332 } | |
| 333 } | |
| 334 | 287 |
| 335 return indexPaths; | 288 return indexPaths; |
| 336 } | 289 } |
| 337 | 290 |
| 338 - (NSIndexSet*)addSectionsForSuggestionsToModel: | 291 - (NSIndexSet*)addSectionsForSectionInfoToModel: |
| 339 (NSArray<ContentSuggestion*>*)suggestions { | 292 (NSArray<ContentSuggestionsSectionInformation*>*)sectionsInfo { |
| 340 NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet]; | 293 NSMutableIndexSet* addedSectionIdentifiers = [NSMutableIndexSet indexSet]; |
| 294 NSArray<ContentSuggestionsSectionInformation*>* orderedSectionsInfo = |
| 295 [self.dataSource sectionsInfo]; |
| 341 | 296 |
| 342 CSCollectionViewModel* model = | 297 CSCollectionViewModel* model = |
| 343 self.collectionViewController.collectionViewModel; | 298 self.collectionViewController.collectionViewModel; |
| 344 for (ContentSuggestion* suggestion in suggestions) { | 299 for (ContentSuggestionsSectionInformation* sectionInfo in sectionsInfo) { |
| 345 ContentSuggestionsSectionInformation* sectionInfo = | |
| 346 suggestion.suggestionIdentifier.sectionInfo; | |
| 347 NSInteger sectionIdentifier = SectionIdentifierForInfo(sectionInfo); | 300 NSInteger sectionIdentifier = SectionIdentifierForInfo(sectionInfo); |
| 348 | 301 |
| 349 if ([model hasSectionForSectionIdentifier:sectionIdentifier] || | 302 if ([model hasSectionForSectionIdentifier:sectionIdentifier] || |
| 350 (suggestion.type == ContentSuggestionTypeEmpty && | 303 (!sectionInfo.showIfEmpty && |
| 351 !sectionInfo.showIfEmpty)) { | 304 [self.dataSource itemsForSectionInfo:sectionInfo].count == 0)) { |
| 352 continue; | 305 continue; |
| 353 } | 306 } |
| 354 | 307 |
| 355 [model addSectionWithIdentifier:sectionIdentifier]; | 308 NSUInteger sectionIndex = 0; |
| 309 for (ContentSuggestionsSectionInformation* orderedSectionInfo in |
| 310 orderedSectionsInfo) { |
| 311 NSInteger orderedSectionIdentifier = |
| 312 SectionIdentifierForInfo(orderedSectionInfo); |
| 313 if ([model hasSectionForSectionIdentifier:orderedSectionIdentifier]) { |
| 314 sectionIndex++; |
| 315 } |
| 316 } |
| 317 [model insertSectionWithIdentifier:sectionIdentifier atIndex:sectionIndex]; |
| 318 |
| 356 self.sectionInfoBySectionIdentifier[@(sectionIdentifier)] = sectionInfo; | 319 self.sectionInfoBySectionIdentifier[@(sectionIdentifier)] = sectionInfo; |
| 357 [indexSet addIndex:[model sectionForSectionIdentifier:sectionIdentifier]]; | 320 [addedSectionIdentifiers addIndex:sectionIdentifier]; |
| 358 | 321 |
| 359 [self addHeader:sectionInfo]; | 322 [self addHeaderIfNeeded:sectionInfo]; |
| 360 [self addFooterIfNeeded:sectionInfo]; | 323 [self addFooterIfNeeded:sectionInfo]; |
| 361 } | 324 } |
| 325 |
| 326 NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet]; |
| 327 [addedSectionIdentifiers enumerateIndexesUsingBlock:^( |
| 328 NSUInteger sectionIdentifier, |
| 329 BOOL* _Nonnull stop) { |
| 330 [indexSet addIndex:[model sectionForSectionIdentifier:sectionIdentifier]]; |
| 331 }]; |
| 362 return indexSet; | 332 return indexSet; |
| 363 } | 333 } |
| 364 | 334 |
| 365 - (NSIndexPath*)addEmptyItemForSection:(NSInteger)section { | 335 - (NSIndexPath*)addEmptyItemForSection:(NSInteger)section { |
| 366 CSCollectionViewModel* model = | 336 CSCollectionViewModel* model = |
| 367 self.collectionViewController.collectionViewModel; | 337 self.collectionViewController.collectionViewModel; |
| 368 NSInteger sectionIdentifier = [model sectionIdentifierForSection:section]; | 338 NSInteger sectionIdentifier = [model sectionIdentifierForSection:section]; |
| 369 ContentSuggestionsSectionInformation* sectionInfo = | 339 ContentSuggestionsSectionInformation* sectionInfo = |
| 370 self.sectionInfoBySectionIdentifier[@(sectionIdentifier)]; | 340 self.sectionInfoBySectionIdentifier[@(sectionIdentifier)]; |
| 371 | 341 |
| 372 CSCollectionViewItem* item = [self emptyItemForSectionInfo:sectionInfo]; | 342 CSCollectionViewItem* item = [self emptyItemForSectionInfo:sectionInfo]; |
| 373 return [self addItem:item toSectionWithIdentifier:sectionIdentifier]; | 343 return [self addItem:item toSectionWithIdentifier:sectionIdentifier]; |
| 374 } | 344 } |
| 375 | 345 |
| 376 #pragma mark - ContentSuggestionsItemDelegate | 346 #pragma mark - SuggestedContentDelegate |
| 377 | 347 |
| 378 - (void)loadImageForSuggestionItem:(ContentSuggestionsItem*)suggestionItem { | 348 - (void)loadImageForSuggestedItem:(CSCollectionViewItem*)suggestedItem { |
| 379 __weak ContentSuggestionsCollectionUpdater* weakSelf = self; | 349 __weak ContentSuggestionsCollectionUpdater* weakSelf = self; |
| 380 __weak ContentSuggestionsItem* weakArticle = suggestionItem; | 350 __weak CSCollectionViewItem* weakItem = suggestedItem; |
| 381 | 351 |
| 382 void (^imageFetchedCallback)(UIImage*) = ^(UIImage* image) { | 352 void (^imageFetchedCallback)(UIImage*) = ^(UIImage* image) { |
| 383 ContentSuggestionsCollectionUpdater* strongSelf = weakSelf; | 353 ContentSuggestionsCollectionUpdater* strongSelf = weakSelf; |
| 384 ContentSuggestionsItem* strongArticle = weakArticle; | 354 CSCollectionViewItem* strongItem = weakItem; |
| 385 if (!strongSelf || !strongArticle) { | 355 if (!strongSelf || !strongItem) { |
| 386 return; | 356 return; |
| 387 } | 357 } |
| 388 | 358 |
| 389 strongArticle.image = image; | 359 strongItem.image = image; |
| 390 [strongSelf.collectionViewController | 360 [strongSelf.collectionViewController |
| 391 reconfigureCellsForItems:@[ strongArticle ]]; | 361 reconfigureCellsForItems:@[ strongItem ]]; |
| 392 }; | 362 }; |
| 393 | 363 |
| 394 [self.dataSource.imageFetcher | 364 [self.dataSource.imageFetcher |
| 395 fetchImageForSuggestion:suggestionItem.suggestionIdentifier | 365 fetchImageForSuggestion:suggestedItem.suggestionIdentifier |
| 396 callback:imageFetchedCallback]; | 366 callback:imageFetchedCallback]; |
| 397 } | 367 } |
| 398 | 368 |
| 399 #pragma mark - Private methods | 369 #pragma mark - Private methods |
| 400 | 370 |
| 401 // Adds a footer to the section identified by |sectionInfo| if there is none | 371 // Adds a footer to the section identified by |sectionInfo| if there is none |
| 402 // present and the section info contains a title for it. | 372 // present and the section info contains a title for it. |
| 403 - (void)addFooterIfNeeded:(ContentSuggestionsSectionInformation*)sectionInfo { | 373 - (void)addFooterIfNeeded:(ContentSuggestionsSectionInformation*)sectionInfo { |
| 404 NSInteger sectionIdentifier = SectionIdentifierForInfo(sectionInfo); | 374 NSInteger sectionIdentifier = SectionIdentifierForInfo(sectionInfo); |
| 405 | 375 |
| 406 __weak ContentSuggestionsCollectionUpdater* weakSelf = self; | 376 __weak ContentSuggestionsCollectionUpdater* weakSelf = self; |
| 407 if (sectionInfo.footerTitle && | 377 if (sectionInfo.footerTitle && |
| 408 ![self.collectionViewController.collectionViewModel | 378 ![self.collectionViewController.collectionViewModel |
| 409 footerForSectionWithIdentifier:sectionIdentifier]) { | 379 footerForSectionWithIdentifier:sectionIdentifier]) { |
| 410 ContentSuggestionsFooterItem* footer = [[ContentSuggestionsFooterItem alloc] | 380 ContentSuggestionsFooterItem* footer = [[ContentSuggestionsFooterItem alloc] |
| 411 initWithType:ItemTypeFooter | 381 initWithType:ItemTypeFooter |
| 412 title:sectionInfo.footerTitle | 382 title:sectionInfo.footerTitle |
| 413 block:^{ | 383 block:^{ |
| 414 [weakSelf runAdditionalActionForSection:sectionInfo]; | 384 [weakSelf runAdditionalActionForSection:sectionInfo]; |
| 415 }]; | 385 }]; |
| 416 | 386 |
| 417 [self.collectionViewController.collectionViewModel | 387 [self.collectionViewController.collectionViewModel |
| 418 setFooter:footer | 388 setFooter:footer |
| 419 forSectionWithIdentifier:sectionIdentifier]; | 389 forSectionWithIdentifier:sectionIdentifier]; |
| 420 } | 390 } |
| 421 } | 391 } |
| 422 | 392 |
| 423 // Adds the header corresponding to |sectionInfo| to the section. | 393 // Adds the header corresponding to |sectionInfo| to the section if there is |
| 424 - (void)addHeader:(ContentSuggestionsSectionInformation*)sectionInfo { | 394 // none present and the section info contains a title. |
| 395 - (void)addHeaderIfNeeded:(ContentSuggestionsSectionInformation*)sectionInfo { |
| 425 NSInteger sectionIdentifier = SectionIdentifierForInfo(sectionInfo); | 396 NSInteger sectionIdentifier = SectionIdentifierForInfo(sectionInfo); |
| 426 | 397 |
| 427 if (![self.collectionViewController.collectionViewModel | 398 if (![self.collectionViewController.collectionViewModel |
| 428 headerForSectionWithIdentifier:sectionIdentifier]) { | 399 headerForSectionWithIdentifier:sectionIdentifier] && |
| 400 sectionInfo.title) { |
| 429 CollectionViewTextItem* header = | 401 CollectionViewTextItem* header = |
| 430 [[CollectionViewTextItem alloc] initWithType:ItemTypeHeader]; | 402 [[CollectionViewTextItem alloc] initWithType:ItemTypeHeader]; |
| 431 header.text = sectionInfo.title; | 403 header.text = sectionInfo.title; |
| 432 [self.collectionViewController.collectionViewModel | 404 [self.collectionViewController.collectionViewModel |
| 433 setHeader:header | 405 setHeader:header |
| 434 forSectionWithIdentifier:sectionIdentifier]; | 406 forSectionWithIdentifier:sectionIdentifier]; |
| 435 } | 407 } |
| 436 } | 408 } |
| 437 | 409 |
| 438 // Resets the models, removing the current CollectionViewItem and the | 410 // Resets the models, removing the current CollectionViewItem and the |
| 439 // SectionInfo. | 411 // SectionInfo. |
| 440 - (void)resetModels { | 412 - (void)resetModels { |
| 441 [self.collectionViewController loadModel]; | 413 [self.collectionViewController loadModel]; |
| 442 self.sectionInfoBySectionIdentifier = [[NSMutableDictionary alloc] init]; | 414 self.sectionInfoBySectionIdentifier = [[NSMutableDictionary alloc] init]; |
| 443 } | 415 } |
| 444 | 416 |
| 417 // Fetches the favicon attributes for the |item|. |
| 418 - (void)fetchFaviconForItem:(CSCollectionViewItem*)item { |
| 419 __weak ContentSuggestionsCollectionUpdater* weakSelf = self; |
| 420 __weak CSCollectionViewItem* weakItem = item; |
| 421 |
| 422 [self.dataSource |
| 423 fetchFaviconAttributesForItem:item |
| 424 completion:^(FaviconAttributes* attributes) { |
| 425 ContentSuggestionsCollectionUpdater* strongSelf = |
| 426 weakSelf; |
| 427 CSCollectionViewItem* strongItem = weakItem; |
| 428 if (!strongSelf || !strongItem) { |
| 429 return; |
| 430 } |
| 431 |
| 432 strongItem.attributes = attributes; |
| 433 [strongSelf.collectionViewController |
| 434 reconfigureCellsForItems:@[ strongItem ]]; |
| 435 |
| 436 [strongSelf fetchFaviconImageForItem:strongItem]; |
| 437 }]; |
| 438 } |
| 439 |
| 440 // Fetches the favicon image for the |item|. |
| 441 - (void)fetchFaviconImageForItem:(CSCollectionViewItem*)item { |
| 442 __weak ContentSuggestionsCollectionUpdater* weakSelf = self; |
| 443 __weak CSCollectionViewItem* weakItem = item; |
| 444 |
| 445 [self.dataSource |
| 446 fetchFaviconImageForItem:item |
| 447 completion:^(UIImage* image) { |
| 448 ContentSuggestionsCollectionUpdater* strongSelf = |
| 449 weakSelf; |
| 450 CSCollectionViewItem* strongItem = weakItem; |
| 451 if (!strongSelf || !strongItem || !image) { |
| 452 return; |
| 453 } |
| 454 |
| 455 strongItem.attributes = |
| 456 [FaviconAttributes attributesWithImage:image]; |
| 457 [strongSelf.collectionViewController |
| 458 reconfigureCellsForItems:@[ strongItem ]]; |
| 459 }]; |
| 460 } |
| 461 |
| 445 // Runs the additional action for the section identified by |sectionInfo|. | 462 // Runs the additional action for the section identified by |sectionInfo|. |
| 446 - (void)runAdditionalActionForSection: | 463 - (void)runAdditionalActionForSection: |
| 447 (ContentSuggestionsSectionInformation*)sectionInfo { | 464 (ContentSuggestionsSectionInformation*)sectionInfo { |
| 448 SectionIdentifier sectionIdentifier = SectionIdentifierForInfo(sectionInfo); | 465 SectionIdentifier sectionIdentifier = SectionIdentifierForInfo(sectionInfo); |
| 449 | 466 |
| 467 // TODO(crbug.com/721229): Start spinner. |
| 468 |
| 450 NSMutableArray<ContentSuggestionIdentifier*>* knownSuggestionIdentifiers = | 469 NSMutableArray<ContentSuggestionIdentifier*>* knownSuggestionIdentifiers = |
| 451 [NSMutableArray array]; | 470 [NSMutableArray array]; |
| 452 | 471 |
| 453 NSArray<CSCollectionViewItem*>* knownSuggestions = | 472 NSArray<CSCollectionViewItem*>* knownSuggestions = |
| 454 [self.collectionViewController.collectionViewModel | 473 [self.collectionViewController.collectionViewModel |
| 455 itemsInSectionWithIdentifier:sectionIdentifier]; | 474 itemsInSectionWithIdentifier:sectionIdentifier]; |
| 456 for (CSCollectionViewItem* suggestion in knownSuggestions) { | 475 for (CSCollectionViewItem* suggestion in knownSuggestions) { |
| 457 if (suggestion.type != ItemTypeEmpty) { | 476 if (suggestion.type != ItemTypeEmpty) { |
| 458 [knownSuggestionIdentifiers addObject:suggestion.suggestionIdentifier]; | 477 [knownSuggestionIdentifiers addObject:suggestion.suggestionIdentifier]; |
| 459 } | 478 } |
| 460 } | 479 } |
| 461 | 480 |
| 462 __weak ContentSuggestionsCollectionUpdater* weakSelf = self; | 481 __weak ContentSuggestionsCollectionUpdater* weakSelf = self; |
| 463 [self.dataSource | 482 [self.dataSource |
| 464 fetchMoreSuggestionsKnowing:knownSuggestionIdentifiers | 483 fetchMoreSuggestionsKnowing:knownSuggestionIdentifiers |
| 465 fromSectionInfo:sectionInfo | 484 fromSectionInfo:sectionInfo |
| 466 callback:^(NSArray<ContentSuggestion*>* suggestions) { | 485 callback:^( |
| 467 [weakSelf moreSuggestionsFetched:suggestions]; | 486 NSArray<CSCollectionViewItem*>* suggestions) { |
| 487 [weakSelf moreSuggestionsFetched:suggestions |
| 488 inSectionInfo:sectionInfo]; |
| 468 }]; | 489 }]; |
| 469 } | 490 } |
| 470 | 491 |
| 471 // Adds the |suggestions| to the collection view. All the suggestions must have | 492 // Adds the |suggestions| to the collection view. All the suggestions must have |
| 472 // the same sectionInfo. | 493 // the same sectionInfo. |
| 473 - (void)moreSuggestionsFetched:(NSArray<ContentSuggestion*>*)suggestions { | 494 - (void)moreSuggestionsFetched:(NSArray<CSCollectionViewItem*>*)suggestions |
| 474 [self.collectionViewController addSuggestions:suggestions]; | 495 inSectionInfo: |
| 496 (ContentSuggestionsSectionInformation*)sectionInfo { |
| 497 if (suggestions) { |
| 498 [self.collectionViewController addSuggestions:suggestions |
| 499 toSectionInfo:sectionInfo]; |
| 500 } |
| 501 // TODO(crbug.com/721229):Stop spinner. |
| 475 } | 502 } |
| 476 | 503 |
| 477 // Returns a item to be displayed when the section identified by |sectionInfo| | 504 // Returns a item to be displayed when the section identified by |sectionInfo| |
| 478 // is empty. | 505 // is empty. |
| 479 - (CSCollectionViewItem*)emptyItemForSectionInfo: | 506 - (CSCollectionViewItem*)emptyItemForSectionInfo: |
| 480 (ContentSuggestionsSectionInformation*)sectionInfo { | 507 (ContentSuggestionsSectionInformation*)sectionInfo { |
| 481 ContentSuggestionsTextItem* item = | 508 ContentSuggestionsTextItem* item = |
| 482 [[ContentSuggestionsTextItem alloc] initWithType:ItemTypeEmpty]; | 509 [[ContentSuggestionsTextItem alloc] initWithType:ItemTypeEmpty]; |
| 483 item.text = l10n_util::GetNSString(IDS_NTP_TITLE_NO_SUGGESTIONS); | 510 item.text = l10n_util::GetNSString(IDS_NTP_TITLE_NO_SUGGESTIONS); |
| 484 item.detailText = sectionInfo.emptyText; | 511 item.detailText = sectionInfo.emptyText; |
| 485 | 512 |
| 486 return item; | 513 return item; |
| 487 } | 514 } |
| 488 | 515 |
| 489 // Returns a suggestion item built with the |suggestion|. | |
| 490 - (ContentSuggestionsItem*)suggestionItemForSuggestion: | |
| 491 (ContentSuggestion*)suggestion { | |
| 492 ContentSuggestionsItem* suggestionItem = [[ContentSuggestionsItem alloc] | |
| 493 initWithType:ItemTypeForContentSuggestionType(suggestion.type) | |
| 494 title:suggestion.title | |
| 495 subtitle:suggestion.text | |
| 496 delegate:self | |
| 497 url:suggestion.url]; | |
| 498 | |
| 499 suggestionItem.publisher = suggestion.publisher; | |
| 500 suggestionItem.publishDate = suggestion.publishDate; | |
| 501 suggestionItem.availableOffline = suggestion.availableOffline; | |
| 502 | |
| 503 suggestionItem.suggestionIdentifier = suggestion.suggestionIdentifier; | |
| 504 | |
| 505 return suggestionItem; | |
| 506 } | |
| 507 | |
| 508 // Fetches the favicon associated with the |URL|, call the |callback| with the | |
| 509 // attributes then reconfigure the |item|. | |
| 510 - (void)fetchFaviconForItem:(CSCollectionViewItem*)item | |
| 511 withURL:(const GURL&)URL | |
| 512 callback:(void (^)(FaviconAttributes*))callback { | |
| 513 if (!callback) | |
| 514 return; | |
| 515 | |
| 516 __weak ContentSuggestionsCollectionUpdater* weakSelf = self; | |
| 517 __weak CSCollectionViewItem* weakItem = item; | |
| 518 void (^completionBlock)(FaviconAttributes* attributes) = | |
| 519 ^(FaviconAttributes* attributes) { | |
| 520 CSCollectionViewItem* strongItem = weakItem; | |
| 521 ContentSuggestionsCollectionUpdater* strongSelf = weakSelf; | |
| 522 if (!strongSelf || !strongItem) { | |
| 523 return; | |
| 524 } | |
| 525 | |
| 526 callback(attributes); | |
| 527 | |
| 528 [strongSelf.collectionViewController | |
| 529 reconfigureCellsForItems:@[ strongItem ]]; | |
| 530 }; | |
| 531 | |
| 532 [self.dataSource fetchFaviconAttributesForURL:URL completion:completionBlock]; | |
| 533 } | |
| 534 | |
| 535 // Adds |item| to |sectionIdentifier| section of the model of the | 516 // Adds |item| to |sectionIdentifier| section of the model of the |
| 536 // CollectionView. Returns the IndexPath of the newly added item. | 517 // CollectionView. Returns the IndexPath of the newly added item. |
| 537 - (NSIndexPath*)addItem:(CSCollectionViewItem*)item | 518 - (NSIndexPath*)addItem:(CSCollectionViewItem*)item |
| 538 toSectionWithIdentifier:(NSInteger)sectionIdentifier { | 519 toSectionWithIdentifier:(NSInteger)sectionIdentifier { |
| 539 CSCollectionViewModel* model = | 520 CSCollectionViewModel* model = |
| 540 self.collectionViewController.collectionViewModel; | 521 self.collectionViewController.collectionViewModel; |
| 541 NSInteger section = [model sectionForSectionIdentifier:sectionIdentifier]; | 522 NSInteger section = [model sectionForSectionIdentifier:sectionIdentifier]; |
| 542 NSInteger itemNumber = [model numberOfItemsInSection:section]; | 523 NSInteger itemNumber = [model numberOfItemsInSection:section]; |
| 543 [model addItem:item toSectionWithIdentifier:sectionIdentifier]; | 524 [model addItem:item toSectionWithIdentifier:sectionIdentifier]; |
| 544 | 525 |
| 545 return [NSIndexPath indexPathForItem:itemNumber inSection:section]; | 526 return [NSIndexPath indexPathForItem:itemNumber inSection:section]; |
| 546 } | 527 } |
| 547 | 528 |
| 548 @end | 529 @end |
| OLD | NEW |