mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
#1720 (Grid view)
- Experimenting with using SwiftUI for the list and grid views. Working really well! - Beginnings of a real data model, at last; probably only for caching stories initially.
This commit is contained in:
parent
8985bf85e4
commit
7b28791603
7 changed files with 541 additions and 7 deletions
|
@ -257,20 +257,36 @@ class DetailViewController: BaseViewController {
|
|||
}
|
||||
|
||||
/// Moves the story pages controller to a Grid layout cell content (automatically removing it from the previous parent).
|
||||
@objc func moveStoriesToGridCell(_ cellContent: UIView) {
|
||||
func prepareStoriesForGridView() {
|
||||
guard let storyPagesViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
print("🎈 moveStoriesToGridCell: \(storyPagesViewController.currentPage.activeStory["story_title"] ?? "none")")
|
||||
print("🎈 prepareStoriesForGridView: \(storyPagesViewController.currentPage.activeStory?["story_title"] ?? "none")")
|
||||
|
||||
add(viewController: storyPagesViewController, to: cellContent, of: appDelegate.feedDetailViewController)
|
||||
storyPagesViewController.updatePage(withActiveStory: appDelegate.storiesCollection.locationOfActiveStory(), updateFeedDetail: false)
|
||||
|
||||
adjustForAutoscroll()
|
||||
|
||||
storyPagesViewController.currentPage.webView.scrollView.isScrollEnabled = false
|
||||
}
|
||||
|
||||
/// Moves the story pages controller to a Grid layout cell content (automatically removing it from the previous parent).
|
||||
@objc func moveStoriesToGridCell(_ cellContent: UIView) {
|
||||
#warning("hack disabled for SwiftUI experiment")
|
||||
// guard let storyPagesViewController else {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// print("🎈 moveStoriesToGridCell: \(storyPagesViewController.currentPage.activeStory["story_title"] ?? "none")")
|
||||
//
|
||||
// add(viewController: storyPagesViewController, to: cellContent, of: appDelegate.feedDetailViewController)
|
||||
//
|
||||
// adjustForAutoscroll()
|
||||
//
|
||||
// storyPagesViewController.currentPage.webView.scrollView.isScrollEnabled = false
|
||||
}
|
||||
|
||||
/// Moves the story pages controller to the detail controller (automatically removing it from the previous parent).
|
||||
@objc func moveStoriesToDetail() {
|
||||
guard let storyPagesViewController else {
|
||||
|
|
442
clients/ios/Classes/FeedDetailGridView.swift
Normal file
442
clients/ios/Classes/FeedDetailGridView.swift
Normal file
|
@ -0,0 +1,442 @@
|
|||
//
|
||||
// FeedDetailGridView.swift
|
||||
// NewsBlur
|
||||
//
|
||||
// Created by David Sinclair on 2023-01-19.
|
||||
// Copyright © 2023 NewsBlur. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// NOTE: this code is rather untidy, as it is experimental; it'll get cleaned up later.
|
||||
|
||||
class Story: Identifiable {
|
||||
let id = UUID()
|
||||
let index: Int
|
||||
// lazy var title: String = {
|
||||
// return "lazy story #\(index)"
|
||||
// }()
|
||||
|
||||
var dictionary: [String : Any]?
|
||||
|
||||
var feedID = ""
|
||||
var feedName = ""
|
||||
var title = ""
|
||||
var content = ""
|
||||
var dateString = ""
|
||||
var timestamp = 0
|
||||
var isSaved = false
|
||||
var isShared = false
|
||||
var hash = ""
|
||||
var author = ""
|
||||
|
||||
var dateAndAuthor: String {
|
||||
let date = Utilities.formatShortDate(fromTimestamp: timestamp) ?? ""
|
||||
|
||||
return author.isEmpty ? date : "\(date) · \(author)"
|
||||
}
|
||||
|
||||
var feedColorBar: UIColor?
|
||||
var feedColorBarTopBorder: UIColor?
|
||||
|
||||
var isSelected: Bool {
|
||||
return index == NewsBlurAppDelegate.shared!.storiesCollection.indexOfActiveStory()
|
||||
}
|
||||
|
||||
init(index: Int) {
|
||||
self.index = index
|
||||
}
|
||||
|
||||
private func string(for key: String) -> String {
|
||||
guard let dictionary else {
|
||||
return ""
|
||||
}
|
||||
|
||||
return dictionary[key] as? String ?? ""
|
||||
}
|
||||
|
||||
func load() {
|
||||
guard let appDelegate = NewsBlurAppDelegate.shared, let storiesCollection = appDelegate.storiesCollection,
|
||||
index < storiesCollection.activeFeedStoryLocations.count,
|
||||
let row = storiesCollection.activeFeedStoryLocations[index] as? Int,
|
||||
let story = storiesCollection.activeFeedStories[row] as? [String : Any] else {
|
||||
return
|
||||
}
|
||||
|
||||
dictionary = story
|
||||
|
||||
feedID = appDelegate.feedIdWithoutSearchQuery(string(for: "story_feed_id"))
|
||||
feedName = string(for: "feed_title")
|
||||
title = (string(for: "story_title") as NSString).decodingHTMLEntities()
|
||||
content = String(string(for: "story_content").convertHTML().decodingXMLEntities().decodingHTMLEntities().replacingOccurrences(of: "\n", with: " ").prefix(500))
|
||||
author = string(for: "story_authors").replacingOccurrences(of: "\"", with: "")
|
||||
dateString = string(for: "short_parsed_date")
|
||||
timestamp = dictionary?["story_timestamp"] as? Int ?? 0
|
||||
isSaved = dictionary?["starred"] as? Bool ?? false
|
||||
isShared = dictionary?["shared"] as? Bool ?? false
|
||||
hash = string(for: "story_hash")
|
||||
|
||||
//TODO: 🚧 might make some of these lazy computed properties
|
||||
// // feed color bar border
|
||||
// unsigned int colorBorder = 0;
|
||||
// NSString *faviconColor = [feed valueForKey:@"favicon_fade"];
|
||||
//
|
||||
// if ([faviconColor class] == [NSNull class] || !faviconColor) {
|
||||
// faviconColor = @"707070";
|
||||
// }
|
||||
// NSScanner *scannerBorder = [NSScanner scannerWithString:faviconColor];
|
||||
// [scannerBorder scanHexInt:&colorBorder];
|
||||
//
|
||||
// cell.feedColorBar = UIColorFromFixedRGB(colorBorder);
|
||||
//
|
||||
// // feed color bar border
|
||||
// NSString *faviconFade = [feed valueForKey:@"favicon_color"];
|
||||
// if ([faviconFade class] == [NSNull class] || !faviconFade) {
|
||||
// faviconFade = @"505050";
|
||||
// }
|
||||
// scannerBorder = [NSScanner scannerWithString:faviconFade];
|
||||
// [scannerBorder scanHexInt:&colorBorder];
|
||||
// cell.feedColorBarTopBorder = UIColorFromFixedRGB(colorBorder);
|
||||
//
|
||||
// // favicon
|
||||
// cell.siteFavicon = [appDelegate getFavicon:feedIdStr];
|
||||
// cell.hasAlpha = NO;
|
||||
//
|
||||
// // undread indicator
|
||||
//
|
||||
// int score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]];
|
||||
// cell.storyScore = score;
|
||||
//
|
||||
// cell.isRead = ![storiesCollection isStoryUnread:story];
|
||||
// cell.isReadAvailable = ![storiesCollection.activeFolder isEqualToString:@"saved_stories"];
|
||||
// cell.textSize = self.textSize;
|
||||
// cell.isShort = NO;
|
||||
//
|
||||
// UIInterfaceOrientation orientation = self.view.window.windowScene.interfaceOrientation;
|
||||
// if (!self.isPhoneOrCompact &&
|
||||
// !appDelegate.detailViewController.storyTitlesOnLeft &&
|
||||
// UIInterfaceOrientationIsPortrait(orientation)) {
|
||||
// cell.isShort = YES;
|
||||
// }
|
||||
//
|
||||
// cell.isRiverOrSocial = NO;
|
||||
// if (storiesCollection.isRiverView ||
|
||||
// storiesCollection.isSavedView ||
|
||||
// storiesCollection.isReadView ||
|
||||
// storiesCollection.isWidgetView ||
|
||||
// storiesCollection.isSocialView ||
|
||||
// storiesCollection.isSocialRiverView) {
|
||||
// cell.isRiverOrSocial = YES;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
extension Story: Equatable {
|
||||
static func == (lhs: Story, rhs: Story) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
extension Story: CustomDebugStringConvertible {
|
||||
var debugDescription: String {
|
||||
return "Story \"\(title)\" in \(feedName)"
|
||||
}
|
||||
}
|
||||
|
||||
class StoryCache: ObservableObject {
|
||||
let appDelegate = NewsBlurAppDelegate.shared!
|
||||
|
||||
var isGrid: Bool {
|
||||
return appDelegate.detailViewController.layout == .grid
|
||||
}
|
||||
|
||||
@Published var before = [Story]()
|
||||
@Published var selected: Story?
|
||||
@Published var after = [Story]()
|
||||
|
||||
func appendStories(beforeSelection: [Int], selectedIndex: Int, afterSelection: [Int]) {
|
||||
before = beforeSelection.map { Story(index: $0) }
|
||||
selected = selectedIndex >= 0 ? Story(index: selectedIndex) : nil
|
||||
after = afterSelection.map { Story(index: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
struct CardView: View {
|
||||
let cache: StoryCache
|
||||
|
||||
let story: Story
|
||||
|
||||
var previewImage: UIImage? {
|
||||
guard let image = cache.appDelegate.cachedImage(forStoryHash: story.hash), image.isKind(of: UIImage.self) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if cache.isGrid {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 12).foregroundColor(.init(white: 0.9))
|
||||
|
||||
VStack {
|
||||
// RoundedRectangle(cornerRadius: 12).foregroundColor(.random)
|
||||
// .frame(height: 200)
|
||||
|
||||
if let previewImage {
|
||||
Image(uiImage: previewImage)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(height: 200)
|
||||
// .clipped()
|
||||
// .clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
.cornerRadius(12, corners: [.topLeft, .topRight])
|
||||
.padding(0)
|
||||
}
|
||||
|
||||
CardContentView(story: story)
|
||||
.frame(maxHeight: .infinity, alignment: .leading)
|
||||
.padding(10)
|
||||
.padding(.leading, 20)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ZStack {
|
||||
if story.isSelected {
|
||||
RoundedRectangle(cornerRadius: 12).foregroundColor(.init(white: 0.9))
|
||||
}
|
||||
|
||||
HStack {
|
||||
CardContentView(story: story)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 20)
|
||||
|
||||
if let previewImage {
|
||||
// RoundedRectangle(cornerRadius: 12).foregroundColor(.random)
|
||||
Image(uiImage: previewImage)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 80)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
// .clipped()
|
||||
}
|
||||
}
|
||||
.if(story.isSelected) { view in
|
||||
view.padding(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//struct CardView_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// CardView(cache: StoryCache(), story: Story(index: 0))
|
||||
// }
|
||||
//}
|
||||
|
||||
struct CardContentView: View {
|
||||
let story: Story
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text(story.title)
|
||||
.font(.custom("WhitneySSm-Medium", size: 18, relativeTo: .caption).bold())
|
||||
Text(story.content.prefix(400))
|
||||
.font(.custom("WhitneySSm-Book", size: 13, relativeTo: .caption))
|
||||
.padding(.top, 5)
|
||||
Spacer()
|
||||
Text(story.dateAndAuthor)
|
||||
.font(.custom("WhitneySSm-Medium", size: 10, relativeTo: .caption))
|
||||
.padding(.top, 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StoryPagesView: UIViewControllerRepresentable {
|
||||
typealias UIViewControllerType = StoryPagesViewController
|
||||
|
||||
let appDelegate = NewsBlurAppDelegate.shared!
|
||||
|
||||
func makeUIViewController(context: Context) -> StoryPagesViewController {
|
||||
appDelegate.detailViewController.prepareStoriesForGridView()
|
||||
|
||||
return appDelegate.storyPagesViewController
|
||||
}
|
||||
|
||||
func updateUIViewController(_ storyPagesViewController: StoryPagesViewController, context: Context) {
|
||||
storyPagesViewController.updatePage(withActiveStory: appDelegate.storiesCollection.locationOfActiveStory(), updateFeedDetail: false)
|
||||
}
|
||||
}
|
||||
|
||||
struct StoryView: View {
|
||||
let story: Story
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text(story.title)
|
||||
StoryPagesView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Color {
|
||||
static var random: Color {
|
||||
return Color(
|
||||
red: .random(in: 0...1),
|
||||
green: .random(in: 0...1),
|
||||
blue: .random(in: 0...1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
@ViewBuilder
|
||||
func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
|
||||
if condition {
|
||||
transform(self)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
@ViewBuilder
|
||||
func modify<Content: View>(@ViewBuilder _ transform: (Self) -> Content?) -> some View {
|
||||
if let view = transform(self), !(view is EmptyView) {
|
||||
view
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
|
||||
clipShape( RoundedCorner(radius: radius, corners: corners) )
|
||||
}
|
||||
}
|
||||
|
||||
struct RoundedCorner: Shape {
|
||||
|
||||
var radius: CGFloat = .infinity
|
||||
var corners: UIRectCorner = .allCorners
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
|
||||
return Path(path.cgPath)
|
||||
}
|
||||
}
|
||||
|
||||
//extension View {
|
||||
// @ViewBuilder
|
||||
// func swipe() -> some View {
|
||||
// if #available(iOS 15, *) {
|
||||
// self.swipeActions {
|
||||
// Button("Order") {
|
||||
// print("Awesome!")
|
||||
// }
|
||||
// .tint(.green)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
protocol FeedDetailInteraction {
|
||||
func storyAppeared(_ story: Story)
|
||||
func storyTapped(_ story: Story)
|
||||
}
|
||||
|
||||
struct FeedDetailGridView: View {
|
||||
var feedDetailInteraction: FeedDetailInteraction
|
||||
|
||||
@ObservedObject var cache: StoryCache
|
||||
|
||||
var columns: [GridItem] {
|
||||
if cache.isGrid {
|
||||
return [GridItem(.flexible(), spacing: 20),
|
||||
GridItem(.flexible(), spacing: 20),
|
||||
GridItem(.flexible(), spacing: 20),
|
||||
]
|
||||
} else {
|
||||
return [GridItem(.flexible()),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
var isOS15OrLater: Bool {
|
||||
if #available(iOS 15.0, *) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var cardHeight: CGFloat {
|
||||
//TODO: 🚧 switch based on grid card height
|
||||
return 400
|
||||
}
|
||||
|
||||
var storyHeight: CGFloat {
|
||||
//TODO: 🚧 determine ideal height of story view
|
||||
return 1000
|
||||
}
|
||||
|
||||
// let stories: [Story] = StoryCache.stories
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: columns, spacing: 20) {
|
||||
Section {
|
||||
ForEach(cache.before) { story in
|
||||
makeCardView(for: story, cache: cache)
|
||||
}
|
||||
}
|
||||
|
||||
if !cache.isGrid, let story = cache.selected {
|
||||
makeCardView(for: story, cache: cache)
|
||||
}
|
||||
|
||||
Section(header: makeStoryView(cache: cache)) {
|
||||
ForEach(cache.after) { story in
|
||||
makeCardView(for: story, cache: cache)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
func makeCardView(for story: Story, cache: StoryCache) -> some View {
|
||||
return CardView(cache: cache, story: self.loaded(story: story))
|
||||
.onAppear {
|
||||
feedDetailInteraction.storyAppeared(story)
|
||||
}
|
||||
.onTapGesture {
|
||||
feedDetailInteraction.storyTapped(story)
|
||||
}
|
||||
.if(cache.isGrid) { view in
|
||||
view.frame(height: cardHeight)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func makeStoryView(cache: StoryCache) -> some View {
|
||||
if cache.isGrid, let story = cache.selected {
|
||||
StoryView(story: story)
|
||||
.frame(height: storyHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func loaded(story: Story) -> Story {
|
||||
story.load()
|
||||
return story
|
||||
}
|
||||
}
|
||||
|
||||
//struct FeedDetailGridView_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// FeedDetailGridView(feedDetailInteraction: FeedDetailViewController(), storyCache: StoryCache())
|
||||
// }
|
||||
//}
|
|
@ -7,9 +7,14 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
|
||||
/// List of stories for a feed.
|
||||
class FeedDetailViewController: FeedDetailObjCViewController {
|
||||
lazy var gridViewController = makeGridViewController()
|
||||
|
||||
lazy var storyCache = StoryCache()
|
||||
|
||||
enum SectionLayoutKind: Int, CaseIterable {
|
||||
/// Feed cells before the story.
|
||||
case feedBeforeStory
|
||||
|
@ -77,10 +82,32 @@ class FeedDetailViewController: FeedDetailObjCViewController {
|
|||
|
||||
var dataSource: UICollectionViewDiffableDataSource<SectionLayoutKind, Int>! = nil
|
||||
|
||||
private func makeGridViewController() -> UIHostingController<FeedDetailGridView> {
|
||||
// let headerView = FeedDetailGridView(isGrid: isGrid, storyCache: storyCache)
|
||||
let gridView = FeedDetailGridView(feedDetailInteraction: self, cache: storyCache)
|
||||
let gridViewController = UIHostingController(rootView: gridView)
|
||||
gridViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
return gridViewController
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
changedLayout()
|
||||
configureDataSource()
|
||||
|
||||
feedCollectionView.isHidden = true
|
||||
|
||||
addChild(gridViewController)
|
||||
view.addSubview(gridViewController.view)
|
||||
gridViewController.didMove(toParent: self)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
gridViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
gridViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
gridViewController.view.widthAnchor.constraint(equalTo: view.widthAnchor),
|
||||
gridViewController.view.heightAnchor.constraint(equalTo: view.heightAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
@objc override func changedLayout() {
|
||||
|
@ -248,28 +275,64 @@ extension FeedDetailViewController {
|
|||
var snapshot = NSDiffableDataSourceSnapshot<SectionLayoutKind, Int>()
|
||||
|
||||
let storyCount = Int(appDelegate.storiesCollection.storyLocationsCount)
|
||||
var beforeSelection = [Int]()
|
||||
var selectedIndex = -999
|
||||
var afterSelection = [Int]()
|
||||
|
||||
snapshot.appendSections(SectionLayoutKind.allCases)
|
||||
|
||||
if self.messageView.isHidden {
|
||||
if storyCount > 0 {
|
||||
let selectedIndex = appDelegate.storiesCollection.indexOfActiveStory()
|
||||
selectedIndex = appDelegate.storiesCollection.indexOfActiveStory()
|
||||
|
||||
if selectedIndex < 0 {
|
||||
snapshot.appendItems(Array(0..<storyCount), toSection: .feedBeforeStory)
|
||||
beforeSelection = Array(0..<storyCount)
|
||||
snapshot.appendItems(beforeSelection, toSection: .feedBeforeStory)
|
||||
} else {
|
||||
snapshot.appendItems(Array(0..<selectedIndex), toSection: .feedBeforeStory)
|
||||
beforeSelection = Array(0..<selectedIndex)
|
||||
|
||||
snapshot.appendItems(beforeSelection, toSection: .feedBeforeStory)
|
||||
snapshot.appendItems([selectedIndex], toSection: .selectedStory)
|
||||
|
||||
if selectedIndex + 1 < storyCount {
|
||||
snapshot.appendItems(Array(selectedIndex + 1..<storyCount), toSection: .feedAfterStory)
|
||||
afterSelection = Array(selectedIndex + 1..<storyCount)
|
||||
snapshot.appendItems(afterSelection, toSection: .feedAfterStory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snapshot.appendItems([-1], toSection: .loading)
|
||||
|
||||
//TODO: 🚧 move the above logic into StoryCache
|
||||
storyCache.appendStories(beforeSelection: beforeSelection, selectedIndex: selectedIndex, afterSelection: afterSelection)
|
||||
}
|
||||
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedDetailViewController: FeedDetailInteraction {
|
||||
func storyAppeared(_ story: Story) {
|
||||
print("\(story.title) appeared")
|
||||
|
||||
//TODO: 🚧: this logic is from checkScroll; some more stuff there that may be needed
|
||||
if story.index >= storyCache.before.count + storyCache.after.count - 5 {
|
||||
if storiesCollection.isRiverView, storiesCollection.activeFolder != nil {
|
||||
fetchRiverPage(storiesCollection.feedPage + 1, withCallback: nil)
|
||||
} else {
|
||||
fetchFeedDetail(storiesCollection.feedPage + 1, withCallback: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func storyTapped(_ story: Story) {
|
||||
print("tapped \(story.title)")
|
||||
|
||||
let indexPath = IndexPath(row: story.index, section: 0)
|
||||
|
||||
//TODO: 🚧 change this function to work better with the grid view
|
||||
collectionView(feedCollectionView, didSelectItemAt: indexPath)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -157,6 +157,8 @@ SFSafariViewControllerDelegate> {
|
|||
PINCache *cachedStoryImages;
|
||||
}
|
||||
|
||||
@property (class, nonatomic) NewsBlurAppDelegate *shared;
|
||||
|
||||
@property (nonatomic) SplitViewController *splitViewController;
|
||||
@property (nonatomic) IBOutlet UINavigationController *ftuxNavigationController;
|
||||
@property (nonatomic) IBOutlet UINavigationController *feedsNavigationController;
|
||||
|
|
|
@ -196,6 +196,10 @@
|
|||
return (NewsBlurAppDelegate *)[UIApplication sharedApplication].delegate;
|
||||
}
|
||||
|
||||
+ (instancetype)shared {
|
||||
return (NewsBlurAppDelegate *)[UIApplication sharedApplication].delegate;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
[self registerDefaultsFromSettingsBundle];
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
172AD264251D901D000BB264 /* HorizontalPageDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172AD263251D901D000BB264 /* HorizontalPageDelegate.swift */; };
|
||||
172AD274251D9F40000BB264 /* Storyboards.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172AD273251D9F40000BB264 /* Storyboards.swift */; };
|
||||
17362ADD23639B4E00A0FCCC /* OfflineFetchText.m in Sources */ = {isa = PBXBuildFile; fileRef = 17362ADC23639B4E00A0FCCC /* OfflineFetchText.m */; };
|
||||
1737A9E22979EA8700E84348 /* FeedDetailGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1737A9E12979EA8700E84348 /* FeedDetailGridView.swift */; };
|
||||
1737A9E32979EA8700E84348 /* FeedDetailGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1737A9E12979EA8700E84348 /* FeedDetailGridView.swift */; };
|
||||
173CB30F26BCE94700BA872A /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 173CB30E26BCE94700BA872A /* WidgetKit.framework */; };
|
||||
173CB31126BCE94700BA872A /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 173CB31026BCE94700BA872A /* SwiftUI.framework */; };
|
||||
173CB31426BCE94700BA872A /* WidgetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173CB31326BCE94700BA872A /* WidgetExtension.swift */; };
|
||||
|
@ -1396,6 +1398,7 @@
|
|||
172AD273251D9F40000BB264 /* Storyboards.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storyboards.swift; sourceTree = "<group>"; };
|
||||
17362ADB23639B4E00A0FCCC /* OfflineFetchText.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = OfflineFetchText.h; path = offline/OfflineFetchText.h; sourceTree = "<group>"; };
|
||||
17362ADC23639B4E00A0FCCC /* OfflineFetchText.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = OfflineFetchText.m; path = offline/OfflineFetchText.m; sourceTree = "<group>"; };
|
||||
1737A9E12979EA8700E84348 /* FeedDetailGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedDetailGridView.swift; sourceTree = "<group>"; };
|
||||
173CB30D26BCE94700BA872A /* NewsBlur Widget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "NewsBlur Widget.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
173CB30E26BCE94700BA872A /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||
173CB31026BCE94700BA872A /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
|
@ -2683,6 +2686,7 @@
|
|||
170E3CD624F8AB0D009CE819 /* FeedDetailViewController.swift */,
|
||||
FF4D9074265BE13500792DB3 /* FeedDetailObjCViewController.h */,
|
||||
FF4D9073265BE13500792DB3 /* FeedDetailObjCViewController.m */,
|
||||
1737A9E12979EA8700E84348 /* FeedDetailGridView.swift */,
|
||||
17435D302942DBBA002C126C /* FeedDetailCollectionCell.swift */,
|
||||
7843F50311EEB1A000675F64 /* FeedDetailCollectionCellObsoleteObjCEdition.h */,
|
||||
7843F50411EEB1A000675F64 /* FeedDetailCollectionCellObsoleteObjCEdition.m */,
|
||||
|
@ -4895,6 +4899,7 @@
|
|||
175792702930605500490924 /* THCircularProgressView.m in Sources */,
|
||||
175792712930605500490924 /* IASKSpecifier.m in Sources */,
|
||||
175792722930605500490924 /* UIView+ViewController.m in Sources */,
|
||||
1737A9E32979EA8700E84348 /* FeedDetailGridView.swift in Sources */,
|
||||
175792732930605500490924 /* PINCache.m in Sources */,
|
||||
175792742930605500490924 /* FeedChooserViewCell.m in Sources */,
|
||||
175792752930605500490924 /* UIView+TKCategory.m in Sources */,
|
||||
|
@ -5072,6 +5077,7 @@
|
|||
FFD6604C1BACA45D006E4B8D /* THCircularProgressView.m in Sources */,
|
||||
FF34FD681E9D93CB0062F8ED /* IASKSpecifier.m in Sources */,
|
||||
FFA0484419CA73B700618DC4 /* UIView+ViewController.m in Sources */,
|
||||
1737A9E22979EA8700E84348 /* FeedDetailGridView.swift in Sources */,
|
||||
FF2924E51E932D2900FCFA63 /* PINCache.m in Sources */,
|
||||
17432C891C534BC6003F8FD6 /* FeedChooserViewCell.m in Sources */,
|
||||
43A4C3E515B00966008787B5 /* UIView+TKCategory.m in Sources */,
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "NSString+HTML.h"
|
||||
#import "NewsBlurAppDelegate.h"
|
||||
#import "ThemeManager.h"
|
||||
#import "StoriesCollection.h"
|
||||
|
|
Loading…
Add table
Reference in a new issue