Skip to content

Commit

Permalink
Merge pull request #4775 from wikimedia/image-rec-explore-card-2
Browse files Browse the repository at this point in the history
Image recommendations - Explore feed card (Part 2)
  • Loading branch information
mazevedofs authored Mar 22, 2024
2 parents bc4d48f + 3c73be1 commit a4e9bae
Show file tree
Hide file tree
Showing 24 changed files with 264 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ struct WKImageRecommendationsView: View {
@ObservedObject var appEnvironment = WKAppEnvironment.current
@ObservedObject var viewModel: WKImageRecommendationsViewModel
let viewArticleAction: (String) -> Void

var isRTL: Bool {
return viewModel.semanticContentAttribute == .forceRightToLeft
}

var sizeClassPadding: CGFloat {
horizontalSizeClass == .regular ? 64 : 16
Expand All @@ -23,6 +27,7 @@ struct WKImageRecommendationsView: View {
VStack {
HStack {
WKArticleSummaryView(articleSummary: articleSummary)
.environment(\.layoutDirection, isRTL ? .rightToLeft : .leftToRight)
}
Spacer()
.frame(height: 19)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import WKData
import Combine
import UIKit

public final class WKImageRecommendationsViewModel: ObservableObject {

Expand Down Expand Up @@ -69,6 +70,7 @@ public final class WKImageRecommendationsViewModel: ObservableObject {
// MARK: - Properties

let project: WKProject
let semanticContentAttribute: UISemanticContentAttribute
let localizedStrings: LocalizedStrings

private(set) var imageRecommendations: [ImageRecommendation] = []
Expand All @@ -83,8 +85,9 @@ public final class WKImageRecommendationsViewModel: ObservableObject {

// MARK: - Lifecycle

public init(project: WKProject, localizedStrings: LocalizedStrings) {
public init(project: WKProject, semanticContentAttribute: UISemanticContentAttribute, localizedStrings: LocalizedStrings) {
self.project = project
self.semanticContentAttribute = semanticContentAttribute
self.localizedStrings = localizedStrings
self.growthTasksDataController = WKGrowthTasksDataController(project: project)
self.articleSummaryDataController = WKArticleSummaryDataController()
Expand All @@ -107,9 +110,7 @@ public final class WKImageRecommendationsViewModel: ObservableObject {
}

loading = true

growthTasksDataController.getImageRecommendationsCombined { [weak self] result in

guard let self else {
completion()
return
Expand Down
2 changes: 1 addition & 1 deletion Components/Sources/Components/Style/WKFont.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public enum WKFont {
case italicsGeorgiaHeadline
case boldItalicsGeorgiaHeadline

static func `for`(_ font: WKFont, compatibleWith traitCollection: UITraitCollection = WKAppEnvironment.current.traitCollection) -> UIFont {
public static func `for`(_ font: WKFont, compatibleWith traitCollection: UITraitCollection = WKAppEnvironment.current.traitCollection) -> UIFont {
switch font {
case .headline:
return UIFont.preferredFont(forTextStyle: .headline, compatibleWith: traitCollection)
Expand Down
3 changes: 3 additions & 0 deletions Components/Sources/Components/Style/WKIcon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public enum WKSFSymbolIcon {
case link
case curlybraces
case photo
case addPhoto
case docTextMagnifyingGlass
case magnifyingGlass
case listBullet
Expand Down Expand Up @@ -105,6 +106,8 @@ public enum WKSFSymbolIcon {
image = UIImage(systemName: "curlybraces", withConfiguration: configuration)
case .photo:
image = UIImage(systemName: "photo", withConfiguration: configuration)
case .addPhoto:
image = UIImage(systemName: "photo.badge.plus", withConfiguration: configuration)
case .docTextMagnifyingGlass:
image = UIImage(systemName: "doc.text.magnifyingglass", withConfiguration: configuration)
case .magnifyingGlass:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class WKImageRecommendationsViewModelTests: XCTestCase {
}

func testFetchInitialImageRecommendations() throws {
let viewModel = WKImageRecommendationsViewModel(project: csProject, localizedStrings: localizedStrings)
let viewModel = WKImageRecommendationsViewModel(project: csProject, semanticContentAttribute: .forceLeftToRight, localizedStrings: localizedStrings)

let expectation = XCTestExpectation(description: "Fetch Image Recommendations")

Expand All @@ -30,7 +30,7 @@ final class WKImageRecommendationsViewModelTests: XCTestCase {
}

func testFetchNextImageRecommendation() throws {
let viewModel = WKImageRecommendationsViewModel(project: csProject, localizedStrings: localizedStrings)
let viewModel = WKImageRecommendationsViewModel(project: csProject, semanticContentAttribute: .forceLeftToRight, localizedStrings: localizedStrings)

let expectation1 = XCTestExpectation(description: "Fetch Image Recommendations")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

public final class WKGrowthTasksDataController {
@objc public final class WKGrowthTasksDataController: NSObject {

private var service = WKDataEnvironment.current.mediaWikiService
let project: WKProject
Expand Down Expand Up @@ -72,6 +72,7 @@ public final class WKGrowthTasksDataController {
}

fileprivate func getGrowthAPIImageSuggestions(for page: WKImageRecommendationAPIResponse.Page) -> [WKImageRecommendation.GrowthImageSuggestionData] {

var suggestions: [WKImageRecommendation.GrowthImageSuggestionData] = []

for item in page.growthimagesuggestiondata ?? [] {
Expand Down Expand Up @@ -115,3 +116,25 @@ public final class WKGrowthTasksDataController {
public enum WKGrowthTaskType: String {
case imageRecommendation = "image-recommendation"
}

// MARK: Objective-C Helpers

public extension WKGrowthTasksDataController {

@objc convenience init(languageCode: String) {
let language = WKLanguage(languageCode: languageCode, languageVariantCode: nil)
self.init(project: WKProject.wikipedia(language))
}

@objc func hasImageRecommendations(completion: @escaping (Bool) -> Void) {
getImageRecommendationsCombined { result in
switch result {
case .success(let pages):
let pagesWithSuggestions = pages.filter { !($0.growthimagesuggestiondata ?? []).isEmpty }
completion(!pagesWithSuggestions.isEmpty)
case .failure:
completion(false)
}
}
}
}
1 change: 1 addition & 0 deletions WMF Framework/WMF.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ FOUNDATION_EXPORT const unsigned char WMFVersionString[];
#import <WMF/WMFFeedContentSource.h>
#import <WMF/WMFRandomContentSource.h>
#import <WMF/WMFAnnouncementsContentSource.h>
#import <WMF/WMFSuggestedEditsContentSource.h>

#import <WMF/WMFFeedContentFetcher.h>
#import <WMF/WMFFeedDayResponse.h>
Expand Down
19 changes: 8 additions & 11 deletions WMF Framework/WMFContentGroup+Extensions.m
Original file line number Diff line number Diff line change
Expand Up @@ -175,35 +175,32 @@ - (void)updateDailySortPriorityWithSortOrderByContentLanguageCode:(nullable NSDi
case WMFContentGroupKindFeaturedArticle:
updatedDailySortPriority = contentLanguageSortOrder + 4;
break;
case WMFContentGroupKindSuggestedEdits:
updatedDailySortPriority = contentLanguageSortOrder + 5;
break;
case WMFContentGroupKindTopRead:
updatedDailySortPriority = contentLanguageSortOrder + 6;
updatedDailySortPriority = contentLanguageSortOrder + 5;
break;
case WMFContentGroupKindNews:
updatedDailySortPriority = contentLanguageSortOrder + 7;
updatedDailySortPriority = contentLanguageSortOrder + 6;
break;
case WMFContentGroupKindNotification:
updatedDailySortPriority = -1;
break;
case WMFContentGroupKindPictureOfTheDay:
updatedDailySortPriority = 9;
updatedDailySortPriority = 8;
break;
case WMFContentGroupKindOnThisDay:
updatedDailySortPriority = contentLanguageSortOrder + 10;
updatedDailySortPriority = contentLanguageSortOrder + 9;
break;
case WMFContentGroupKindLocationPlaceholder:
updatedDailySortPriority = contentLanguageSortOrder + 11;
updatedDailySortPriority = contentLanguageSortOrder + 10;
break;
case WMFContentGroupKindLocation:
updatedDailySortPriority = contentLanguageSortOrder + 12;
updatedDailySortPriority = contentLanguageSortOrder + 11;
break;
case WMFContentGroupKindRandom:
updatedDailySortPriority = contentLanguageSortOrder + 13;
updatedDailySortPriority = contentLanguageSortOrder + 12;
break;
case WMFContentGroupKindMainPage:
updatedDailySortPriority = contentLanguageSortOrder + 14;
updatedDailySortPriority = contentLanguageSortOrder + 13;
break;
default:
break;
Expand Down
2 changes: 2 additions & 0 deletions WMF Framework/WMFExploreFeedContentController.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ extern const NSInteger WMFExploreFeedMaximumNumberOfDays;

- (void)updateContentSource:(Class)class force:(BOOL)force completion:(nullable dispatch_block_t)completion;

- (NSArray<NSSortDescriptor *> *)exploreFeedSortDescriptors;

// Preferences

/**
Expand Down
27 changes: 26 additions & 1 deletion WMF Framework/WMFExploreFeedContentController.m
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ - (void)setExploreFeedPreferences:(NSDictionary *)exploreFeedPreferences {
return [self.dataStore.languageLinkController.preferredSiteURLs copy];
}

- (NSArray<NSSortDescriptor *> *)exploreFeedSortDescriptors {
NSSortDescriptor *midnightUTCDateSort = [[NSSortDescriptor alloc] initWithKey:@"midnightUTCDate" ascending:NO];
NSSortDescriptor *dailySort = [[NSSortDescriptor alloc] initWithKey:@"dailySortPriority" ascending:YES];
NSSortDescriptor *dateSort = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO];
return @[midnightUTCDateSort, dailySort, dateSort];
}

#pragma mark - Content Sources

- (WMFFeedContentSource *)feedContentSource {
Expand Down Expand Up @@ -699,7 +706,7 @@ - (void)applyExploreFeedPreferencesToAllObjectsInManagedObjectContext:(NSManaged
[self applyExploreFeedPreferencesToObjects:contentGroups inManagedObjectContext:moc];
}

- (void)applyExploreFeedPreferencesToObjects:(id<NSFastEnumeration>)objects inManagedObjectContext:(NSManagedObjectContext *)moc {
- (void)applyExploreFeedPreferencesToObjects:(NSArray<WMFContentGroup *>*)objects inManagedObjectContext:(NSManagedObjectContext *)moc {
NSDictionary *exploreFeedPreferences = [self exploreFeedPreferencesInManagedObjectContext:moc];
for (NSManagedObject *object in objects) {
if (![object isKindOfClass:[WMFContentGroup class]]) {
Expand Down Expand Up @@ -740,6 +747,24 @@ - (void)applyExploreFeedPreferencesToObjects:(id<NSFastEnumeration>)objects inMa
contentGroup.isVisible = isVisible;
}
}

// Do a second pass over objects and shuffle ordering around

NSArray *nonSuggestedEditsGroups = [[objects filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"contentGroupKindInteger != %d && isVisible == true", WMFContentGroupKindSuggestedEdits]] sortedArrayUsingDescriptors:self.exploreFeedSortDescriptors];

WMFContentGroup *suggestedEditsGroup = [objects filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"contentGroupKindInteger == %d && isVisible == true", WMFContentGroupKindSuggestedEdits]].firstObject;

NSMutableArray *finalSorting = [[NSMutableArray alloc] initWithArray:nonSuggestedEditsGroups];
if (suggestedEditsGroup && finalSorting.count > 0) {
// Suggested Edits should always be 2nd card in the feed.
[finalSorting insertObject:suggestedEditsGroup atIndex:1];
}

int index = 0;
for(WMFContentGroup *group in finalSorting) {
group.dailySortPriority = index;
index++;
}
}

- (void)save:(NSManagedObjectContext *)moc {
Expand Down
2 changes: 1 addition & 1 deletion Wikipedia.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@
675175DE276D3B9700CD2974 /* DisappearingCallbackNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675175DB276D3B9700CD2974 /* DisappearingCallbackNavigationController.swift */; };
67540CA924D221E3008B2894 /* LocationManagerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67540CA824D221E3008B2894 /* LocationManagerFactory.swift */; };
675A3D6B2A21600100F9B653 /* MediaWikiFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675A3D6A2A21600100F9B653 /* MediaWikiFetcher.swift */; };
675D875A2B8EA16D007D63F8 /* WMFSuggestedEditsContentSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 675D87582B8EA16D007D63F8 /* WMFSuggestedEditsContentSource.h */; };
675D875A2B8EA16D007D63F8 /* WMFSuggestedEditsContentSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 675D87582B8EA16D007D63F8 /* WMFSuggestedEditsContentSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
675D875B2B8EA16D007D63F8 /* WMFSuggestedEditsContentSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 675D87592B8EA16D007D63F8 /* WMFSuggestedEditsContentSource.m */; };
675D875D2B8FA0FA007D63F8 /* SuggestedEditsExploreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675D875C2B8FA0FA007D63F8 /* SuggestedEditsExploreCell.swift */; };
675D875E2B8FA0FA007D63F8 /* SuggestedEditsExploreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675D875C2B8FA0FA007D63F8 /* SuggestedEditsExploreCell.swift */; };
Expand Down
7 changes: 5 additions & 2 deletions Wikipedia/Code/ExploreCardViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -385,8 +385,11 @@ class ExploreCardViewController: UIViewController, UICollectionViewDataSource, U
return
}

// TODO: Temporary UI
cell.caption = "Testing!"
let languageCode = dataStore.languageLinkController.appLanguage?.languageCode

cell.title = WMFLocalizedString("explore-suggested-edits-image-recs-title", languageCode: languageCode, value: "Add an image", comment: "Title text shown in the image recommendations explore feed card.")
cell.body = WMFLocalizedString("explore-suggested-edits-image-recs-body", languageCode: languageCode, value: "Add suggested images to Wikipedia articles to enhance understanding.", comment: "Body text shown in the image recommendations explore feed card.")
cell.apply(theme: theme)
}

func updateLocationCells() {
Expand Down
4 changes: 2 additions & 2 deletions Wikipedia/Code/ExploreViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ class ExploreViewController: ColumnarCollectionViewController, ExploreCardViewCo
let today = NSDate().wmf_midnightUTCDateFromLocal as Date
let oldestDate = Calendar.current.date(byAdding: .day, value: -WMFExploreFeedMaximumNumberOfDays, to: today) ?? today
fetchRequest.predicate = NSPredicate(format: "isVisible == YES && (placement == NULL || placement == %@) && midnightUTCDate >= %@", "feed", oldestDate as NSDate)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "midnightUTCDate", ascending: false), NSSortDescriptor(key: "dailySortPriority", ascending: true), NSSortDescriptor(key: "date", ascending: false)]
fetchRequest.sortDescriptors = dataStore.feedContentController.exploreFeedSortDescriptors()
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: dataStore.viewContext, sectionNameKeyPath: "midnightUTCDate", cacheName: nil)
fetchedResultsController = frc
let updater = CollectionViewUpdater(fetchedResultsController: frc, collectionView: collectionView)
Expand Down Expand Up @@ -667,7 +667,7 @@ class ExploreViewController: ColumnarCollectionViewController, ExploreCardViewCo
func exploreCardViewController(_ exploreCardViewController: ExploreCardViewController, didSelectItemAtIndexPath indexPath: IndexPath) {
guard
let contentGroup = exploreCardViewController.contentGroup,
let vc = contentGroup.detailViewControllerForPreviewItemAtIndex(indexPath.row, dataStore: dataStore, theme: theme) else {
let vc = contentGroup.detailViewControllerForPreviewItemAtIndex(indexPath.row, dataStore: dataStore, theme: theme, imageRecDelegate: self) else {
return
}

Expand Down
Loading

0 comments on commit a4e9bae

Please sign in to comment.