Initial attachments, tested with picture only so far
This commit is contained in:
parent
954c773500
commit
545f0414ab
8 changed files with 283 additions and 6 deletions
|
|
@ -31,12 +31,33 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
let notificationTopic = userInfo["topic"] as? String,
|
||||
let notificationTimestamp = userInfo["time"] as? String,
|
||||
let notiticationTimestampInt = Int64(notificationTimestamp),
|
||||
let notificationTitle = userInfo["title"] as? String,
|
||||
let notificationMessage = userInfo["message"] as? String {
|
||||
|
||||
print("Attempting to create notification")
|
||||
let notificationTitle = userInfo["title"] as? String ?? ""
|
||||
let notificationPriority = Int(userInfo["priority"] as? String ?? "")
|
||||
let notificationTags = userInfo["tags"] as? String ?? ""
|
||||
|
||||
let attachmentName = userInfo["attachment_name"] as? String ?? "attachment.bin"
|
||||
let attachmentType = userInfo["attachment_type"] as? String ?? ""
|
||||
let attachmentSize = Int64(userInfo["attachment_size"] as? String ?? "0") ?? 0
|
||||
let attachmentUrl = userInfo["attachment_url"] as? String ?? ""
|
||||
let attachmentExpires = Int64(userInfo["attachment_expires"] as? String ?? "0") ?? 0
|
||||
|
||||
if let subscription = Database.current.getSubscription(topic: notificationTopic) {
|
||||
var attachment: NtfyAttachment? = nil
|
||||
if !attachmentUrl.isEmpty {
|
||||
attachment = NtfyAttachment(
|
||||
id: 0,
|
||||
name: attachmentName,
|
||||
type: attachmentType,
|
||||
size: attachmentSize,
|
||||
expires: attachmentExpires,
|
||||
url: attachmentUrl,
|
||||
contentUrl: ""
|
||||
)
|
||||
}
|
||||
|
||||
let ntfyNotification = NtfyNotification(
|
||||
id: notificationId,
|
||||
subscriptionId: subscription.id,
|
||||
|
|
@ -44,7 +65,8 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
title: notificationTitle,
|
||||
message: notificationMessage,
|
||||
priority: Int(notificationPriority ?? 3),
|
||||
tags: notificationTags.components(separatedBy: ",")
|
||||
tags: notificationTags.components(separatedBy: ","),
|
||||
attachment: attachment
|
||||
)
|
||||
ntfyNotification.save()
|
||||
print("Created notification")
|
||||
|
|
|
|||
85
ntfy-ios/Models/NtfyAttachment.swift
Normal file
85
ntfy-ios/Models/NtfyAttachment.swift
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// NtfyAttachment.swift
|
||||
// ntfy.sh
|
||||
//
|
||||
// Created by Andrew Cope on 2/20/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class NtfyAttachment {
|
||||
var id: Int64!
|
||||
var name: String
|
||||
var type: String
|
||||
var size: Int64
|
||||
var expires: Int64
|
||||
var url: String
|
||||
var contentUrl: String
|
||||
|
||||
init(id: Int64, name: String, type: String = "", size: Int64 = 0, expires: Int64 = 0, url: String = "", contentUrl: String = "") {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.size = size
|
||||
self.expires = expires
|
||||
self.url = url
|
||||
self.contentUrl = contentUrl
|
||||
}
|
||||
|
||||
func save() {
|
||||
Database.current.updateAttachment(attachment: self)
|
||||
}
|
||||
|
||||
func sizeString() -> String {
|
||||
if (self.size < 1000) { return "\(self.size) B" }
|
||||
let exp = Int(log2(Double(self.size)) / log2(1000.0))
|
||||
let unit = ["KB", "MB", "GB", "TB", "PB", "EB"][exp - 1]
|
||||
let number = Double(self.size) / pow(1000, Double(exp))
|
||||
return String(format: "%.1f %@", number, unit)
|
||||
}
|
||||
|
||||
func isDownloaded() -> Bool {
|
||||
return !contentUrl.isEmpty
|
||||
}
|
||||
|
||||
func expiresString() -> String {
|
||||
return "Expires \(self.expires)"
|
||||
}
|
||||
|
||||
func download() {
|
||||
print("Attempting attachment download")
|
||||
guard let attachmentUrl = URL(string: self.url) else { return }
|
||||
print("Attachment URL: \(attachmentUrl)")
|
||||
URLSession.shared.downloadTask(with: attachmentUrl) { (data, response, error) in
|
||||
print("Attachment download complete")
|
||||
print("Response: \(response)")
|
||||
print("Data: \(data)")
|
||||
if let error = error {
|
||||
print("Download attachment error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
if let response = response,
|
||||
let data = data {
|
||||
let fileManager = FileManager.default
|
||||
// Get the App Group path, which is accessed by both the app and the notification service extension
|
||||
if let path = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.ntfy") {
|
||||
guard let fileUrl = URL(string: "\(path)/downloads/\(response.hash)") else { return }
|
||||
do {
|
||||
let parentPath = fileUrl.deletingLastPathComponent()
|
||||
if !fileManager.fileExists(atPath: parentPath.path) {
|
||||
try fileManager.createDirectory(atPath: parentPath.path, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
try fileManager.moveItem(at: data.absoluteURL, to: fileUrl)
|
||||
self.contentUrl = fileUrl.path
|
||||
self.save()
|
||||
print(self.contentUrl)
|
||||
print("Attachment saved to \(fileUrl.path)")
|
||||
} catch {
|
||||
print("Error saving attachment: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
}
|
||||
|
|
@ -17,12 +17,13 @@ class NtfyNotification: Identifiable, Decodable {
|
|||
var message: String
|
||||
var priority: Int
|
||||
var tags: [String]
|
||||
var attachment: NtfyAttachment?
|
||||
|
||||
// Object Properties
|
||||
var emojiTags: [String] = []
|
||||
var nonEmojiTags: [String] = []
|
||||
|
||||
init(id: String, subscriptionId: Int64, timestamp: Int64, title: String, message: String, priority: Int = 3, tags: [String] = []) {
|
||||
init(id: String, subscriptionId: Int64, timestamp: Int64, title: String, message: String, priority: Int = 3, tags: [String] = [], attachment: NtfyAttachment?) {
|
||||
// Initialize values
|
||||
self.id = id
|
||||
self.subscriptionId = subscriptionId
|
||||
|
|
@ -31,6 +32,7 @@ class NtfyNotification: Identifiable, Decodable {
|
|||
self.message = message
|
||||
self.priority = priority
|
||||
self.tags = tags
|
||||
self.attachment = attachment
|
||||
|
||||
// Set notification tags
|
||||
self.setTags()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
class ApiService {
|
||||
class ApiService: NSObject {
|
||||
static let shared = ApiService()
|
||||
|
||||
func poll(subscription: NtfySubscription, completionHandler: @escaping ([NtfyNotification]?, Error?) -> Void) {
|
||||
|
|
@ -32,6 +32,20 @@ class ApiService {
|
|||
}.resume()
|
||||
}
|
||||
|
||||
/*func checkAuth(baseUrl: String, topic: String, user: NtfyUser?) -> Bool {
|
||||
guard let url = URL(string: "\(baseUrl)/\(topic)/auth") else { return false }
|
||||
var request = URLRequest(url: url)
|
||||
if user != nil {
|
||||
let credential = URLCredential(user: user!.username, password: user!.password, persistence: URLCredential.Persistence.none)
|
||||
request
|
||||
}
|
||||
URLSession.shared.dataTask(with: request) { (data, response, error) in
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
}*/
|
||||
|
||||
private func fetchJsonData<T: Decodable>(urlString: String, completionHandler: @escaping ([T]?, Error?) -> ()) {
|
||||
guard let url = URL(string: urlString) else { return }
|
||||
URLSession.shared.dataTask(with: url) { (data, response, error) in
|
||||
|
|
|
|||
|
|
@ -35,6 +35,17 @@ class Database {
|
|||
let notification_message = Expression<String>("message")
|
||||
let notification_priority = Expression<Int>("priority")
|
||||
let notification_tags = Expression<String>("tags")
|
||||
let notification_attachment_id = Expression<Int64>("attachmentId")
|
||||
|
||||
// Attachments Table
|
||||
let attachments = Table("Attachments")
|
||||
var attachment_id = Expression<Int64>("id")
|
||||
let attachment_name = Expression<String>("name")
|
||||
let attachment_type = Expression<String>("type")
|
||||
let attachment_size = Expression<Int64>("size")
|
||||
let attachment_expires = Expression<Int64>("expires")
|
||||
let attachment_url = Expression<String>("url")
|
||||
let attachment_content_url = Expression<String>("contentUrl")
|
||||
|
||||
// Initialize
|
||||
init() {
|
||||
|
|
@ -61,6 +72,18 @@ class Database {
|
|||
table.column(notification_message)
|
||||
table.column(notification_priority)
|
||||
table.column(notification_tags)
|
||||
table.column(notification_attachment_id)
|
||||
})
|
||||
|
||||
// Initialize Attachments Table
|
||||
try db?.run(attachments.create(ifNotExists: true) { table in
|
||||
table.column(attachment_id, primaryKey: .autoincrement)
|
||||
table.column(attachment_name)
|
||||
table.column(attachment_type)
|
||||
table.column(attachment_size)
|
||||
table.column(attachment_expires)
|
||||
table.column(attachment_url)
|
||||
table.column(attachment_content_url)
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
|
|
@ -149,6 +172,21 @@ class Database {
|
|||
}
|
||||
if let result = try db?.prepare(query) {
|
||||
for line in result {
|
||||
var attachment: NtfyAttachment? = nil
|
||||
let attachmentId = try line.get(notification_attachment_id)
|
||||
if attachmentId != 0 {
|
||||
if let attachmentResult = try db?.pluck(attachments.filter(attachment_id == attachmentId)) {
|
||||
attachment = NtfyAttachment(
|
||||
id: try attachmentResult.get(attachment_id),
|
||||
name: try attachmentResult.get(attachment_name),
|
||||
type: try attachmentResult.get(attachment_type),
|
||||
size: try attachmentResult.get(attachment_size),
|
||||
expires: try attachmentResult.get(attachment_expires),
|
||||
url: try attachmentResult.get(attachment_url),
|
||||
contentUrl: try attachmentResult.get(attachment_content_url)
|
||||
)
|
||||
}
|
||||
}
|
||||
list.append(
|
||||
NtfyNotification(
|
||||
id: try line.get(notification_id),
|
||||
|
|
@ -157,7 +195,8 @@ class Database {
|
|||
title: try line.get(notification_title),
|
||||
message: try line.get(notification_message),
|
||||
priority: try line.get(notification_priority),
|
||||
tags: try line.get(notification_tags).components(separatedBy: ",")
|
||||
tags: try line.get(notification_tags).components(separatedBy: ","),
|
||||
attachment: attachment
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -171,6 +210,10 @@ class Database {
|
|||
|
||||
func addNotification(notification: NtfyNotification) -> NtfyNotification {
|
||||
do {
|
||||
var attachmentId: Int64 = 0
|
||||
if notification.attachment != nil {
|
||||
attachmentId = addAttachment(attachment: notification.attachment!) ?? 0
|
||||
}
|
||||
try db?.run(notifications.insert(
|
||||
notification_id <- notification.id,
|
||||
notification_subscription_id <- notification.subscriptionId,
|
||||
|
|
@ -178,7 +221,8 @@ class Database {
|
|||
notification_title <- notification.title,
|
||||
notification_message <- notification.message,
|
||||
notification_priority <- notification.priority,
|
||||
notification_tags <- notification.tags.joined(separator: ",")
|
||||
notification_tags <- notification.tags.joined(separator: ","),
|
||||
notification_attachment_id <- attachmentId
|
||||
))
|
||||
} catch let Result.error(message, code, _) where code == SQLITE_CONSTRAINT {
|
||||
// Likely means that the notification already exists
|
||||
|
|
@ -220,4 +264,31 @@ class Database {
|
|||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func addAttachment(attachment: NtfyAttachment) -> Int64? {
|
||||
do {
|
||||
return try db?.run(attachments.insert(
|
||||
attachment_name <- attachment.name,
|
||||
attachment_type <- attachment.type,
|
||||
attachment_size <- attachment.size,
|
||||
attachment_expires <- attachment.expires,
|
||||
attachment_url <- attachment.url,
|
||||
attachment_content_url <- attachment.contentUrl
|
||||
))
|
||||
} catch {
|
||||
print("Error saving attachment: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateAttachment(attachment: NtfyAttachment) {
|
||||
do {
|
||||
let dbAttachment = attachments.filter(attachment_id == attachment.id)
|
||||
try db?.run(dbAttachment.update(
|
||||
attachment_content_url <- attachment.contentUrl
|
||||
))
|
||||
} catch {
|
||||
print("Error updating attachment: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
62
ntfy-ios/Views/NotificationAttachmentView.swift
Normal file
62
ntfy-ios/Views/NotificationAttachmentView.swift
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// NotificationAttachmentView.swift
|
||||
// ntfy.sh
|
||||
//
|
||||
// Created by Andrew Cope on 2/20/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
struct NotificationAttachmentView: View {
|
||||
let attachment: NtfyAttachment
|
||||
|
||||
@State private var isAttachmentOpen = false
|
||||
|
||||
var body: some View {
|
||||
if attachment.isDownloaded() {
|
||||
if let imageUrl = UIImage(contentsOfFile: attachment.contentUrl) {
|
||||
let image = Image(uiImage: imageUrl)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
ZStack {
|
||||
image
|
||||
}
|
||||
.onTapGesture {
|
||||
isAttachmentOpen.toggle()
|
||||
}
|
||||
.fullScreenCover(isPresented: $isAttachmentOpen, onDismiss: {
|
||||
// Dismiss logic here
|
||||
}, content: {
|
||||
VStack {
|
||||
image
|
||||
}
|
||||
.onTapGesture {
|
||||
isAttachmentOpen.toggle()
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
HStack {
|
||||
// TODO: Replace paperclip here with mimetype icon
|
||||
Image(systemName: "paperclip")
|
||||
VStack(alignment: .leading) {
|
||||
Text(attachment.name)
|
||||
.font(.footnote)
|
||||
HStack {
|
||||
Text(attachment.sizeString())
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
Text("Not downloaded")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
Text(attachment.expiresString())
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,18 @@ struct NotificationRow: View {
|
|||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
if let attachment = notification.attachment {
|
||||
Spacer()
|
||||
NotificationAttachmentView(attachment: attachment)
|
||||
}
|
||||
}
|
||||
.padding(.all, 4)
|
||||
.onTapGesture {
|
||||
if let attachment = notification.attachment {
|
||||
if !attachment.isDownloaded() {
|
||||
attachment.download()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@
|
|||
80386E9927935CC9009B0480 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 80386E9827935CC9009B0480 /* FirebaseMessaging */; };
|
||||
80386E9F27936087009B0480 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80386E9E27936087009B0480 /* AppDelegate.swift */; };
|
||||
80386EA92793A7AC009B0480 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = 80386EA82793A7AC009B0480 /* SQLite */; };
|
||||
8079FF4C27C2874A00FB3D18 /* NtfyAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8079FF4B27C2874A00FB3D18 /* NtfyAttachment.swift */; };
|
||||
8079FF4D27C2874A00FB3D18 /* NtfyAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8079FF4B27C2874A00FB3D18 /* NtfyAttachment.swift */; };
|
||||
8079FF4F27C29D3300FB3D18 /* NotificationAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8079FF4E27C29D3300FB3D18 /* NotificationAttachmentView.swift */; };
|
||||
80856C7F27BDE0A7008AC8B8 /* ApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80856C7E27BDE0A7008AC8B8 /* ApiService.swift */; };
|
||||
80856C8027BDE0A7008AC8B8 /* ApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80856C7E27BDE0A7008AC8B8 /* ApiService.swift */; };
|
||||
8086EB242794630800C3628A /* AddSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8086EB232794630800C3628A /* AddSubscriptionView.swift */; };
|
||||
|
|
@ -80,6 +83,8 @@
|
|||
80386E9C27935EE9009B0480 /* ntfy-sh-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ntfy-sh-Info.plist"; sourceTree = "<group>"; };
|
||||
80386E9E27936087009B0480 /* AppDelegate.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; tabWidth = 4; usesTabs = 0; };
|
||||
80386EA0279363A2009B0480 /* ntfy.sh.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ntfy.sh.entitlements; sourceTree = "<group>"; };
|
||||
8079FF4B27C2874A00FB3D18 /* NtfyAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NtfyAttachment.swift; sourceTree = "<group>"; };
|
||||
8079FF4E27C29D3300FB3D18 /* NotificationAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAttachmentView.swift; sourceTree = "<group>"; };
|
||||
8081A7F427B4CB67004A8986 /* FEATURE_PARITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = FEATURE_PARITY.md; sourceTree = "<group>"; };
|
||||
80856C7E27BDE0A7008AC8B8 /* ApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiService.swift; sourceTree = "<group>"; };
|
||||
8086EB232794630800C3628A /* AddSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSubscriptionView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -216,6 +221,7 @@
|
|||
80A313FC2793C42000F1A639 /* NotificationRow.swift */,
|
||||
8086EB232794630800C3628A /* AddSubscriptionView.swift */,
|
||||
8086EB2527946FCE00C3628A /* ContentView.swift */,
|
||||
8079FF4E27C29D3300FB3D18 /* NotificationAttachmentView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -233,6 +239,7 @@
|
|||
children = (
|
||||
80A313F42793B1CF00F1A639 /* NtfySubscription.swift */,
|
||||
80A313F62793B56800F1A639 /* NtfyNotification.swift */,
|
||||
8079FF4B27C2874A00FB3D18 /* NtfyAttachment.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -353,6 +360,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
802D626C27B1A37700DDD3AF /* Database.swift in Sources */,
|
||||
8079FF4D27C2874A00FB3D18 /* NtfyAttachment.swift in Sources */,
|
||||
80ED61BF27BCA36A00FCEA36 /* Configuration.swift in Sources */,
|
||||
802D626D27B1A37900DDD3AF /* NtfyNotification.swift in Sources */,
|
||||
800FA49627B19CA0005D05B9 /* NotificationService.swift in Sources */,
|
||||
|
|
@ -375,8 +383,10 @@
|
|||
8086EB242794630800C3628A /* AddSubscriptionView.swift in Sources */,
|
||||
80A313FD2793C42000F1A639 /* NotificationRow.swift in Sources */,
|
||||
80A313FB2793C2EA00F1A639 /* SubscriptionDetail.swift in Sources */,
|
||||
8079FF4C27C2874A00FB3D18 /* NtfyAttachment.swift in Sources */,
|
||||
80A313F92793C0D800F1A639 /* SubscriptionRow.swift in Sources */,
|
||||
80386E802793585B009B0480 /* AppMain.swift in Sources */,
|
||||
8079FF4F27C29D3300FB3D18 /* NotificationAttachmentView.swift in Sources */,
|
||||
80A313F72793B56800F1A639 /* NtfyNotification.swift in Sources */,
|
||||
80856C7F27BDE0A7008AC8B8 /* ApiService.swift in Sources */,
|
||||
80A313F52793B1CF00F1A639 /* NtfySubscription.swift in Sources */,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue