mirror of
https://github.com/viq/NewsBlur.git
synced 2025-09-18 21:43:31 +00:00
Merge branch 'dejal'
* dejal: #1162 (widget) #1271 (crash on feed list load)
This commit is contained in:
commit
e881e48810
5 changed files with 152 additions and 63 deletions
|
@ -313,7 +313,7 @@ SFSafariViewControllerDelegate> {
|
|||
- (void)setHiddenPreferencesAnimated:(BOOL)animated;
|
||||
- (void)resizePreviewSize;
|
||||
- (void)resizeFontSize;
|
||||
- (void)popToRoot;
|
||||
- (void)popToRootWithCompletion:(void (^)(void))completion;
|
||||
|
||||
- (void)showMoveSite;
|
||||
- (void)openTrainSite;
|
||||
|
|
|
@ -515,8 +515,9 @@
|
|||
}];
|
||||
} else if ([action isEqualToString:@"VIEW_STORY_IDENTIFIER"] ||
|
||||
[action isEqualToString:@"com.apple.UNNotificationDefaultActionIdentifier"]) {
|
||||
[self popToRoot];
|
||||
[self loadFeed:feedIdStr withStory:storyHash animated:NO];
|
||||
[self popToRootWithCompletion:^{
|
||||
[self loadFeed:feedIdStr withStory:storyHash animated:NO];
|
||||
}];
|
||||
if (completionHandler) completionHandler();
|
||||
} else if ([action isEqualToString:@"DISMISS_IDENTIFIER"]) {
|
||||
if (completionHandler) completionHandler();
|
||||
|
@ -561,8 +562,9 @@
|
|||
NSString *error = query[@"error"];
|
||||
|
||||
if (error.length) {
|
||||
[self popToRoot];
|
||||
[self showWidgetSites];
|
||||
[self popToRootWithCompletion:^{
|
||||
[self showWidgetSites];
|
||||
}];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
@ -571,16 +573,16 @@
|
|||
return NO;
|
||||
}
|
||||
|
||||
[self popToRoot];
|
||||
|
||||
self.inFindingStoryMode = YES;
|
||||
[storiesCollection reset];
|
||||
storiesCollection.isRiverView = YES;
|
||||
|
||||
self.tryFeedStoryId = storyHash;
|
||||
storiesCollection.activeFolder = @"everything";
|
||||
|
||||
[self loadRiverFeedDetailView:self.feedDetailViewController withFolder:storiesCollection.activeFolder];
|
||||
[self popToRootWithCompletion:^{
|
||||
self.inFindingStoryMode = YES;
|
||||
[storiesCollection reset];
|
||||
storiesCollection.isRiverView = YES;
|
||||
|
||||
self.tryFeedStoryId = storyHash;
|
||||
storiesCollection.activeFolder = @"everything";
|
||||
|
||||
[self loadRiverFeedDetailView:self.feedDetailViewController withFolder:storiesCollection.activeFolder];
|
||||
}];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
@ -750,14 +752,25 @@
|
|||
[feedsViewController resizeFontSize];
|
||||
}
|
||||
|
||||
- (void)popToRoot {
|
||||
- (void)popToRootWithCompletion:(void (^)(void))completion {
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||
if (completion) {
|
||||
[CATransaction begin];
|
||||
[CATransaction setCompletionBlock:completion];
|
||||
}
|
||||
|
||||
[masterContainerViewController dismissViewControllerAnimated:NO completion:nil];
|
||||
[self.navigationController
|
||||
popToViewController:[self.navigationController.viewControllers objectAtIndex:0]
|
||||
animated:YES];
|
||||
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:0] animated:YES];
|
||||
|
||||
if (completion) {
|
||||
[CATransaction commit];
|
||||
}
|
||||
} else {
|
||||
[self.navigationController popToRootViewControllerAnimated:NO];
|
||||
|
||||
if (completion) {
|
||||
completion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1170,12 +1170,20 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
|
|||
NSDictionary *resultsFeeds = results[@"feeds"];
|
||||
|
||||
[resultsFeeds enumerateKeysAndObjectsUsingBlock:^(id key, NSDictionary *obj, BOOL *stop) {
|
||||
NSString *identifier = [NSString stringWithFormat:@"%@", key];
|
||||
NSString *title = obj[@"feed_title"];
|
||||
NSMutableDictionary *feed = [NSMutableDictionary dictionary];
|
||||
NSString *fade = obj[@"favicon_fade"];
|
||||
NSString *color = obj[@"favicon_color"];
|
||||
|
||||
NSDictionary *feed = @{@"id" : identifier, @"feed_title" : title, @"favicon_fade": fade, @"favicon_color" : color};
|
||||
feed[@"id"] = [NSString stringWithFormat:@"%@", key];
|
||||
feed[@"feed_title"] = [NSString stringWithFormat:@"%@", obj[@"feed_title"]];
|
||||
|
||||
if (fade != nil && ![fade isKindOfClass:[NSNull class]]) {
|
||||
feed[@"favicon_fade"] = fade;
|
||||
}
|
||||
|
||||
if (color != nil && ![color isKindOfClass:[NSNull class]]) {
|
||||
feed[@"favicon_color"] = color;
|
||||
}
|
||||
|
||||
[feeds addObject:feed];
|
||||
}];
|
||||
|
|
|
@ -50,10 +50,13 @@ class WidgetExtensionViewController: UITableViewController, NCWidgetProviding {
|
|||
static let feeds = "widget:feeds_array"
|
||||
static let widgetFolder = "Widget"
|
||||
static let storiesFilename = "Stories.json"
|
||||
static let feedImagesFilename = "Feed Images"
|
||||
static let storyImagesFilename = "Story Images"
|
||||
static let imageExtension = "png"
|
||||
static let limit = 5
|
||||
static let defaultRowHeight: CGFloat = 110
|
||||
static let storyImageSize: CGFloat = 64 * 3
|
||||
static let storyImageLimit: CGFloat = 200
|
||||
}
|
||||
|
||||
// MARK: - View lifecycle
|
||||
|
@ -85,7 +88,8 @@ class WidgetExtensionViewController: UITableViewController, NCWidgetProviding {
|
|||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
|
||||
imageCache.removeAll()
|
||||
feedImageCache.removeAll()
|
||||
storyImageCache.removeAll()
|
||||
}
|
||||
|
||||
// MARK: - Widget provider protocol
|
||||
|
@ -97,7 +101,8 @@ class WidgetExtensionViewController: UITableViewController, NCWidgetProviding {
|
|||
private var widgetCompletion: WidgetCompletion?
|
||||
|
||||
private typealias ImageDictionary = [String : UIImage]
|
||||
private var imageCache = ImageDictionary()
|
||||
private var feedImageCache = ImageDictionary()
|
||||
private var storyImageCache = ImageDictionary()
|
||||
|
||||
private typealias LoaderDictionary = [String : Loader]
|
||||
private var loaders = LoaderDictionary()
|
||||
|
@ -333,6 +338,7 @@ private extension WidgetExtensionViewController {
|
|||
}
|
||||
|
||||
saveStories()
|
||||
flushStoryImages()
|
||||
|
||||
if stories.isEmpty, error == nil {
|
||||
error = .noStories
|
||||
|
@ -363,8 +369,16 @@ private extension WidgetExtensionViewController {
|
|||
return widgetFolderURL?.appendingPathComponent(Constant.storiesFilename)
|
||||
}
|
||||
|
||||
func createWidgetFolder() {
|
||||
guard let url = widgetFolderURL else {
|
||||
var feedImagesURL: URL? {
|
||||
return widgetFolderURL?.appendingPathComponent(Constant.feedImagesFilename)
|
||||
}
|
||||
|
||||
var storyImagesURL: URL? {
|
||||
return widgetFolderURL?.appendingPathComponent(Constant.storyImagesFilename)
|
||||
}
|
||||
|
||||
func createWidgetFolder(url: URL? = nil) {
|
||||
guard let url = url ?? widgetFolderURL else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -423,41 +437,22 @@ private extension WidgetExtensionViewController {
|
|||
}
|
||||
}
|
||||
|
||||
func cachedImage(for identifier: String) -> UIImage? {
|
||||
if let image = imageCache[identifier] {
|
||||
return image
|
||||
}
|
||||
|
||||
guard let folderURL = widgetFolderURL else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
let imageURL = folderURL.appendingPathComponent(identifier).appendingPathExtension(Constant.imageExtension)
|
||||
let data = try Data(contentsOf: imageURL)
|
||||
|
||||
guard let image = UIImage(data: data) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
imageCache[identifier] = image
|
||||
|
||||
return image
|
||||
} catch {
|
||||
print("Image error: \(error)")
|
||||
}
|
||||
|
||||
return nil
|
||||
func save(feedImage: UIImage, for identifier: String) {
|
||||
feedImageCache[identifier] = feedImage
|
||||
save(image: feedImage, to: feedImagesURL, for: identifier)
|
||||
}
|
||||
|
||||
func save(image: UIImage, for identifier: String) {
|
||||
guard let folderURL = widgetFolderURL else {
|
||||
func save(storyImage: UIImage, for identifier: String) {
|
||||
storyImageCache[identifier] = storyImage
|
||||
save(image: storyImage, to: storyImagesURL, for: identifier)
|
||||
}
|
||||
|
||||
func save(image: UIImage, to folderURL: URL?, for identifier: String) {
|
||||
guard let folderURL = folderURL else {
|
||||
return
|
||||
}
|
||||
|
||||
imageCache[identifier] = image
|
||||
|
||||
createWidgetFolder()
|
||||
createWidgetFolder(url: folderURL)
|
||||
|
||||
do {
|
||||
let imageURL = folderURL.appendingPathComponent(identifier).appendingPathExtension(Constant.imageExtension)
|
||||
|
@ -468,6 +463,30 @@ private extension WidgetExtensionViewController {
|
|||
}
|
||||
}
|
||||
|
||||
func flushStoryImages() {
|
||||
guard let folderURL = storyImagesURL else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let manager = FileManager.default
|
||||
let contents = try manager.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: [], options: .skipsHiddenFiles)
|
||||
|
||||
for imageURL in contents {
|
||||
let identifier = imageURL.deletingPathExtension().lastPathComponent
|
||||
|
||||
if stories.contains(where: { $0.id == identifier }) {
|
||||
continue
|
||||
}
|
||||
|
||||
try manager.removeItem(at: imageURL)
|
||||
storyImageCache[identifier] = nil
|
||||
}
|
||||
} catch {
|
||||
print("Flush story images error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
typealias ImageCompletion = (UIImage?, String?) -> Void
|
||||
|
||||
func feedImage(for feed: String, completion: @escaping ImageCompletion) {
|
||||
|
@ -476,7 +495,7 @@ private extension WidgetExtensionViewController {
|
|||
return
|
||||
}
|
||||
|
||||
if let image = cachedImage(for: feed) {
|
||||
if let image = cachedFeedImage(for: feed) {
|
||||
completion(image, feed)
|
||||
return
|
||||
}
|
||||
|
@ -497,7 +516,7 @@ private extension WidgetExtensionViewController {
|
|||
return
|
||||
}
|
||||
|
||||
self.save(image: image, for: feed)
|
||||
self.save(feedImage: image, for: feed)
|
||||
completion(image, feed)
|
||||
case .failure:
|
||||
completion(nil, feed)
|
||||
|
@ -512,7 +531,7 @@ private extension WidgetExtensionViewController {
|
|||
return
|
||||
}
|
||||
|
||||
if let image = cachedImage(for: identifier) {
|
||||
if let image = cachedStoryImage(for: identifier) {
|
||||
completion(image, identifier)
|
||||
return
|
||||
}
|
||||
|
@ -532,7 +551,7 @@ private extension WidgetExtensionViewController {
|
|||
|
||||
let scaledImage = self.scale(image: loadedImage)
|
||||
|
||||
self.save(image: scaledImage, for: identifier)
|
||||
self.save(storyImage: scaledImage, for: identifier)
|
||||
completion(scaledImage, identifier)
|
||||
case .failure:
|
||||
completion(nil, identifier)
|
||||
|
@ -541,10 +560,59 @@ private extension WidgetExtensionViewController {
|
|||
}
|
||||
}
|
||||
|
||||
func cachedFeedImage(for feed: String) -> UIImage? {
|
||||
if let image = feedImageCache[feed] {
|
||||
return image
|
||||
}
|
||||
|
||||
guard let image = loadCachedImage(folderURL: feedImagesURL, identifier: feed) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
feedImageCache[feed] = image
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
func cachedStoryImage(for identifier: String) -> UIImage? {
|
||||
if let image = storyImageCache[identifier] {
|
||||
return image
|
||||
}
|
||||
|
||||
guard let image = loadCachedImage(folderURL: storyImagesURL, identifier: identifier) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
storyImageCache[identifier] = image
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
func loadCachedImage(folderURL: URL?, identifier: String) -> UIImage? {
|
||||
guard let folderURL = folderURL else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
let imageURL = folderURL.appendingPathComponent(identifier).appendingPathExtension(Constant.imageExtension)
|
||||
let data = try Data(contentsOf: imageURL)
|
||||
|
||||
guard let image = UIImage(data: data) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return image
|
||||
} catch {
|
||||
print("Image error: \(error)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func scale(image: UIImage) -> UIImage {
|
||||
let oldSize = image.size
|
||||
|
||||
guard oldSize.width > Constant.storyImageSize || oldSize.height > Constant.storyImageSize else {
|
||||
guard oldSize.width > Constant.storyImageLimit || oldSize.height > Constant.storyImageLimit else {
|
||||
return image
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ struct Story: Codable, Identifiable {
|
|||
static let author = "story_authors"
|
||||
static let title = "story_title"
|
||||
static let content = "story_content"
|
||||
static let imageURLs = "image_urls"
|
||||
static let imageURLs = "secure_image_thumbnails"
|
||||
}
|
||||
|
||||
/// Initializer from a dictionary.
|
||||
|
@ -56,7 +56,7 @@ struct Story: Codable, Identifiable {
|
|||
title = dictionary[DictionaryKeys.title] as? String ?? ""
|
||||
content = dictionary[DictionaryKeys.content] as? String ?? ""
|
||||
|
||||
if let images = dictionary[DictionaryKeys.imageURLs] as? [String], let first = images.first {
|
||||
if let images = dictionary[DictionaryKeys.imageURLs] as? [String : String], let first = images.values.first {
|
||||
imageURL = URL(string: first)
|
||||
} else {
|
||||
imageURL = nil
|
||||
|
|
Loading…
Add table
Reference in a new issue