SubscriptionListView.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import SwiftUI
  2. import CoreData
  3. import FirebaseMessaging
  4. import UserNotifications
  5. struct SubscriptionListView: View {
  6. let tag = "SubscriptionList"
  7. @EnvironmentObject private var store: Store
  8. @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Subscription.topic, ascending: true)]) var subscriptions: FetchedResults<Subscription>
  9. @State private var showingAddDialog = false
  10. private var subscriptionManager: SubscriptionManager {
  11. return SubscriptionManager(store: store)
  12. }
  13. var body: some View {
  14. NavigationView {
  15. if #available(iOS 15.0, *) {
  16. subscriptionList
  17. .refreshable {
  18. subscriptions.forEach { subscription in
  19. subscriptionManager.poll(subscription)
  20. }
  21. }
  22. } else {
  23. subscriptionList
  24. .toolbar {
  25. ToolbarItem(placement: .navigationBarLeading) {
  26. Button {
  27. subscriptions.forEach { subscription in
  28. subscriptionManager.poll(subscription)
  29. }
  30. } label: {
  31. Image(systemName: "arrow.clockwise")
  32. }
  33. }
  34. }
  35. }
  36. }
  37. .navigationViewStyle(StackNavigationViewStyle())
  38. }
  39. private var subscriptionList: some View {
  40. List {
  41. ForEach(subscriptions) { subscription in
  42. SubscriptionItemNavView(subscription: subscription)
  43. }
  44. }
  45. .listStyle(PlainListStyle())
  46. .navigationTitle("Subscribed topics")
  47. .toolbar {
  48. ToolbarItem(placement: .navigationBarTrailing) {
  49. Button {
  50. self.showingAddDialog = true
  51. } label: {
  52. Image(systemName: "plus")
  53. }
  54. }
  55. }
  56. .overlay(Group {
  57. if subscriptions.isEmpty {
  58. VStack {
  59. Text("It looks like you don't have any subscriptions yet")
  60. .font(.title2)
  61. .foregroundColor(.gray)
  62. .multilineTextAlignment(.center)
  63. .padding(.bottom)
  64. if #available(iOS 15.0, *) {
  65. Text("Click the + to create or subscribe to a topic. Afterwards, you receive notifications on your device when sending messages via PUT or POST.\n\nDetailed instructions are available on [ntfy.sh](https://ntfy.sh) and [in the docs](https://ntfy.sh/docs).")
  66. .foregroundColor(.gray)
  67. } else {
  68. Text("Click the + to create or subscribe to a topic. Afterwards, you receive notifications on your device when sending messages via PUT or POST.\n\nDetailed instructions are available on https://ntfy.sh and https://ntfy.sh/docs")
  69. .foregroundColor(.gray)
  70. }
  71. }
  72. .padding(40)
  73. }
  74. })
  75. .sheet(isPresented: $showingAddDialog) {
  76. SubscriptionAddView(isShowing: $showingAddDialog)
  77. }
  78. }
  79. }
  80. struct SubscriptionItemNavView: View {
  81. @EnvironmentObject private var store: Store
  82. @EnvironmentObject private var delegate: AppDelegate
  83. @ObservedObject var subscription: Subscription
  84. @State private var unsubscribeAlert = false
  85. private var subscriptionManager: SubscriptionManager {
  86. return SubscriptionManager(store: store)
  87. }
  88. var body: some View {
  89. if #available(iOS 15.0, *) {
  90. subscriptionRow
  91. .swipeActions(edge: .trailing) {
  92. Button(role: .destructive) {
  93. self.unsubscribeAlert = true
  94. } label: {
  95. Label("Delete", systemImage: "trash.circle")
  96. }
  97. }
  98. } else {
  99. subscriptionRow
  100. }
  101. }
  102. private var subscriptionRow: some View {
  103. ZStack {
  104. NavigationLink(
  105. destination: NotificationListView(subscription: subscription),
  106. tag: subscription.urlString(),
  107. selection: $delegate.selectedBaseUrl
  108. ) {
  109. EmptyView()
  110. }
  111. .opacity(0.0)
  112. .buttonStyle(PlainButtonStyle())
  113. SubscriptionItemRowView(subscription: subscription)
  114. }
  115. .alert(isPresented: $unsubscribeAlert) {
  116. Alert(
  117. title: Text("Unsubscribe"),
  118. message: Text("Do you really want to unsubscribe from this topic and delete all of the notifications you received?"),
  119. primaryButton: .destructive(
  120. Text("Unsubscribe"),
  121. action: {
  122. self.subscriptionManager.unsubscribe(subscription)
  123. self.unsubscribeAlert = false
  124. }
  125. ),
  126. secondaryButton: .cancel()
  127. )
  128. }
  129. }
  130. }
  131. struct SubscriptionItemRowView: View {
  132. @ObservedObject var subscription: Subscription
  133. var body: some View {
  134. let totalNotificationCount = subscription.notificationCount()
  135. VStack(alignment: .leading, spacing: 0) {
  136. HStack {
  137. Text(subscription.displayName())
  138. .font(.headline)
  139. .bold()
  140. .lineLimit(1)
  141. Spacer()
  142. Text(subscription.lastNotification()?.shortDateTime() ?? "")
  143. .font(.subheadline)
  144. .foregroundColor(.gray)
  145. Image(systemName: "chevron.forward")
  146. .font(.system(size: 12.0))
  147. .foregroundColor(.gray)
  148. }
  149. Spacer()
  150. Text("\(totalNotificationCount) notification\(totalNotificationCount != 1 ? "s" : "")")
  151. .font(.subheadline)
  152. .foregroundColor(.gray)
  153. }
  154. .padding(.all, 4)
  155. }
  156. }
  157. struct SubscriptionsListView_Previews: PreviewProvider {
  158. static var previews: some View {
  159. let store = Store.preview // Store.previewEmpty
  160. SubscriptionListView()
  161. .environment(\.managedObjectContext, store.context)
  162. .environmentObject(store)
  163. }
  164. }