Add push notifications
This commit is contained in:
@ -41,6 +41,9 @@ struct MainView: App {
|
||||
@StateObject
|
||||
private var upload: RemotePush = .init()
|
||||
|
||||
@StateObject
|
||||
private var notifications: NotificationSender = .init()
|
||||
|
||||
@State
|
||||
private var language: ContentLanguage = .english
|
||||
|
||||
@ -68,6 +71,9 @@ struct MainView: App {
|
||||
@State
|
||||
private var showUploadSheet = false
|
||||
|
||||
@State
|
||||
private var showNotificationsSheet = false
|
||||
|
||||
@ViewBuilder
|
||||
var sidebar: some View {
|
||||
switch selection.tab {
|
||||
@ -188,6 +194,11 @@ struct MainView: App {
|
||||
Image(systemSymbol: .squareAndArrowUp)
|
||||
}
|
||||
}
|
||||
ToolbarItem {
|
||||
Button(action: { showNotificationsSheet = true }) {
|
||||
Image(systemSymbol: .bell)
|
||||
}
|
||||
}
|
||||
ToolbarItem {
|
||||
Button(action: saveButtonPressed) {
|
||||
Image(systemSymbol: content.saveState.symbol)
|
||||
@ -235,6 +246,11 @@ struct MainView: App {
|
||||
.environmentObject(content)
|
||||
.environmentObject(upload)
|
||||
}
|
||||
.sheet(isPresented: $showNotificationsSheet) {
|
||||
NotificationSheet()
|
||||
.environmentObject(content)
|
||||
.environmentObject(notifications)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,11 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
final class GeneralSettings: ObservableObject {
|
||||
|
||||
@AppStorage("pushNotificationAccessToken")
|
||||
var pushNotificationAccessToken: String?
|
||||
|
||||
@Published
|
||||
var url: String
|
||||
|
||||
@ -20,13 +24,17 @@ final class GeneralSettings: ObservableObject {
|
||||
@Published
|
||||
var remotePathForUpload: String
|
||||
|
||||
init(url: String, linkPreviewImageWidth: Int, linkPreviewImageHeight: Int, remoteUserForUpload: String, remotePortForUpload: Int, remotePathForUpload: String) {
|
||||
@Published
|
||||
var urlForPushNotification: String?
|
||||
|
||||
init(url: String, linkPreviewImageWidth: Int, linkPreviewImageHeight: Int, remoteUserForUpload: String, remotePortForUpload: Int, remotePathForUpload: String, urlForPushNotification: String?) {
|
||||
self.url = url
|
||||
self.linkPreviewImageWidth = linkPreviewImageWidth
|
||||
self.linkPreviewImageHeight = linkPreviewImageHeight
|
||||
self.remoteUserForUpload = remoteUserForUpload
|
||||
self.remotePortForUpload = remotePortForUpload
|
||||
self.remotePathForUpload = remotePathForUpload
|
||||
self.urlForPushNotification = urlForPushNotification
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +47,8 @@ extension GeneralSettings {
|
||||
linkPreviewImageHeight: data.linkPreviewImageHeight,
|
||||
remoteUserForUpload: data.remoteUserForUpload,
|
||||
remotePortForUpload: data.remotePortForUpload,
|
||||
remotePathForUpload: data.remotePathForUpload)
|
||||
remotePathForUpload: data.remotePathForUpload,
|
||||
urlForPushNotification: data.urlForPushNotification)
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
@ -49,7 +58,8 @@ extension GeneralSettings {
|
||||
linkPreviewImageHeight: linkPreviewImageHeight,
|
||||
remoteUserForUpload: remoteUserForUpload,
|
||||
remotePortForUpload: remotePortForUpload,
|
||||
remotePathForUpload: remotePathForUpload)
|
||||
remotePathForUpload: remotePathForUpload,
|
||||
urlForPushNotification: urlForPushNotification)
|
||||
}
|
||||
|
||||
struct Data: Codable, Equatable {
|
||||
@ -59,5 +69,6 @@ extension GeneralSettings {
|
||||
let remoteUserForUpload: String
|
||||
let remotePortForUpload: Int
|
||||
let remotePathForUpload: String
|
||||
let urlForPushNotification: String?
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,8 @@ extension GeneralSettings {
|
||||
linkPreviewImageHeight: 630,
|
||||
remoteUserForUpload: "user",
|
||||
remotePortForUpload: 22,
|
||||
remotePathForUpload: "/home/user/web")
|
||||
remotePathForUpload: "/home/user/web",
|
||||
urlForPushNotification: nil)
|
||||
}
|
||||
|
||||
extension AudioPlayerSettings {
|
||||
|
21
CHDataManagement/Notifications/NotificationRequest.swift
Normal file
21
CHDataManagement/Notifications/NotificationRequest.swift
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
struct NotificationRequest {
|
||||
|
||||
let accessToken: String
|
||||
|
||||
/// The notificiations in the available languages
|
||||
let notifications: [String : WebNotification]
|
||||
|
||||
init(accessToken: String, notifications: [String : WebNotification]) {
|
||||
self.accessToken = accessToken
|
||||
self.notifications = notifications
|
||||
}
|
||||
|
||||
func notification(for language: String) -> WebNotification? {
|
||||
notifications[language] ?? notifications["en"]
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationRequest: Codable {
|
||||
|
||||
}
|
73
CHDataManagement/Notifications/NotificationSender.swift
Normal file
73
CHDataManagement/Notifications/NotificationSender.swift
Normal file
@ -0,0 +1,73 @@
|
||||
import Foundation
|
||||
|
||||
final class NotificationSender: ObservableObject {
|
||||
|
||||
@Published
|
||||
var isTransmittingToRemote = false
|
||||
|
||||
@Published
|
||||
var lastPushWasSuccessful = true
|
||||
|
||||
init() {
|
||||
|
||||
}
|
||||
|
||||
func failedToCreateNotificationDueToMissingSettings() {
|
||||
guard !isTransmittingToRemote else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.lastPushWasSuccessful = false
|
||||
}
|
||||
}
|
||||
|
||||
func send(url: String, request: NotificationRequest) {
|
||||
guard !isTransmittingToRemote else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.isTransmittingToRemote = true
|
||||
}
|
||||
|
||||
guard let url = URL(string: url) else {
|
||||
print("Invalid notification url: \(url)")
|
||||
didFinish(success: false)
|
||||
return
|
||||
}
|
||||
let body: Data
|
||||
do {
|
||||
body = try JSONEncoder().encode(request)
|
||||
} catch {
|
||||
print("Failed to encode notification: \(error)")
|
||||
didFinish(success: false)
|
||||
return
|
||||
}
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = body
|
||||
|
||||
Task {
|
||||
do {
|
||||
let (_ , result) = try await URLSession.shared.data(for: request)
|
||||
guard let response = result as? HTTPURLResponse else {
|
||||
print("Invalid response \(result)")
|
||||
didFinish(success: false)
|
||||
return
|
||||
}
|
||||
guard response.statusCode == 200 else {
|
||||
print("Failed to send notification: \(response.statusCode)")
|
||||
didFinish(success: false)
|
||||
return
|
||||
}
|
||||
didFinish(success: true)
|
||||
} catch {
|
||||
print("Failed to send notification request: \(error)")
|
||||
didFinish(success: false)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func didFinish(success: Bool) {
|
||||
DispatchQueue.main.async {
|
||||
self.isTransmittingToRemote = false
|
||||
self.lastPushWasSuccessful = success
|
||||
}
|
||||
}
|
||||
}
|
105
CHDataManagement/Notifications/NotificationSheet.swift
Normal file
105
CHDataManagement/Notifications/NotificationSheet.swift
Normal file
@ -0,0 +1,105 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
struct NotificationSheet: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@EnvironmentObject
|
||||
private var notifications: NotificationSender
|
||||
|
||||
@Environment(\.dismiss)
|
||||
private var dismiss
|
||||
|
||||
@State
|
||||
private var englishTitle = ""
|
||||
|
||||
@State
|
||||
private var englishMessage = ""
|
||||
|
||||
@State
|
||||
private var germanTitle = ""
|
||||
|
||||
@State
|
||||
private var germanMessage = ""
|
||||
|
||||
private var uploadSymbol: SFSymbol {
|
||||
if notifications.isTransmittingToRemote {
|
||||
return .squareAndArrowUpBadgeClock
|
||||
}
|
||||
if !notifications.lastPushWasSuccessful {
|
||||
return .squareAndArrowUpTrianglebadgeExclamationmark
|
||||
}
|
||||
return .squareAndArrowUp
|
||||
}
|
||||
|
||||
var header: String {
|
||||
let user = content.settings.general.remoteUserForUpload
|
||||
let port = content.settings.general.remotePortForUpload
|
||||
let url = content.settings.general.remotePathForUpload.withHttpPrefixRemoved
|
||||
let path = content.settings.general.remotePathForUpload
|
||||
return "\(user)@\(url):\(port)/\(path)"
|
||||
}
|
||||
|
||||
var isIncomplete: Bool {
|
||||
englishTitle.isEmpty || germanTitle.isEmpty || englishMessage.isEmpty || germanMessage.isEmpty
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
Text("Send a notification to subscribers")
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
Button("Close", action: { dismiss() })
|
||||
}
|
||||
StringPropertyView(
|
||||
title: "English title",
|
||||
text: $englishTitle,
|
||||
footer: "The title for devices using the english language")
|
||||
|
||||
StringPropertyView(
|
||||
title: "English message",
|
||||
text: $englishMessage,
|
||||
footer: "The message for devices using the english language")
|
||||
|
||||
StringPropertyView(
|
||||
title: "German title",
|
||||
text: $germanTitle,
|
||||
footer: "The title for devices using the german language")
|
||||
|
||||
StringPropertyView(
|
||||
title: "German message",
|
||||
text: $germanMessage,
|
||||
footer: "The message for devices using the german language")
|
||||
|
||||
Button("Send", action: send)
|
||||
.disabled(notifications.isTransmittingToRemote || isIncomplete)
|
||||
}
|
||||
.padding()
|
||||
.frame(minWidth: 300, idealWidth: 400, idealHeight: 500)
|
||||
}
|
||||
|
||||
private func send() {
|
||||
guard let accessToken = content.settings.general.pushNotificationAccessToken,
|
||||
let url = content.settings.general.urlForPushNotification else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let english = WebNotification(title: englishTitle, body: englishMessage)
|
||||
let german = WebNotification(title: germanTitle, body: germanMessage)
|
||||
|
||||
let notifications: [String : WebNotification] = [
|
||||
"en": english,
|
||||
"de": german
|
||||
]
|
||||
|
||||
let request = NotificationRequest.init(
|
||||
accessToken: accessToken,
|
||||
notifications: notifications)
|
||||
|
||||
self.notifications.send(url: url, request: request)
|
||||
}
|
||||
}
|
19
CHDataManagement/Notifications/WebNotification.swift
Normal file
19
CHDataManagement/Notifications/WebNotification.swift
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
struct WebNotification {
|
||||
|
||||
/// The title of the notification
|
||||
/// - Note: Not supported on Safari for iOS
|
||||
let title: String
|
||||
|
||||
/// The main content/message
|
||||
let body: String
|
||||
|
||||
init(title: String, body: String) {
|
||||
self.title = title
|
||||
self.body = body
|
||||
}
|
||||
}
|
||||
|
||||
extension WebNotification: Codable {
|
||||
|
||||
}
|
@ -37,6 +37,16 @@ struct GeneralSettingsDetailView: View {
|
||||
title: "Upload Folder",
|
||||
text: $generalSettings.remotePathForUpload,
|
||||
footer: "The path to the folder on the server where the files should be uploaded to")
|
||||
|
||||
OptionalStringPropertyView(
|
||||
title: "Push Notification URL",
|
||||
text: $generalSettings.urlForPushNotification,
|
||||
footer: "The url to send push notifications to")
|
||||
|
||||
OptionalStringPropertyView(
|
||||
title: "Push Notification Access Token",
|
||||
text: $generalSettings.pushNotificationAccessToken,
|
||||
footer: "The access token to use for sending push notifications")
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
Reference in New Issue
Block a user