diff --git a/Leganto.xcodeproj/project.pbxproj b/Leganto.xcodeproj/project.pbxproj index ec68a3d..6821cdb 100644 --- a/Leganto.xcodeproj/project.pbxproj +++ b/Leganto.xcodeproj/project.pbxproj @@ -14,7 +14,9 @@ 86B463492A1A382B00CBBE76 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 86B463482A1A382B00CBBE76 /* Preview Assets.xcassets */; }; 86B463532A1A3E4700CBBE76 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 86B463552A1A3E4700CBBE76 /* Localizable.strings */; }; 86B463582A1A42D100CBBE76 /* DataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86B463572A1A42D100CBBE76 /* DataController.swift */; }; - 86B4635A2A1A499300CBBE76 /* AddFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86B463592A1A499300CBBE76 /* AddFeed.swift */; }; + 86B4635A2A1A499300CBBE76 /* AddFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86B463592A1A499300CBBE76 /* AddFeedView.swift */; }; + 86B4635C2A1D1C6C00CBBE76 /* FeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86B4635B2A1D1C6C00CBBE76 /* FeedView.swift */; }; + 86B4635E2A1D217B00CBBE76 /* FeedViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86B4635D2A1D217B00CBBE76 /* FeedViewCell.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -27,7 +29,9 @@ 86B463482A1A382B00CBBE76 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 86B463562A1A3ECE00CBBE76 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; }; 86B463572A1A42D100CBBE76 /* DataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataController.swift; sourceTree = ""; }; - 86B463592A1A499300CBBE76 /* AddFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeed.swift; sourceTree = ""; }; + 86B463592A1A499300CBBE76 /* AddFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedView.swift; sourceTree = ""; }; + 86B4635B2A1D1C6C00CBBE76 /* FeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedView.swift; sourceTree = ""; }; + 86B4635D2A1D217B00CBBE76 /* FeedViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewCell.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -60,6 +64,7 @@ 86B4633A2A1A382800CBBE76 /* Leganto */ = { isa = PBXGroup; children = ( + 86B4635D2A1D217B00CBBE76 /* FeedViewCell.swift */, 86B4633B2A1A382800CBBE76 /* LegantoApp.swift */, 86B463422A1A382800CBBE76 /* ContentView.swift */, 86B463572A1A42D100CBBE76 /* DataController.swift */, @@ -68,7 +73,8 @@ 86B463462A1A382B00CBBE76 /* Leganto.entitlements */, 86B4633F2A1A382800CBBE76 /* Leganto.xcdatamodeld */, 86B463472A1A382B00CBBE76 /* Preview Content */, - 86B463592A1A499300CBBE76 /* AddFeed.swift */, + 86B463592A1A499300CBBE76 /* AddFeedView.swift */, + 86B4635B2A1D1C6C00CBBE76 /* FeedView.swift */, ); path = Leganto; sourceTree = ""; @@ -154,8 +160,10 @@ files = ( 86B463412A1A382800CBBE76 /* Leganto.xcdatamodeld in Sources */, 86B463582A1A42D100CBBE76 /* DataController.swift in Sources */, - 86B4635A2A1A499300CBBE76 /* AddFeed.swift in Sources */, + 86B4635A2A1A499300CBBE76 /* AddFeedView.swift in Sources */, 86B4633C2A1A382800CBBE76 /* LegantoApp.swift in Sources */, + 86B4635C2A1D1C6C00CBBE76 /* FeedView.swift in Sources */, + 86B4635E2A1D217B00CBBE76 /* FeedViewCell.swift in Sources */, 86B463432A1A382800CBBE76 /* ContentView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Leganto/AddFeed.swift b/Leganto/AddFeedView.swift similarity index 70% rename from Leganto/AddFeed.swift rename to Leganto/AddFeedView.swift index b3c5384..e0b5e21 100644 --- a/Leganto/AddFeed.swift +++ b/Leganto/AddFeedView.swift @@ -17,6 +17,19 @@ enum Genre: Int16, CaseIterable, Identifiable { } struct AddFeed: View { + + var body: some View { + #if os(iOS) + NavigationView { + AddFeedContent() + } + #else + AddFeedContent() + #endif + } +} + +struct AddFeedContent: View { @Environment(\.managedObjectContext) var moc @Environment(\.dismiss) var dismiss @@ -26,19 +39,18 @@ struct AddFeed: View { @State private var url = "" var body: some View { - NavigationView { - Form{ - Section { - TextField("name", text: $name) - TextField("desc", text: $desc) - TextField("url", text: $url) - Picker("genre", selection: $genre) { - ForEach(Genre.allCases) { option in - Text(LocalizedStringKey(String(describing: option))) - } + Form { + Section { + TextField("name", text: $name) + TextField("desc", text: $desc) + TextField("url", text: $url) + Picker("genre", selection: $genre) { + ForEach(Genre.allCases) { option in + Text(LocalizedStringKey(String(describing: option))) } } } + } .toolbar { ToolbarItem { Button(action: addItem) { @@ -46,11 +58,8 @@ struct AddFeed: View { } } } - } } - - - + func addItem() { let newFeed = Feed(context: moc) newFeed.id = UUID() diff --git a/Leganto/ContentView.swift b/Leganto/ContentView.swift index 00f196e..04c03be 100644 --- a/Leganto/ContentView.swift +++ b/Leganto/ContentView.swift @@ -18,7 +18,21 @@ struct ContentView: View { NavigationView { List { ForEach(feeds) { item in - Text(item.name!) + NavigationLink { + FeedView(source: item) + } label: { + HStack { + if Genre(rawValue: item.genre) == .news { + Image(systemName: "newspaper") + } else if Genre(rawValue: item.genre) == .technology { + Image(systemName: "laptopcomputer") + } else { + Image(systemName: "doc") + } + + Text(item.name!) + } + } } .onDelete(perform: deleteItems) } @@ -56,5 +70,6 @@ struct ContentView: View { struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() + .environment(\.managedObjectContext, DataController.preview.container.viewContext) } } diff --git a/Leganto/DataController.swift b/Leganto/DataController.swift index 066f9da..9dcbfc9 100644 --- a/Leganto/DataController.swift +++ b/Leganto/DataController.swift @@ -5,17 +5,74 @@ // Created by Louis Hollingworth on 2023-05-21. // -import Foundation import CoreData class DataController: ObservableObject { - let container = NSPersistentContainer(name: "Leganto") + static let shared = DataController() - init() { - container.loadPersistentStores { desc, err in - if let err = err { - print("Core Data failed to load: \(err.localizedDescription)") + static var preview: DataController { + let result = DataController(inMemory: true) + let viewContext = result.container.viewContext + + for i in 0..<3 { + for j in 1...3 { + let newFeed = Feed(context: viewContext) + switch i { + case 2: + newFeed.genre = Genre.technology.rawValue + break + case 3: + newFeed.genre = Genre.news.rawValue + break + default: + newFeed.genre = Genre.uncategorised.rawValue + break + } + + newFeed.name = "\(i+j)" + newFeed.dateAdded = Date() + newFeed.dateUpdated = Date() + newFeed.desc = "\(i+j)" + newFeed.url = "https://git.ludoviko.ch/lucxjo/leganto.rss" } } + + do { + try viewContext.save() + } catch { + let nsError = error as NSError + fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + + } + + return result + } + + let container: NSPersistentCloudKitContainer + + init(inMemory: Bool = false) { + container = NSPersistentCloudKitContainer(name: "Leganto") + if inMemory { + container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") + } + + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error as NSError? { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + + /* + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + + container.viewContext.automaticallyMergesChangesFromParent = true } } diff --git a/Leganto/FeedView.swift b/Leganto/FeedView.swift new file mode 100644 index 0000000..89cac57 --- /dev/null +++ b/Leganto/FeedView.swift @@ -0,0 +1,21 @@ +// +// FeedView.swift +// Leganto +// +// Created by Louis Hollingworth on 2023-05-23. +// + +import SwiftUI + +struct FeedView: View { + var source: Feed + + var body: some View { + List { + ForEach(1..<20) { item in + FeedViewCell() + } + } + .navigationTitle(source.name!) + } +} diff --git a/Leganto/FeedViewCell.swift b/Leganto/FeedViewCell.swift new file mode 100644 index 0000000..5f7299f --- /dev/null +++ b/Leganto/FeedViewCell.swift @@ -0,0 +1,32 @@ +// +// FeedViewCell.swift +// Leganto +// +// Created by Louis Hollingworth on 2023-05-23. +// + +import SwiftUI + +struct FeedViewCell: View { + var body: some View { + VStack(alignment: .leading) { + Text("Title") + .font(.title) + HStack { + Text("Author") + Spacer() + Text("Date") + } + .font(.footnote) + Text("Content") + .font(.body) + } + .multilineTextAlignment(.leading) + } +} + +struct FeedViewCell_Previews: PreviewProvider { + static var previews: some View { + FeedViewCell() + } +}