Javier Cicchelli 9bcdaa697b [Setup] Basic project structure (#1)
This PR contains all the work related to setting up this project as required to implement the [Assignment](https://repo.rock-n-code.com/rock-n-code/deep-linking-assignment/wiki/Assignment) on top, as intended.

To summarise this work:
- [x] created a new **Xcode** project;
- [x] cloned the `Wikipedia` app and inserted it into the **Xcode** project;
- [x] created the `Locations` app and also, its `Libraries` package;
- [x] created the `Shared` package to share dependencies between the apps;
- [x] added a `Makefile` file and implemented some **environment** and **help** commands.

Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Reviewed-on: rock-n-code/deep-linking-assignment#1
2023-04-08 18:37:13 +00:00

324 lines
12 KiB
Objective-C

#import <WMF/WMFArticle+Extensions.h>
#import <WMF/NSURL+WMFLinkParsing.h>
#import <WMF/WMF-Swift.h>
@interface NSManagedObjectContext (WMFArticle_Private)
- (NSUInteger)countOfSavedArticleVariantsWithKey:(nullable NSString *)key error:(NSError **)error;
- (nullable WMFArticle *)savedArticleVariantWithKey:(nullable NSString *)key error:(NSError **)error;
@end
@implementation WMFArticle (Extensions)
- (NSString *)capitalizedWikidataDescription {
return [self.wikidataDescription wmf_stringByCapitalizingFirstCharacterUsingWikipediaLanguageCode:self.URL.wmf_languageCode];
}
- (nullable NSURL *)URL {
NSString *key = self.key;
if (!key) {
return nil;
}
NSURL *value = [NSURL URLWithString:key];
value.wmf_languageVariantCode = self.variant;
return value;
}
- (nullable WMFInMemoryURLKey *)inMemoryKey {
return self.URL.wmf_inMemoryKey;
}
- (BOOL)isAnyVariantSaved {
NSUInteger savedCount = [self.managedObjectContext countOfSavedArticleVariantsWithKey:self.key error:nil];
NSAssert(savedCount < 2, @"More than one article variant marked as saved for key '%@'", self.key);
return savedCount > 0;
}
- (nullable WMFArticle *)savedVariant {
// If the article has a savedDate, it is the saved variant
if (self.savedDate != nil) {
return self;
// If the article does not have a variant, no further checking is needed. Return nil.
} else if (!self.variant) {
return nil;
} else {
return [self.managedObjectContext savedArticleVariantWithKey:self.key error:nil];
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations" // this is the only section of code where the "deprecated" but not really deprecated displayTitleHTMLString should be used
- (void)setDisplayTitleHTML:(NSString *)displayTitleHTML {
self.displayTitleHTMLString = displayTitleHTML;
self.displayTitle = displayTitleHTML.wmf_stringByRemovingHTML;
}
- (NSString *)displayTitleHTML {
return self.displayTitleHTMLString ?: self.displayTitle ?: self.URL.wmf_title ?: @"";
}
#pragma clang diagnostic pop
- (nullable NSURL *)thumbnailURL {
NSString *thumbnailURLString = self.thumbnailURLString;
if (!thumbnailURLString) {
return [self imageURLForWidth:240]; //hardcoded to not rely on UIScreen in a model object
}
return [NSURL URLWithString:thumbnailURLString];
}
+ (nullable NSURL *)imageURLForTargetImageWidth:(NSInteger)width fromImageSource:(NSString *)imageSource withOriginalWidth:(NSInteger)originalWidth {
NSAssert(width > 0, @"Width must be > 0");
if (width <= 0) {
return nil;
}
NSString *lowercasePathExtension = [[imageSource pathExtension] lowercaseString];
if (width >= originalWidth && ![lowercasePathExtension isEqualToString:@"svg"] && ![lowercasePathExtension isEqualToString:@"pdf"]) {
return [NSURL URLWithString:imageSource];
}
return [NSURL URLWithString:WMFChangeImageSourceURLSizePrefix(imageSource, width)];
}
- (nullable NSURL *)imageURLForWidth:(NSInteger)width {
NSAssert(width > 0, @"Width must be > 0");
if (width <= 0) {
return nil;
}
NSString *imageURLString = self.imageURLString;
NSNumber *imageWidth = self.imageWidth;
if (!imageURLString || !imageWidth) {
NSString *thumbnailURLString = self.thumbnailURLString;
if (!thumbnailURLString) {
return nil;
}
NSInteger sizePrefix = WMFParseSizePrefixFromSourceURL(thumbnailURLString);
if (sizePrefix == NSNotFound || width >= sizePrefix) {
return [NSURL URLWithString:thumbnailURLString];
}
return [NSURL URLWithString:WMFChangeImageSourceURLSizePrefix(thumbnailURLString, width)];
}
return [WMFArticle imageURLForTargetImageWidth:width fromImageSource:imageURLString withOriginalWidth:[imageWidth integerValue]];
}
- (void)setThumbnailURL:(NSURL *)thumbnailURL {
self.thumbnailURLString = thumbnailURL.absoluteString;
}
- (NSArray<NSNumber *> *)pageViewsSortedByDate {
return self.pageViews.wmf_pageViewsSortedByDate;
}
- (void)updateViewedDateWithoutTime {
NSDate *viewedDate = self.viewedDate;
if (viewedDate) {
NSCalendar *calendar = [NSCalendar wmf_gregorianCalendar];
NSDateComponents *components = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:viewedDate];
self.viewedDateWithoutTime = [calendar dateFromComponents:components];
} else {
self.viewedDateWithoutTime = nil;
}
}
- (void)updateWithSearchResult:(nullable MWKSearchResult *)searchResult {
if ([searchResult.displayTitleHTML length] > 0) {
self.displayTitleHTML = searchResult.displayTitleHTML;
} else if ([searchResult.displayTitle length] > 0) {
self.displayTitleHTML = searchResult.displayTitle;
}
if ([searchResult.wikidataDescription length] > 0) {
self.wikidataDescription = searchResult.wikidataDescription;
}
if ([searchResult.extract length] > 0) {
self.snippet = searchResult.extract;
}
if (searchResult.thumbnailURL != nil) {
self.thumbnailURL = searchResult.thumbnailURL;
}
if (searchResult.location != nil) {
self.location = searchResult.location;
}
if (searchResult.geoDimension != nil) {
self.geoDimensionNumber = searchResult.geoDimension;
}
if (searchResult.geoType != nil) {
self.geoTypeNumber = searchResult.geoType;
}
}
@end
#pragma mark - NSManagedObjectContext Extensions
@implementation NSManagedObjectContext (WMFArticle)
- (nullable WMFArticle *)fetchArticleWithURL:(nullable NSURL *)articleURL {
return [self fetchArticleWithKey:articleURL.wmf_databaseKey variant:articleURL.wmf_languageVariantCode];
}
- (nullable NSArray<WMFArticle *> *)fetchArticlesWithURL:(nullable NSURL *)url error:(NSError **)error {
return [self fetchArticlesWithKey:url.wmf_databaseKey variant:url.wmf_languageVariantCode error:error];
}
- (nullable NSArray<WMFArticle *> *)fetchArticlesWithKey:(nullable NSString *)key variant:(nullable NSString *)variant error:(NSError **)error {
if (!key) {
return @[];
}
NSFetchRequest *request = [WMFArticle fetchRequest];
request.predicate = [NSPredicate predicateWithFormat:@"key == %@ && variant == %@", key, variant];
return [self executeFetchRequest:request error:error];
}
- (nullable NSArray<WMFArticle *> *)fetchArticlesWithInMemoryURLKeys:(NSArray<WMFInMemoryURLKey *> *)urlKeys error:(NSError **)error {
if (urlKeys.count == 0) {
return @[];
}
NSFetchRequest *request = [WMFArticle fetchRequest];
request.predicate = [self articlePredicateForInMemoryURLKeys:urlKeys];
return [self executeFetchRequest:request error:error];
}
- (NSPredicate *)articlePredicateForInMemoryURLKeys:(NSArray<WMFInMemoryURLKey *> *)urlKeys {
NSMutableArray<NSPredicate *> *subpredicates = [[NSMutableArray alloc] init];
for (WMFInMemoryURLKey *urlKey in urlKeys) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"key == %@ && variant == %@", urlKey.databaseKey, urlKey.languageVariantCode];
[subpredicates addObject:predicate];
}
return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
}
- (nullable WMFArticle *)fetchArticleWithKey:(nullable NSString *)key variant:(nullable NSString *)variant {
if (!key) {
return nil;
}
WMFArticle *article = nil;
NSFetchRequest *request = [WMFArticle fetchRequest];
request.fetchLimit = 1;
request.predicate = [NSPredicate predicateWithFormat:@"key == %@ && variant == %@", key, variant];
article = [[self executeFetchRequest:request error:nil] firstObject];
return article;
}
- (nullable WMFArticle *)fetchArticleWithWikidataID:(nullable NSString *)wikidataID {
if (!wikidataID) {
return nil;
}
WMFArticle *article = nil;
NSFetchRequest *request = [WMFArticle fetchRequest];
request.fetchLimit = 1;
request.predicate = [NSPredicate predicateWithFormat:@"wikidataID == %@", wikidataID];
article = [[self executeFetchRequest:request error:nil] firstObject];
return article;
}
- (nullable WMFArticle *)createArticleWithURL:(nullable NSURL *)url {
return [self createArticleWithKey:url.wmf_databaseKey variant:url.wmf_languageVariantCode];
}
- (nullable WMFArticle *)createArticleWithKey:(nullable NSString *)key variant:(nullable NSString *)variant {
WMFArticle *article = [[WMFArticle alloc] initWithContext:self];
article.key = key;
article.variant = variant;
return article;
}
- (nullable WMFArticle *)fetchOrCreateArticleWithKey:(nullable NSString *)key variant:(nullable NSString *)variant {
if (!key) {
return nil;
}
WMFArticle *article = [self fetchArticleWithKey:key variant:variant];
if (!article) {
article = [self createArticleWithKey:key variant:variant];
}
return article;
}
- (nullable WMFArticle *)fetchOrCreateArticleWithURL:(nullable NSURL *)articleURL {
return [self fetchOrCreateArticleWithKey:articleURL.wmf_databaseKey variant:articleURL.wmf_languageVariantCode];
}
- (nullable WMFArticle *)fetchOrCreateArticleWithURL:(nullable NSURL *)articleURL updatedWithSearchResult:(nullable MWKSearchResult *)searchResult {
NSParameterAssert(articleURL);
WMFArticle *article = [self fetchOrCreateArticleWithURL:articleURL];
[article updateWithSearchResult:searchResult];
return article;
}
- (nullable WMFArticle *)fetchOrCreateArticleWithURL:(nullable NSURL *)articleURL updatedWithFeedPreview:(nullable WMFFeedArticlePreview *)feedPreview pageViews:(nullable NSDictionary<NSDate *, NSNumber *> *)pageViews isFeatured:(BOOL)isFeatured {
NSParameterAssert(articleURL);
if (!articleURL) {
return nil;
}
WMFArticle *article = [self fetchOrCreateArticleWithURL:articleURL];
if (isFeatured) {
WMFFeedArticlePreview *oldFeedPreview = [article feedArticlePreview];
if (![oldFeedPreview isEqual:feedPreview]) {
[SharedContainerCacheClearFeaturedArticleWrapper clearOutFeaturedArticleWidgetCache];
}
}
if ([feedPreview.displayTitleHTML length] > 0) {
article.displayTitleHTML = feedPreview.displayTitleHTML;
} else if ([feedPreview.displayTitle length] > 0) {
article.displayTitleHTML = feedPreview.displayTitle;
}
if ([feedPreview.wikidataDescription length] > 0) {
article.wikidataDescription = feedPreview.wikidataDescription;
}
if ([feedPreview.snippet length] > 0) {
article.snippet = feedPreview.snippet;
}
if (feedPreview.thumbnailURL != nil) {
article.thumbnailURL = feedPreview.thumbnailURL;
}
if (pageViews != nil) {
if (article.pageViews == nil) {
article.pageViews = pageViews;
} else {
article.pageViews = [article.pageViews mtl_dictionaryByAddingEntriesFromDictionary:pageViews];
}
}
if (feedPreview.imageURLString != nil) {
article.imageURLString = feedPreview.imageURLString;
}
if (feedPreview.imageWidth != nil) {
article.imageWidth = feedPreview.imageWidth;
}
if (feedPreview.imageHeight != nil) {
article.imageHeight = feedPreview.imageHeight;
}
return article;
}
@end
@implementation NSManagedObjectContext (WMFArticle_Private)
- (NSUInteger)countOfSavedArticleVariantsWithKey:(nullable NSString *)key error:(NSError **)error {
if (!key) {
return 0;
}
NSFetchRequest *request = [WMFArticle fetchRequest];
request.predicate = [NSPredicate predicateWithFormat:@"key == %@ && savedDate != NULL", key];
NSUInteger count = [self countForFetchRequest:request error:error];
return count == NSNotFound ? 0 : count;
}
- (nullable WMFArticle *)savedArticleVariantWithKey:(nullable NSString *)key error:(NSError **)error {
if (!key) {
return nil;
}
NSFetchRequest *request = [WMFArticle fetchRequest];
request.predicate = [NSPredicate predicateWithFormat:@"key == %@ && savedDate != NULL", key];
return (WMFArticle *)[[self executeFetchRequest:request error:error] firstObject];
}
@end