add rss feed manager view

This commit is contained in:
Duong Thai 2024-03-13 14:15:31 +07:00
parent 52655c6d11
commit 07ce48fe6c
8 changed files with 198 additions and 11 deletions

View file

@ -149,6 +149,9 @@ extension View {
.withEnvironments()
case .addNewRSSFeed:
RSSAddNewFeed()
case .rssFeedManager:
RSSFeedManager()
.withEnvironments()
}
}
}

View file

@ -17,7 +17,8 @@ import CoreData
@MainActor
public struct RSSTab: View {
@FetchRequest(sortDescriptors: [SortDescriptor(\.date, order: .reverse)])
@FetchRequest(sortDescriptors: [SortDescriptor(\.date, order: .reverse)],
predicate: NSPredicate(format: "feed.isShowing == TRUE"))
private var items: FetchedResults<RSSItem>
@Environment(\.managedObjectContext) private var moContext
@ -50,6 +51,17 @@ public struct RSSTab: View {
AppAccountsSelectorView(routerPath: routerPath)
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
Task { @MainActor in
routerPath.presentedSheet = SheetDestination.rssFeedManager
HapticManager.shared.fireHaptic(.buttonPress)
}
} label: {
Image(systemName: "list.bullet.rectangle.portrait")
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
Task { @MainActor in
@ -58,13 +70,6 @@ public struct RSSTab: View {
}
} label: {
Image(systemName: "plus")
.accessibilityLabel("accessibility.tabs.timeline.new-post.label")
.accessibilityInputLabels([
LocalizedStringKey("accessibility.tabs.timeline.new-post.label"),
LocalizedStringKey("accessibility.tabs.timeline.new-post.inputLabel1"),
LocalizedStringKey("accessibility.tabs.timeline.new-post.inputLabel2"),
])
.offset(y: -2)
}
}
}

View file

@ -71,6 +71,7 @@ public enum SheetDestination: Identifiable, Hashable {
case accountEditInfo
case accountFiltersList
case addNewRSSFeed
case rssFeedManager
public var id: String {
switch self {
@ -107,6 +108,8 @@ public enum SheetDestination: Identifiable, Hashable {
"accountFiltersList"
case .addNewRSSFeed:
"addNewRSSFeed"
case .rssFeedManager:
"rssFeedManager"
}
}
}

View file

@ -51,5 +51,6 @@ extension RSSFeed {
}
self.expired = sendableData.parsedFeed.expired
self.isShowing = true
}
}

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23D60" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23E214" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
<entity name="RSSAuthor" representedClassName="RSSAuthor" syncable="YES" codeGenerationType="class">
<attribute name="avatarURL" optional="YES" attributeType="URI"/>
<attribute name="displayName" optional="YES" attributeType="String"/>
@ -19,9 +19,10 @@
<attribute name="expired" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="faviconURL" optional="YES" attributeType="URI"/>
<attribute name="feedDescription" optional="YES" attributeType="String"/>
<attribute name="feedURL" optional="YES" attributeType="URI"/>
<attribute name="feedURL" attributeType="URI"/>
<attribute name="homePageURL" optional="YES" attributeType="URI"/>
<attribute name="iconURL" optional="YES" attributeType="URI"/>
<attribute name="isShowing" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="nextURL" optional="YES" attributeType="URI"/>
<attribute name="title" optional="YES" attributeType="String"/>
<attribute name="type" optional="YES" attributeType="String"/>

View file

@ -0,0 +1,59 @@
//
// RSSFeedManager.swift
//
//
// Created by Duong Thai on 12/3/24.
//
import SwiftUI
import DesignSystem
public struct RSSFeedManager: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.managedObjectContext) private var moContext
@FetchRequest(sortDescriptors: [SortDescriptor(\.title, order: .reverse)])
private var feeds: FetchedResults<RSSFeed>
public var body: some View {
NavigationStack{
List {
ForEach(feeds) { feed in
RSSFeedView(feed)
}
.onDelete { indices in
for index in indices {
moContext.delete(feeds[index])
}
}
}
.listStyle(PlainListStyle())
.navigationTitle("rss.rssFeedManager.title")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button {
dismiss()
moContext.rollback()
} label: {
Image(systemName: "xmark")
}
}
ToolbarItem(placement: .topBarTrailing) {
Button("rss.rssFeedManager.action.done") {
dismiss()
try? moContext.save()
}
}
}
}
.onDisappear { moContext.rollback() }
.environment(Theme.shared)
}
public init() {}
}
#Preview {
RSSFeedManager()
}

View file

@ -0,0 +1,115 @@
//
// SwiftUIView.swift
//
//
// Created by Duong Thai on 12/3/24.
//
import SwiftUI
import DesignSystem
public struct RSSFeedView: View {
@ObservedObject private var feed: RSSFeed
@Environment(Theme.self) private var theme
private var items: ArraySlice<RSSItem> {
((feed.items?.allObjects as? [RSSItem]) ?? [])
.sorted { i0, i1 in
if let d0 = i0.date,
let d1 = i1.date
{ d0 > d1 } else { false }
}.prefix(5)
}
private var contentPadding: CGFloat {
theme.avatarPosition == .top
? 0
: AvatarView.FrameConfig.status.width + .statusColumnsSpacing
}
public var body: some View {
VStack(alignment: .leading, spacing: .statusComponentSpacing) {
headerView()
VStack(alignment: .leading, spacing: theme.lineSpacing) {
if let feedDescription = feed.feedDescription {
Text(feedDescription)
}
Text("rss.rssFeedManager.latestItemsSection.label")
.font(.scaledFootnote)
.fontWeight(.bold)
.lineLimit(1)
ForEach(items) { item in
HStack(alignment: .lastTextBaseline) {
Text("\(item.title ?? "No Title")")
Spacer()
Text(item.date ?? .now, style: .offset)
.foregroundStyle(.secondary)
}
}
.font(.scaledFootnote)
.lineLimit(1)
}
HStack {
Button {
feed.isShowing.toggle()
feed.managedObjectContext?.refreshAllObjects()
} label: {
Image(systemName: "eye")
.foregroundColor(Color(UIColor.secondaryLabel))
.padding(.horizontal, 8)
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
}
Divider()
Button {
feed.managedObjectContext!.delete(feed)
} label: {
Image(systemName: "trash")
.foregroundColor(Color(UIColor.secondaryLabel))
.padding(.horizontal, 8)
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
}
}
.padding(.leading, contentPadding)
.padding(.top, 6)
.buttonStyle(.borderless)
#if targetEnvironment(macCatalyst)
.font(.scaledBody)
#else
.font(.body)
.dynamicTypeSize(.large)
#endif
}
.opacity(feed.isShowing ? 1 : 0.5)
}
public init(_ feed: RSSFeed) {
self.feed = feed
}
private func headerView() -> some View {
HStack {
AvatarView(feed.iconURL ?? feed.faviconURL, config: .status)
.accessibility(addTraits: .isButton)
.contentShape(Circle())
.hoverEffect()
HStack(alignment: .bottom) {
HStack(spacing: 2) {
Text(feed.feedURL?.host() ?? "")
.foregroundColor(theme.labelColor)
Image(systemName: "dot.radiowaves.up.forward")
.foregroundColor(theme.tintColor)
}
.font(.scaledSubheadline)
.fontWeight(.semibold)
.lineLimit(1)
}
}
}
}

View file

@ -1,5 +1,5 @@
//
// SwiftUIView.swift
// RSSItemView.swift
//
//
// Created by Duong Thai on 03/03/2024.