More previews, more cleanup; add alert for unsubscribing
This commit is contained in:
parent
af87a440cd
commit
364f1a6381
8 changed files with 165 additions and 158 deletions
|
|
@ -16,16 +16,14 @@
|
|||
9474F1DC282F30B500CDE4DD /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9474F1DB282F30B500CDE4DD /* GoogleService-Info.plist */; };
|
||||
9474F1E7282F3FFD00CDE4DD /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F1E6282F3FFD00CDE4DD /* NotificationService.swift */; };
|
||||
9474F1EB282F3FFD00CDE4DD /* ntfyNSE.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 9474F1E4282F3FFD00CDE4DD /* ntfyNSE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
9474F1F22830825600CDE4DD /* SubscriptionsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F1F12830825600CDE4DD /* SubscriptionsListView.swift */; };
|
||||
9474F1F22830825600CDE4DD /* SubscriptionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F1F12830825600CDE4DD /* SubscriptionListView.swift */; };
|
||||
9474F1F72830830700CDE4DD /* ntfy.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 9474F1F52830830700CDE4DD /* ntfy.xcdatamodeld */; };
|
||||
9474F1F92830835400CDE4DD /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F1F82830835400CDE4DD /* Store.swift */; };
|
||||
9474F1FB28308A2B00CDE4DD /* SubscriptionRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F1FA28308A2B00CDE4DD /* SubscriptionRowView.swift */; };
|
||||
9474F1FD2831311A00CDE4DD /* SubscriptionAddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F1FC2831311A00CDE4DD /* SubscriptionAddView.swift */; };
|
||||
9474F1FF28316ACE00CDE4DD /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F1FE28316ACE00CDE4DD /* Subscription.swift */; };
|
||||
9474F2052831D51500CDE4DD /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F1F82830835400CDE4DD /* Store.swift */; };
|
||||
9474F2062831D73C00CDE4DD /* ntfy.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 9474F1F52830830700CDE4DD /* ntfy.xcdatamodeld */; };
|
||||
9474F20928331F3A00CDE4DD /* NotificationListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F20728331F3900CDE4DD /* NotificationListView.swift */; };
|
||||
9474F20A28331F3A00CDE4DD /* NotificationRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F20828331F3A00CDE4DD /* NotificationRowView.swift */; };
|
||||
9474F20C283321C300CDE4DD /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F20B283321C300CDE4DD /* Notification.swift */; };
|
||||
9474F20F283326C500CDE4DD /* ApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F20E283326C500CDE4DD /* ApiService.swift */; };
|
||||
9474F212283327C200CDE4DD /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F211283327C200CDE4DD /* Helpers.swift */; };
|
||||
|
|
@ -74,15 +72,13 @@
|
|||
9474F1E4282F3FFD00CDE4DD /* ntfyNSE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ntfyNSE.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9474F1E6282F3FFD00CDE4DD /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
9474F1E8282F3FFD00CDE4DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
9474F1F12830825600CDE4DD /* SubscriptionsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionsListView.swift; sourceTree = "<group>"; };
|
||||
9474F1F12830825600CDE4DD /* SubscriptionListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionListView.swift; sourceTree = "<group>"; };
|
||||
9474F1F62830830700CDE4DD /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
|
||||
9474F1F82830835400CDE4DD /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = "<group>"; };
|
||||
9474F1FA28308A2B00CDE4DD /* SubscriptionRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionRowView.swift; sourceTree = "<group>"; };
|
||||
9474F1FC2831311A00CDE4DD /* SubscriptionAddView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionAddView.swift; sourceTree = "<group>"; };
|
||||
9474F1FE28316ACE00CDE4DD /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = "<group>"; };
|
||||
9474F2042831CDBF00CDE4DD /* ntfyNSE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ntfyNSE.entitlements; sourceTree = "<group>"; };
|
||||
9474F20728331F3900CDE4DD /* NotificationListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationListView.swift; sourceTree = "<group>"; };
|
||||
9474F20828331F3A00CDE4DD /* NotificationRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationRowView.swift; sourceTree = "<group>"; };
|
||||
9474F20B283321C300CDE4DD /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
|
||||
9474F20E283326C500CDE4DD /* ApiService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiService.swift; sourceTree = "<group>"; };
|
||||
9474F211283327C200CDE4DD /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -175,10 +171,8 @@
|
|||
children = (
|
||||
9474F1C2282F2AA700CDE4DD /* ContentView.swift */,
|
||||
9474F1FC2831311A00CDE4DD /* SubscriptionAddView.swift */,
|
||||
9474F20828331F3A00CDE4DD /* NotificationRowView.swift */,
|
||||
9474F20728331F3900CDE4DD /* NotificationListView.swift */,
|
||||
9474F1FA28308A2B00CDE4DD /* SubscriptionRowView.swift */,
|
||||
9474F1F12830825600CDE4DD /* SubscriptionsListView.swift */,
|
||||
9474F1F12830825600CDE4DD /* SubscriptionListView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -325,12 +319,10 @@
|
|||
9474F217283531A300CDE4DD /* Log.swift in Sources */,
|
||||
9474F20928331F3A00CDE4DD /* NotificationListView.swift in Sources */,
|
||||
94A3F7C8283734D900C48E79 /* SubscriptionManager.swift in Sources */,
|
||||
9474F20A28331F3A00CDE4DD /* NotificationRowView.swift in Sources */,
|
||||
9474F1D2282F2D2C00CDE4DD /* AppDelegate.swift in Sources */,
|
||||
9474F1C3282F2AA700CDE4DD /* ContentView.swift in Sources */,
|
||||
9474F1FB28308A2B00CDE4DD /* SubscriptionRowView.swift in Sources */,
|
||||
9474F20C283321C300CDE4DD /* Notification.swift in Sources */,
|
||||
9474F1F22830825600CDE4DD /* SubscriptionsListView.swift in Sources */,
|
||||
9474F1F22830825600CDE4DD /* SubscriptionListView.swift in Sources */,
|
||||
9474F1FD2831311A00CDE4DD /* SubscriptionAddView.swift in Sources */,
|
||||
9474F1FF28316ACE00CDE4DD /* Subscription.swift in Sources */,
|
||||
9474F1C1282F2AA700CDE4DD /* AppMain.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class Store: ObservableObject {
|
|||
var context: NSManagedObjectContext {
|
||||
return container.viewContext
|
||||
}
|
||||
private var subscriptions: Set<AnyCancellable> = []
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
|
||||
init(inMemory: Bool = false) {
|
||||
let storeUrl = (inMemory) ? URL(fileURLWithPath: "/dev/null") : FileManager.default
|
||||
|
|
@ -49,8 +49,7 @@ class Store: ObservableObject {
|
|||
self.container.viewContext.refreshAllObjects()
|
||||
}
|
||||
}
|
||||
.store(in: &subscriptions)
|
||||
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func saveSubscription(baseUrl: String, topic: String) {
|
||||
|
|
@ -158,10 +157,12 @@ class Store: ObservableObject {
|
|||
|
||||
|
||||
extension Store {
|
||||
private static let topics = [
|
||||
static let sampleData = [
|
||||
"stats": [
|
||||
Message(id: "1", time: 1653048956, message: "200 users/h\n123 IPs", title: nil),
|
||||
Message(id: "2", time: 1653058956, message: "201 users/h\n80 IPs", title: nil)
|
||||
Message(id: "1", time: 1653048956, message: "In the last 24 hours, hyou had 5,000 users across 13 countries visit your website", title: "Record visitor numbers"),
|
||||
Message(id: "2", time: 1653058956, message: "201 users/h\n80 IPs", title: "This is a title"),
|
||||
Message(id: "3", time: 1643058956, message: "This message does not have a title, but is instead super long. Like really really long. It can't be any longer I think. I mean, there is s 4,000 byte limit of the message, so I guess I have to make this 4,000 bytes long. Or do I? 😁 I don't know. It's quite tedious to come up with something so long, so I'll stop now. Bye!", title: nil)
|
||||
|
||||
],
|
||||
"backups": [],
|
||||
"announcements": [],
|
||||
|
|
@ -172,16 +173,16 @@ extension Store {
|
|||
static var preview: Store = {
|
||||
let store = Store(inMemory: true)
|
||||
store.context.perform {
|
||||
topics.forEach { topic, messages in
|
||||
let notifications = messages.map { store.makeNotification(store.context, $0) }
|
||||
store.makeSubscription(store.context, topic, notifications)
|
||||
sampleData.forEach { topic, messages in
|
||||
store.makeSubscription(store.context, topic, messages)
|
||||
}
|
||||
}
|
||||
return store
|
||||
}()
|
||||
|
||||
@discardableResult
|
||||
func makeSubscription(_ context: NSManagedObjectContext, _ topic: String, _ notifications: [Notification]) -> Subscription {
|
||||
func makeSubscription(_ context: NSManagedObjectContext, _ topic: String, _ messages: [Message]) -> Subscription {
|
||||
let notifications = messages.map { makeNotification(context, $0) }
|
||||
let subscription = Subscription(context: context)
|
||||
subscription.baseUrl = appBaseUrl
|
||||
subscription.topic = topic
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ enum ActiveAlert {
|
|||
struct NotificationListView: View {
|
||||
private let tag = "NotificationListView"
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@EnvironmentObject private var store: Store
|
||||
|
||||
|
|
@ -133,7 +132,7 @@ struct NotificationListView: View {
|
|||
}
|
||||
|
||||
private func sendTestNotification() {
|
||||
let possibleTags = ["warning", "skull", "success", "triangular_flag_on_post", "de", "us", "dog", "cat", "rotating_light", "bike", "backup", "rsync", "this-s-a-tag", "ios"]
|
||||
let possibleTags: Array<String> = ["warning", "skull", "success", "triangular_flag_on_post", "de", "us", "dog", "cat", "rotating_light", "bike", "backup", "rsync", "this-s-a-tag", "ios"]
|
||||
let priority = Int.random(in: 1..<6)
|
||||
let tags = Array(possibleTags.shuffled().prefix(Int.random(in: 0..<4)))
|
||||
ApiService.shared.publish(
|
||||
|
|
@ -177,3 +176,33 @@ struct NotificationListView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NotificationRowView: View {
|
||||
let notification: Notification
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(notification.shortDateTime())
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
if let title = notification.title, title != "" {
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
.bold()
|
||||
}
|
||||
Text(notification.message ?? "")
|
||||
.font(.body)
|
||||
}
|
||||
.padding(.all, 4)
|
||||
}
|
||||
}
|
||||
|
||||
struct NotificationListView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let store = Store.preview
|
||||
let subscription = store.makeSubscription(store.context, "stats", Store.sampleData["stats"]!)
|
||||
NotificationListView(subscription: subscription)
|
||||
.environment(\.managedObjectContext, store.context)
|
||||
.environmentObject(store)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
import SwiftUI
|
||||
|
||||
struct NotificationRowView: View {
|
||||
let notification: Notification
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack {
|
||||
Text(notification.title ?? "")
|
||||
.font(.headline)
|
||||
.bold()
|
||||
.lineLimit(1)
|
||||
Spacer()
|
||||
Text(notification.shortDateTime())
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
}
|
||||
Spacer()
|
||||
Text(notification.message ?? "")
|
||||
.font(.body)
|
||||
}
|
||||
.padding(.all, 4)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import SwiftUI
|
||||
import FirebaseMessaging
|
||||
|
||||
struct SubscriptionAddView: View {
|
||||
private let tag = "SubscriptionAddView"
|
||||
|
|
|
|||
119
ntfy/Views/SubscriptionListView.swift
Normal file
119
ntfy/Views/SubscriptionListView.swift
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
// https://www.raywenderlich.com/14958063-modern-efficient-core-data
|
||||
// https://www.hackingwithswift.com/books/ios-swiftui/how-to-combine-core-data-and-swiftui
|
||||
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
import FirebaseMessaging
|
||||
import UserNotifications
|
||||
|
||||
struct SubscriptionListView: View {
|
||||
let tag = "SubscriptionList"
|
||||
|
||||
@EnvironmentObject private var store: Store
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Subscription.topic, ascending: true)]) var subscriptions: FetchedResults<Subscription>
|
||||
|
||||
@State private var unsubscribeAlert = false
|
||||
@State private var unsubscribeSubscription: Subscription?
|
||||
|
||||
private var subscriptionManager: SubscriptionManager {
|
||||
return SubscriptionManager(store: store)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
ForEach(subscriptions) { subscription in
|
||||
ZStack {
|
||||
NavigationLink(destination: NotificationListView(subscription: subscription)) {
|
||||
EmptyView()
|
||||
}
|
||||
.opacity(0.0)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
SubscriptionRowView(subscription: subscription)
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
unsubscribeAlert = true
|
||||
unsubscribeSubscription = subscription
|
||||
//subscriptionManager.unsubscribe(subscription)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash.circle")
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $unsubscribeAlert) {
|
||||
Alert(
|
||||
title: Text("Unsubscribe"),
|
||||
message: Text("Do you really want to unsubscribe from this topic and delete all of the notifications you received?"),
|
||||
primaryButton: .destructive(
|
||||
Text("Unsubscribe"),
|
||||
action: {
|
||||
subscriptionManager.unsubscribe(unsubscribeSubscription!)
|
||||
unsubscribeAlert = false
|
||||
unsubscribeSubscription = nil
|
||||
}
|
||||
),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
.navigationTitle("Subscribed topics")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
NavigationLink(
|
||||
destination: SubscriptionAddView()
|
||||
) {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
.overlay(Group {
|
||||
if subscriptions.isEmpty {
|
||||
Text("No topics")
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
})
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct SubscriptionRowView: View {
|
||||
@ObservedObject var subscription: Subscription
|
||||
|
||||
var body: some View {
|
||||
let totalNotificationCount = subscription.notificationCount()
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack {
|
||||
Text(subscription.displayName())
|
||||
.font(.headline)
|
||||
.bold()
|
||||
.lineLimit(1)
|
||||
Spacer()
|
||||
Text(subscription.lastNotification()?.shortDateTime() ?? "")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
Image(systemName: "chevron.forward")
|
||||
.font(.system(size: 12.0))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Spacer()
|
||||
Text("\(totalNotificationCount) notification\(totalNotificationCount != 1 ? "s" : "")")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.padding(.all, 4)
|
||||
}
|
||||
}
|
||||
|
||||
struct SubscriptionsListView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SubscriptionListView()
|
||||
.environment(\.managedObjectContext, Store.preview.context)
|
||||
.environmentObject(Store.preview)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
//
|
||||
// SubscriptionRow.swift
|
||||
// ntfy.sh
|
||||
//
|
||||
// Created by Andrew Cope on 1/15/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SubscriptionRowView: View {
|
||||
@ObservedObject var subscription: Subscription
|
||||
|
||||
var body: some View {
|
||||
let totalNotificationCount = subscription.notificationCount()
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack {
|
||||
Text(subscription.displayName())
|
||||
.font(.headline)
|
||||
.bold()
|
||||
.lineLimit(1)
|
||||
Spacer()
|
||||
Text(subscription.lastNotification()?.shortDateTime() ?? "")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
Image(systemName: "chevron.forward")
|
||||
.font(.system(size: 12.0))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Spacer()
|
||||
Text("\(totalNotificationCount) notification\(totalNotificationCount != 1 ? "s" : "")")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.padding(.all, 4)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
// https://www.raywenderlich.com/14958063-modern-efficient-core-data
|
||||
// https://www.hackingwithswift.com/books/ios-swiftui/how-to-combine-core-data-and-swiftui
|
||||
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
import FirebaseMessaging
|
||||
import UserNotifications
|
||||
|
||||
struct SubscriptionListView: View {
|
||||
let tag = "SubscriptionList"
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject private var store: Store
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "topic", ascending: true)]) var subscriptions: FetchedResults<Subscription>
|
||||
|
||||
private var subscriptionManager: SubscriptionManager {
|
||||
return SubscriptionManager(store: store)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
ForEach(subscriptions) { subscription in
|
||||
ZStack {
|
||||
NavigationLink(destination: NotificationListView(subscription: subscription)) {
|
||||
EmptyView()
|
||||
}
|
||||
.opacity(0.0)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
SubscriptionRowView(subscription: subscription)
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
subscriptionManager.unsubscribe(subscription)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
.navigationTitle("Subscribed topics")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
NavigationLink(
|
||||
destination: SubscriptionAddView()
|
||||
) {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
.overlay(Group {
|
||||
if subscriptions.isEmpty {
|
||||
Text("No topics")
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
})
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct SubscriptionsList_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SubscriptionListView()
|
||||
.environment(\.managedObjectContext, Store.preview.context)
|
||||
.environmentObject(Store.preview)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue