mirror of
				https://github.com/samuelclay/NewsBlur.git
				synced 2025-11-01 09:09:51 +00:00 
			
		
		
		
	#1720 (Grid view)
- Added support for the Grid layout on iPhone, with one or two columns. Works fairly well. - Bumped up the feed title & date/author font size again. - Now markes as read on scroll when the card is scrolled halfway off the top.
This commit is contained in:
		
							parent
							
								
									eb94e7bc7f
								
							
						
					
					
						commit
						f703d58b94
					
				
					 7 changed files with 87 additions and 46 deletions
				
			
		| 
						 | 
					@ -139,6 +139,11 @@ class DetailViewController: BaseViewController {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    /// Returns `true` if the device is an iPhone, otherwise `false`.
 | 
				
			||||||
 | 
					    @objc var isPhone: Bool {
 | 
				
			||||||
 | 
					        return UIDevice.current.userInterfaceIdiom == .phone
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    /// Returns `true` if the window is in portrait orientation, otherwise `false`.
 | 
					    /// Returns `true` if the window is in portrait orientation, otherwise `false`.
 | 
				
			||||||
    @objc var isPortraitOrientation: Bool {
 | 
					    @objc var isPortraitOrientation: Bool {
 | 
				
			||||||
        return view.window?.windowScene?.interfaceOrientation.isPortrait ?? false
 | 
					        return view.window?.windowScene?.interfaceOrientation.isPortrait ?? false
 | 
				
			||||||
| 
						 | 
					@ -258,7 +263,7 @@ class DetailViewController: BaseViewController {
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Moves the story pages controller to a Grid layout cell content (automatically removing it from the previous parent).
 | 
					    /// Moves the story pages controller to a Grid layout cell content (automatically removing it from the previous parent).
 | 
				
			||||||
    func prepareStoriesForGridView() {
 | 
					    func prepareStoriesForGridView() {
 | 
				
			||||||
        guard let storyPagesViewController else {
 | 
					        guard !isPhone, let storyPagesViewController else {
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
| 
						 | 
					@ -338,7 +343,7 @@ class DetailViewController: BaseViewController {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    private func adjustTopConstraint() {
 | 
					    private func adjustTopConstraint() {
 | 
				
			||||||
        if UIDevice.current.userInterfaceIdiom != .phone {
 | 
					        if !isPhone {
 | 
				
			||||||
            if view.window?.windowScene?.traitCollection.horizontalSizeClass == .compact {
 | 
					            if view.window?.windowScene?.traitCollection.horizontalSizeClass == .compact {
 | 
				
			||||||
                topContainerTopConstraint.constant = -50
 | 
					                topContainerTopConstraint.constant = -50
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
| 
						 | 
					@ -388,7 +393,7 @@ private extension DetailViewController {
 | 
				
			||||||
    func checkViewControllers() {
 | 
					    func checkViewControllers() {
 | 
				
			||||||
        let isTop = layout == .top
 | 
					        let isTop = layout == .top
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        if layout == .left {
 | 
					        if layout == .left || isPhone {
 | 
				
			||||||
            if feedDetailViewController != nil {
 | 
					            if feedDetailViewController != nil {
 | 
				
			||||||
                remove(viewController: feedDetailViewController)
 | 
					                remove(viewController: feedDetailViewController)
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
| 
						 | 
					@ -459,7 +464,7 @@ private extension DetailViewController {
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        appDelegate.feedDetailViewController.changedLayout()
 | 
					        appDelegate.feedDetailViewController.changedLayout()
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        if layout != .grid {
 | 
					        if layout != .grid || isPhone {
 | 
				
			||||||
            moveStoriesToDetail()
 | 
					            moveStoriesToDetail()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -261,7 +261,7 @@ struct CardContentView: View {
 | 
				
			||||||
                        .padding(.leading, 24)
 | 
					                        .padding(.leading, 24)
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                    Text(story.feedName)
 | 
					                    Text(story.feedName)
 | 
				
			||||||
                        .font(font(named: "WhitneySSm-Medium", size: 11))
 | 
					                        .font(font(named: "WhitneySSm-Medium", size: 12))
 | 
				
			||||||
                        .lineLimit(1)
 | 
					                        .lineLimit(1)
 | 
				
			||||||
                        .foregroundColor(feedColor)
 | 
					                        .foregroundColor(feedColor)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
| 
						 | 
					@ -315,7 +315,7 @@ struct CardContentView: View {
 | 
				
			||||||
                    Spacer()
 | 
					                    Spacer()
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                    Text(story.dateAndAuthor)
 | 
					                    Text(story.dateAndAuthor)
 | 
				
			||||||
                        .font(font(named: "WhitneySSm-Medium", size: 11))
 | 
					                        .font(font(named: "WhitneySSm-Medium", size: 12))
 | 
				
			||||||
                        .foregroundColor(dateAndAuthorColor)
 | 
					                        .foregroundColor(dateAndAuthorColor)
 | 
				
			||||||
                        .padding(.top, 5)
 | 
					                        .padding(.top, 5)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -69,7 +69,7 @@ struct FeedDetailGridView: View {
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        
 | 
					                        
 | 
				
			||||||
                        if cache.isGrid {
 | 
					                        if cache.isGrid && !cache.isPhone {
 | 
				
			||||||
                            EmptyView()
 | 
					                            EmptyView()
 | 
				
			||||||
                                .id(storyViewID)
 | 
					                                .id(storyViewID)
 | 
				
			||||||
                        } else if let story = cache.selected {
 | 
					                        } else if let story = cache.selected {
 | 
				
			||||||
| 
						 | 
					@ -134,7 +134,7 @@ struct FeedDetailGridView: View {
 | 
				
			||||||
            .onPreferenceChange(CardKey.self) {
 | 
					            .onPreferenceChange(CardKey.self) {
 | 
				
			||||||
                print("pref change for '\(story.title)': \($0)")
 | 
					                print("pref change for '\(story.title)': \($0)")
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                if let value = $0.first, value.frame.minY < 0 {
 | 
					                if let value = $0.first, value.frame.minY < -(value.frame.size.height / 2) {
 | 
				
			||||||
                    print("pref '\(story.title)': scrolled off the top")
 | 
					                    print("pref '\(story.title)': scrolled off the top")
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                    feedDetailInteraction.read(story: story)
 | 
					                    feedDetailInteraction.read(story: story)
 | 
				
			||||||
| 
						 | 
					@ -153,7 +153,7 @@ struct FeedDetailGridView: View {
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    @ViewBuilder
 | 
					    @ViewBuilder
 | 
				
			||||||
    func makeStoryView(cache: StoryCache) -> some View {
 | 
					    func makeStoryView(cache: StoryCache) -> some View {
 | 
				
			||||||
        if cache.isGrid, let story = cache.selected {
 | 
					        if cache.isGrid, !cache.isPhone, let story = cache.selected {
 | 
				
			||||||
            StoryView(cache: cache, story: loaded(story: story), interaction: feedDetailInteraction)
 | 
					            StoryView(cache: cache, story: loaded(story: story), interaction: feedDetailInteraction)
 | 
				
			||||||
//                .frame(height: storyHeight)
 | 
					//                .frame(height: storyHeight)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2390,21 +2390,34 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
 | 
				
			||||||
        }];
 | 
					        }];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if ([[UIDevice currentDevice] userInterfaceIdiom] != UIUserInterfaceIdiomPhone) {
 | 
					 | 
				
			||||||
    [appDelegate addSplitControlToMenuController:viewController];
 | 
					    [appDelegate addSplitControlToMenuController:viewController];
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    NSString *preferenceKey = @"story_titles_position";
 | 
					    NSString *preferenceKey = @"story_titles_position";
 | 
				
			||||||
        NSArray *titles = @[@"Left", @"Top", @"Bottom", @"Grid"];
 | 
					    NSArray *titles;
 | 
				
			||||||
        NSArray *values = @[@"titles_on_left", @"titles_on_top", @"titles_on_bottom", @"titles_in_grid"];
 | 
					    NSArray *values;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (appDelegate.detailViewController.isPhone) {
 | 
				
			||||||
 | 
					        titles = @[@"List", @"Grid"];
 | 
				
			||||||
 | 
					        values = @[@"titles_on_left", @"titles_in_grid"];
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        titles = @[@"Left", @"Top", @"Bottom", @"Grid"];
 | 
				
			||||||
 | 
					        values = @[@"titles_on_left", @"titles_on_top", @"titles_on_bottom", @"titles_in_grid"];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [viewController addSegmentedControlWithTitles:titles values:values preferenceKey:preferenceKey selectionShouldDismiss:YES handler:^(NSUInteger selectedIndex) {
 | 
					    [viewController addSegmentedControlWithTitles:titles values:values preferenceKey:preferenceKey selectionShouldDismiss:YES handler:^(NSUInteger selectedIndex) {
 | 
				
			||||||
        [self.appDelegate.detailViewController updateLayoutWithReload:YES];
 | 
					        [self.appDelegate.detailViewController updateLayoutWithReload:YES];
 | 
				
			||||||
    }];
 | 
					    }];
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if (self.appDelegate.detailViewController.storyTitlesInGrid) {
 | 
					    if (self.appDelegate.detailViewController.storyTitlesInGrid) {
 | 
				
			||||||
            NSString *preferenceKey = @"grid_columns";
 | 
					        preferenceKey = @"grid_columns";
 | 
				
			||||||
            NSArray *titles = @[@"Auto Cols", @"2", @"3", @"4"];
 | 
					        
 | 
				
			||||||
            NSArray *values = @[@"auto", @"2", @"3", @"4"];
 | 
					        if (appDelegate.detailViewController.isPhone) {
 | 
				
			||||||
 | 
					            titles = @[@"Auto Cols", @"1", @"2"];
 | 
				
			||||||
 | 
					            values = @[@"auto", @"1", @"2"];
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            titles = @[@"Auto Cols", @"1", @"2", @"3", @"4"];
 | 
				
			||||||
 | 
					            values = @[@"auto", @"1", @"2", @"3", @"4"];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [viewController addSegmentedControlWithTitles:titles values:values defaultValue:@"auto" preferenceKey:preferenceKey selectionShouldDismiss:NO handler:^(NSUInteger selectedIndex) {
 | 
					        [viewController addSegmentedControlWithTitles:titles values:values defaultValue:@"auto" preferenceKey:preferenceKey selectionShouldDismiss:NO handler:^(NSUInteger selectedIndex) {
 | 
				
			||||||
            [self.appDelegate.detailViewController updateLayoutWithReload:YES];
 | 
					            [self.appDelegate.detailViewController updateLayoutWithReload:YES];
 | 
				
			||||||
| 
						 | 
					@ -2418,12 +2431,11 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
 | 
				
			||||||
            [self.appDelegate.detailViewController updateLayoutWithReload:YES];
 | 
					            [self.appDelegate.detailViewController updateLayoutWithReload:YES];
 | 
				
			||||||
        }];
 | 
					        }];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if (!self.appDelegate.detailViewController.storyTitlesInGrid) {
 | 
					    if (!self.appDelegate.detailViewController.storyTitlesInGrid) {
 | 
				
			||||||
        NSString *preferenceKey = @"story_list_preview_text_size";
 | 
					        preferenceKey = @"story_list_preview_text_size";
 | 
				
			||||||
        NSArray *titles = @[@"Title", @"content_preview_small.png", @"content_preview_medium.png", @"content_preview_large.png"];
 | 
					        titles = @[@"Title", @"content_preview_small.png", @"content_preview_medium.png", @"content_preview_large.png"];
 | 
				
			||||||
        NSArray *values = @[@"title", @"short", @"medium", @"long"];
 | 
					        values = @[@"title", @"short", @"medium", @"long"];
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [viewController addSegmentedControlWithTitles:titles values:values preferenceKey:preferenceKey selectionShouldDismiss:NO handler:^(NSUInteger selectedIndex) {
 | 
					        [viewController addSegmentedControlWithTitles:titles values:values preferenceKey:preferenceKey selectionShouldDismiss:NO handler:^(NSUInteger selectedIndex) {
 | 
				
			||||||
            [self.appDelegate resizePreviewSize];
 | 
					            [self.appDelegate resizePreviewSize];
 | 
				
			||||||
| 
						 | 
					@ -2447,9 +2459,9 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
 | 
				
			||||||
        }];
 | 
					        }];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    NSString *preferenceKey = @"feed_list_font_size";
 | 
					    preferenceKey = @"feed_list_font_size";
 | 
				
			||||||
    NSArray *titles = @[@"XS", @"S", @"M", @"L", @"XL"];
 | 
					    titles = @[@"XS", @"S", @"M", @"L", @"XL"];
 | 
				
			||||||
    NSArray *values = @[@"xs", @"small", @"medium", @"large", @"xl"];
 | 
					    values = @[@"xs", @"small", @"medium", @"large", @"xl"];
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [viewController addSegmentedControlWithTitles:titles values:values preferenceKey:preferenceKey selectionShouldDismiss:NO handler:^(NSUInteger selectedIndex) {
 | 
					    [viewController addSegmentedControlWithTitles:titles values:values preferenceKey:preferenceKey selectionShouldDismiss:NO handler:^(NSUInteger selectedIndex) {
 | 
				
			||||||
        [self.appDelegate resizeFontSize];
 | 
					        [self.appDelegate resizeFontSize];
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -141,6 +141,10 @@ class StoryCache: ObservableObject {
 | 
				
			||||||
        return appDelegate.detailViewController.layout == .grid
 | 
					        return appDelegate.detailViewController.layout == .grid
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    var isPhone: Bool {
 | 
				
			||||||
 | 
					        return appDelegate.detailViewController.isPhone
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    @Published var before = [Story]()
 | 
					    @Published var before = [Story]()
 | 
				
			||||||
    @Published var selected: Story?
 | 
					    @Published var selected: Story?
 | 
				
			||||||
    @Published var after = [Story]()
 | 
					    @Published var after = [Story]()
 | 
				
			||||||
| 
						 | 
					@ -288,14 +292,14 @@ class StorySettings {
 | 
				
			||||||
//    }
 | 
					//    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    var gridColumns: Int {
 | 
					    var gridColumns: Int {
 | 
				
			||||||
        if NewsBlurAppDelegate.shared.isCompactWidth {
 | 
					 | 
				
			||||||
            return 1
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        guard let pref = UserDefaults.standard.string(forKey: "grid_columns"), let columns = Int(pref) else {
 | 
					        guard let pref = UserDefaults.standard.string(forKey: "grid_columns"), let columns = Int(pref) else {
 | 
				
			||||||
            //TODO: 🚧 could have extra logic to determine the ideal number of columns
 | 
					            //TODO: 🚧 could have extra logic to determine the ideal number of columns
 | 
				
			||||||
 | 
					            if NewsBlurAppDelegate.shared.isCompactWidth {
 | 
				
			||||||
 | 
					                return 1
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
                return 4
 | 
					                return 4
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        return columns
 | 
					        return columns
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -287,7 +287,7 @@
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- (void)deferredEnableScrolling {
 | 
					- (void)deferredEnableScrolling {
 | 
				
			||||||
    self.webView.scrollView.scrollEnabled = !self.appDelegate.detailViewController.storyTitlesInGrid;
 | 
					    self.webView.scrollView.scrollEnabled = self.appDelegate.detailViewController.isPhone || !self.appDelegate.detailViewController.storyTitlesInGrid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- (void)viewDidDisappear:(BOOL)animated {
 | 
					- (void)viewDidDisappear:(BOOL)animated {
 | 
				
			||||||
| 
						 | 
					@ -1817,7 +1817,7 @@
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [self.activityIndicator stopAnimating];
 | 
					    [self.activityIndicator stopAnimating];
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    self.webView.scrollView.scrollEnabled = !self.appDelegate.detailViewController.storyTitlesInGrid;
 | 
					    self.webView.scrollView.scrollEnabled = self.appDelegate.detailViewController.isPhone || !self.appDelegate.detailViewController.storyTitlesInGrid;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [self loadHTMLString:self.fullStoryHTML];
 | 
					    [self loadHTMLString:self.fullStoryHTML];
 | 
				
			||||||
    self.fullStoryHTML = nil;
 | 
					    self.fullStoryHTML = nil;
 | 
				
			||||||
| 
						 | 
					@ -1837,7 +1837,7 @@
 | 
				
			||||||
        self.webView.hidden = NO;
 | 
					        self.webView.hidden = NO;
 | 
				
			||||||
        [self.webView setNeedsDisplay];
 | 
					        [self.webView setNeedsDisplay];
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        if (self == self.appDelegate.storyPagesViewController.currentPage && self.appDelegate.detailViewController.storyTitlesInGrid) {
 | 
					        if (self == self.appDelegate.storyPagesViewController.currentPage && !self.appDelegate.detailViewController.isPhone && self.appDelegate.detailViewController.storyTitlesInGrid) {
 | 
				
			||||||
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
 | 
					            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
 | 
				
			||||||
//                [self.appDelegate.feedDetailViewController changedStoryHeight:self.webView.scrollView.contentSize.height];
 | 
					//                [self.appDelegate.feedDetailViewController changedStoryHeight:self.webView.scrollView.contentSize.height];
 | 
				
			||||||
                [self.appDelegate.feedDetailViewController reload];
 | 
					                [self.appDelegate.feedDetailViewController reload];
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -202,6 +202,26 @@
 | 
				
			||||||
			<key>Key</key>
 | 
								<key>Key</key>
 | 
				
			||||||
			<string>override_scroll_read_filter</string>
 | 
								<string>override_scroll_read_filter</string>
 | 
				
			||||||
		</dict>
 | 
							</dict>
 | 
				
			||||||
 | 
							<dict>
 | 
				
			||||||
 | 
								<key>Type</key>
 | 
				
			||||||
 | 
								<string>PSMultiValueSpecifier</string>
 | 
				
			||||||
 | 
								<key>Title</key>
 | 
				
			||||||
 | 
								<string>Story titles layout</string>
 | 
				
			||||||
 | 
								<key>Titles</key>
 | 
				
			||||||
 | 
								<array>
 | 
				
			||||||
 | 
									<string>Titles in list</string>
 | 
				
			||||||
 | 
									<string>Titles in grid</string>
 | 
				
			||||||
 | 
								</array>
 | 
				
			||||||
 | 
								<key>DefaultValue</key>
 | 
				
			||||||
 | 
								<string>titles_on_left</string>
 | 
				
			||||||
 | 
								<key>Values</key>
 | 
				
			||||||
 | 
								<array>
 | 
				
			||||||
 | 
									<string>titles_on_left</string>
 | 
				
			||||||
 | 
									<string>titles_in_grid</string>
 | 
				
			||||||
 | 
								</array>
 | 
				
			||||||
 | 
								<key>Key</key>
 | 
				
			||||||
 | 
								<string>story_titles_position</string>
 | 
				
			||||||
 | 
							</dict>
 | 
				
			||||||
		<dict>
 | 
							<dict>
 | 
				
			||||||
			<key>Type</key>
 | 
								<key>Type</key>
 | 
				
			||||||
			<string>PSMultiValueSpecifier</string>
 | 
								<string>PSMultiValueSpecifier</string>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue