Merge ios-14 into main

This commit is contained in:
Callum Yarnold 2022-05-25 18:01:25 +01:00
commit b0e0c8fc83
No known key found for this signature in database
GPG key ID: 1B309601199B0473
55 changed files with 23055 additions and 49 deletions

View file

@ -35,6 +35,10 @@
94A3F7C8283734D900C48E79 /* SubscriptionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A3F7C7283734D900C48E79 /* SubscriptionManager.swift */; };
94A3F7CA28386B2100C48E79 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A3F7C928386B2100C48E79 /* Config.swift */; };
94A3F7CB28386B2100C48E79 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A3F7C928386B2100C48E79 /* Config.swift */; };
94CD1966283E662900973B93 /* emojis.json in Resources */ = {isa = PBXBuildFile; fileRef = 94CD1965283E662900973B93 /* emojis.json */; };
94CD1967283E662900973B93 /* emojis.json in Resources */ = {isa = PBXBuildFile; fileRef = 94CD1965283E662900973B93 /* emojis.json */; };
94CD196A283E666900973B93 /* EmojiManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD1969283E666900973B93 /* EmojiManager.swift */; };
94CD196B283E666900973B93 /* EmojiManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD1969283E666900973B93 /* EmojiManager.swift */; };
94E9196C28353E0100F30170 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F216283531A200CDE4DD /* Log.swift */; };
/* End PBXBuildFile section */
@ -71,7 +75,7 @@
9474F1C7282F2AA800CDE4DD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
9474F1D1282F2D2C00CDE4DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
9474F1D5282F2FED00CDE4DD /* ntfy.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ntfy.entitlements; sourceTree = "<group>"; };
9474F1D6282F2FF700CDE4DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
9474F1D6282F2FF700CDE4DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9474F1DB282F30B500CDE4DD /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
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>"; };
@ -89,6 +93,8 @@
9474F216283531A200CDE4DD /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
94A3F7C7283734D900C48E79 /* SubscriptionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionManager.swift; sourceTree = "<group>"; };
94A3F7C928386B2100C48E79 /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = "<group>"; };
94CD1965283E662900973B93 /* emojis.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = emojis.json; sourceTree = "<group>"; };
94CD1969283E666900973B93 /* EmojiManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiManager.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -132,14 +138,13 @@
9474F1BF282F2AA700CDE4DD /* ntfy */ = {
isa = PBXGroup;
children = (
9474F210283326E000CDE4DD /* Utils */,
9474F20D2833264F00CDE4DD /* App */,
9474F1C4282F2AA800CDE4DD /* Assets.xcassets */,
9474F1DB282F30B500CDE4DD /* GoogleService-Info.plist */,
9474F1D6282F2FF700CDE4DD /* Info.plist */,
9474F1D5282F2FED00CDE4DD /* ntfy.entitlements */,
9474F1D6282F2FF700CDE4DD /* Info.plist */,
9474F20D2833264F00CDE4DD /* App */,
94CD1968283E663700973B93 /* Assets */,
9474F2032831725A00CDE4DD /* Persistence */,
9474F1C6282F2AA800CDE4DD /* Preview Content */,
9474F210283326E000CDE4DD /* Utils */,
9474F202283170F000CDE4DD /* Views */,
);
path = ntfy;
@ -207,6 +212,7 @@
isa = PBXGroup;
children = (
9474F20E283326C500CDE4DD /* ApiService.swift */,
94CD1969283E666900973B93 /* EmojiManager.swift */,
9474F211283327C200CDE4DD /* Helpers.swift */,
94A3F7C928386B2100C48E79 /* Config.swift */,
9474F216283531A200CDE4DD /* Log.swift */,
@ -214,6 +220,16 @@
path = Utils;
sourceTree = "<group>";
};
94CD1968283E663700973B93 /* Assets */ = {
isa = PBXGroup;
children = (
9474F1C4282F2AA800CDE4DD /* Assets.xcassets */,
94CD1965283E662900973B93 /* emojis.json */,
9474F1DB282F30B500CDE4DD /* GoogleService-Info.plist */,
);
path = Assets;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -303,6 +319,7 @@
files = (
9474F1C8282F2AA800CDE4DD /* Preview Assets.xcassets in Resources */,
9474F1C5282F2AA800CDE4DD /* Assets.xcassets in Resources */,
94CD1966283E662900973B93 /* emojis.json in Resources */,
9474F1DC282F30B500CDE4DD /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -311,6 +328,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
94CD1967283E662900973B93 /* emojis.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -334,6 +352,7 @@
9474F1F22830825600CDE4DD /* SubscriptionListView.swift in Sources */,
9474F1FD2831311A00CDE4DD /* SubscriptionAddView.swift in Sources */,
9474F1FF28316ACE00CDE4DD /* Subscription.swift in Sources */,
94CD196A283E666900973B93 /* EmojiManager.swift in Sources */,
9474F1C1282F2AA700CDE4DD /* AppMain.swift in Sources */,
9474F20F283326C500CDE4DD /* ApiService.swift in Sources */,
9474F1F72830830700CDE4DD /* ntfy.xcdatamodeld in Sources */,
@ -348,6 +367,7 @@
94E9196C28353E0100F30170 /* Log.swift in Sources */,
9474F2152834758700CDE4DD /* Helpers.swift in Sources */,
9474F1E7282F3FFD00CDE4DD /* NotificationService.swift in Sources */,
94CD196B283E666900973B93 /* EmojiManager.swift in Sources */,
9474F2052831D51500CDE4DD /* Store.swift in Sources */,
9474F2062831D73C00CDE4DD /* ntfy.xcdatamodeld in Sources */,
94A3F7CB28386B2100C48E79 /* Config.swift in Sources */,

View file

@ -30,8 +30,8 @@
filePath = "ntfyNSE/NotificationService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "38"
endingLineNumber = "38"
startingLineNumber = "70"
endingLineNumber = "70"
landmarkName = "didReceive(_:withContentHandler:)"
landmarkType = "7">
</BreakpointContent>
@ -46,8 +46,8 @@
filePath = "ntfyNSE/NotificationService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "26"
endingLineNumber = "26"
startingLineNumber = "32"
endingLineNumber = "32"
landmarkName = "didReceive(_:withContentHandler:)"
landmarkType = "7">
</BreakpointContent>
@ -62,8 +62,8 @@
filePath = "ntfyNSE/NotificationService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "29"
endingLineNumber = "29"
startingLineNumber = "33"
endingLineNumber = "33"
landmarkName = "didReceive(_:withContentHandler:)"
landmarkType = "7">
</BreakpointContent>
@ -78,8 +78,8 @@
filePath = "ntfyNSE/NotificationService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "37"
endingLineNumber = "37"
startingLineNumber = "69"
endingLineNumber = "69"
landmarkName = "didReceive(_:withContentHandler:)"
landmarkType = "7">
</BreakpointContent>

View file

@ -65,8 +65,12 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
let userInfo = response.notification.request.content.userInfo
Log.d(tag, "Notification received via userNotificationCenter(didReceive)", userInfo)
if let topic = userInfo["topic"] as? String {
selectedBaseUrl = topicUrl(baseUrl: Config.appBaseUrl, topic: topic)
let clickUrl = URL(string: userInfo["click"] as? String ?? "")
let topic = userInfo["topic"] as? String ?? ""
if let clickUrl = clickUrl {
UIApplication.shared.open(clickUrl, options: [:], completionHandler: nil)
} else if topic != "" {
selectedBaseUrl = topicUrl(baseUrl: Config.appBaseUrl, topic: topic)
}
completionHandler()

View file

@ -1,14 +1,9 @@
import SwiftUI
import Firebase
// Must have before release:
// TODO: Verify whether model version needs to be specified
// TODO: Disallow adding same topic twice!!
// TODO: When opening a topic, the notifications in the Apple notification center should be dismissed automatically
// TODO: Errors are not shown to the user, but instead just logged
// Nice to have
// TODO: Make notification click open detail view
// TODO: Slide up dialog for "add topic"
@main

View file

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View file

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 198 KiB

View file

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View file

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View file

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 729 B

After

Width:  |  Height:  |  Size: 729 B

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 985 B

After

Width:  |  Height:  |  Size: 985 B

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View file

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View file

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "priority-1.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1 @@
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path style="color:#000;fill:#999;fill-opacity:1;stroke-width:.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none" d="M12.195 20.828a1.275 1.275 0 0 0 .662-.185l6.646-4.037a1.275 1.275 0 0 0 .428-1.751 1.275 1.275 0 0 0-1.751-.428l-5.985 3.635-5.985-3.635a1.275 1.275 0 0 0-1.75.428 1.275 1.275 0 0 0 .427 1.75l6.646 4.038a1.275 1.275 0 0 0 .662.185z"/><path style="color:#000;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none" d="M12.195 15.694a1.275 1.275 0 0 0 .662-.185l6.646-4.037a1.275 1.275 0 0 0 .428-1.751 1.275 1.275 0 0 0-1.751-.428l-5.985 3.635L6.21 9.293a1.275 1.275 0 0 0-1.75.428 1.275 1.275 0 0 0 .427 1.75l6.646 4.038a1.275 1.275 0 0 0 .662.185z"/><path style="color:#000;fill:#ccc;fill-opacity:1;stroke:none;stroke-width:.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none" d="M12.117 10.427a1.275 1.275 0 0 0 .661-.185l6.647-4.038a1.275 1.275 0 0 0 .428-1.75 1.275 1.275 0 0 0-1.751-.428L12.117 7.66 6.132 4.026a1.275 1.275 0 0 0-1.751.427 1.275 1.275 0 0 0 .428 1.751l6.646 4.038a1.275 1.275 0 0 0 .662.185z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "priority-2.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1 @@
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path style="color:#000;fill:#999;fill-opacity:1;stroke-width:.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none" d="M12.173 17.774a1.275 1.275 0 0 0 .661-.185l6.647-4.037a1.275 1.275 0 0 0 .427-1.751 1.275 1.275 0 0 0-1.75-.428l-5.985 3.635-5.985-3.635a1.275 1.275 0 0 0-1.751.428 1.275 1.275 0 0 0 .428 1.751l6.646 4.037a1.275 1.275 0 0 0 .662.185z"/><path style="color:#000;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none" d="M12.173 12.64a1.275 1.275 0 0 0 .661-.185l6.647-4.037a1.275 1.275 0 0 0 .427-1.751 1.275 1.275 0 0 0-1.75-.428l-5.985 3.635L6.188 6.24a1.275 1.275 0 0 0-1.751.428 1.275 1.275 0 0 0 .428 1.75l6.646 4.038a1.275 1.275 0 0 0 .662.185z"/></svg>

After

Width:  |  Height:  |  Size: 833 B

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "priority-3.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1 @@
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M4.882 16.057c-.33-.114-.636-.42-.795-.797-.084-.202-.1-.292-.1-.578-.001-.302.011-.366.116-.6a1.44 1.44 0 01.652-.704l.205-.106h14.077l.205.106c.756.39 1.01 1.376.548 2.128a1.217 1.217 0 01-.588.515l-.201.091-6.985-.001c-5.641-.002-7.013-.012-7.134-.054zM4.858 10.595c-.33-.114-.635-.42-.794-.797-.085-.201-.1-.292-.101-.578 0-.302.012-.366.116-.6a1.44 1.44 0 01.653-.704l.205-.106h14.076l.205.106c.757.39 1.01 1.377.548 2.128a1.217 1.217 0 01-.587.515l-.202.092-6.984-.002c-5.642-.002-7.013-.012-7.135-.054z" fill="#0091ff"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "priority-4.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1 @@
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path style="color:#000;fill:#c60000;fill-opacity:1;stroke:none;stroke-width:.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none" d="M12.117 6.54a1.275 1.275 0 0 0-.662.185L4.81 10.762a1.275 1.275 0 0 0-.428 1.75 1.275 1.275 0 0 0 1.751.429l5.985-3.636 5.985 3.636a1.275 1.275 0 0 0 1.75-.428 1.275 1.275 0 0 0-.427-1.751L12.78 6.725a1.275 1.275 0 0 0-.662-.186Z"/><path style="color:#000;fill:#de0000;fill-opacity:1;stroke:none;stroke-width:.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none" d="M12.195 11.807a1.275 1.275 0 0 0-.662.185l-6.646 4.037a1.275 1.275 0 0 0-.428 1.751 1.275 1.275 0 0 0 1.751.428l5.985-3.635 5.985 3.635a1.275 1.275 0 0 0 1.75-.428 1.275 1.275 0 0 0-.427-1.75l-6.646-4.038a1.275 1.275 0 0 0-.662-.185z"/></svg>

After

Width:  |  Height:  |  Size: 847 B

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "priority-5.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1 @@
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path style="color:#000;fill:#a00;fill-opacity:1;stroke-width:.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none" d="M12.117 3.405a1.275 1.275 0 0 0-.662.185L4.81 7.628a1.275 1.275 0 0 0-.428 1.75 1.275 1.275 0 0 0 1.751.428l5.985-3.635 5.985 3.635a1.275 1.275 0 0 0 1.75-.427 1.275 1.275 0 0 0-.427-1.751L12.779 3.59a1.275 1.275 0 0 0-.662-.185Z"/><path style="color:#000;fill:#c60000;fill-opacity:1;stroke:none;stroke-width:.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none" d="M12.117 8.54a1.275 1.275 0 0 0-.662.185L4.81 12.762a1.275 1.275 0 0 0-.428 1.75 1.275 1.275 0 0 0 1.751.429l5.985-3.636 5.985 3.636a1.275 1.275 0 0 0 1.75-.428 1.275 1.275 0 0 0-.427-1.751L12.78 8.725a1.275 1.275 0 0 0-.662-.186Z"/><path style="color:#000;fill:#de0000;fill-opacity:1;stroke:none;stroke-width:.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none" d="M12.195 13.807a1.275 1.275 0 0 0-.662.185l-6.646 4.037a1.275 1.275 0 0 0-.428 1.751 1.275 1.275 0 0 0 1.751.428l5.985-3.635 5.985 3.635a1.275 1.275 0 0 0 1.75-.428 1.275 1.275 0 0 0-.427-1.75l-6.646-4.038a1.275 1.275 0 0 0-.662-.185z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

22747
ntfy/Assets/emojis.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -23,6 +23,41 @@ extension Notification {
return dateFormatter.string(from: date)
}
func formatMessage() -> String {
let message = message ?? ""
if let title = title, title != "" {
return message
}
let emojiTags = emojiTags()
if !emojiTags.isEmpty {
return emojiTags.joined(separator: "") + " " + message
}
return message
}
func formatTitle() -> String? {
if let title = title, title != "" {
let emojiTags = emojiTags()
if !emojiTags.isEmpty {
return emojiTags.joined(separator: "") + " " + title
}
return title
}
return nil
}
func allTags() -> [String] {
return parseAllTags(tags)
}
func emojiTags() -> [String] {
return parseEmojiTags(tags)
}
func nonEmojiTags() -> [String] {
return parseNonEmojiTags(tags)
}
}
/// This is the "on the wire" message as it is received from the ntfy server
@ -31,4 +66,6 @@ struct Message: Decodable {
var time: Int64
var message: String?
var title: String?
var priority: Int16?
var tags: [String]?
}

View file

@ -89,20 +89,18 @@ class Store: ObservableObject {
Log.d(Store.tag, "Subscription for topic \(topic) unknown")
return
}
do {
let notification = Notification(context: context)
notification.id = id
notification.time = timeInt
notification.message = message
notification.title = userInfo["title"] as? String ?? ""
subscription.addToNotifications(notification)
subscription.lastNotificationId = id
try context.save()
} catch let error {
Log.w(Store.tag, "Cannot store notification (fromUserInfo)", error)
rollbackAndRefresh()
}
let title = userInfo["title"] as? String ?? ""
let priority = Int16(userInfo["priority"] as? String ?? "3") ?? 3
let tags = (userInfo["tags"] as? String ?? "").components(separatedBy: ",")
let m = Message(
id: id,
time: timeInt,
message: message,
title: title,
priority: priority,
tags: tags
)
save(notificationFromMessage: m, withSubscription: subscription)
}
func save(notificationFromMessage message: Message, withSubscription subscription: Subscription) {
@ -112,6 +110,8 @@ class Store: ObservableObject {
notification.time = message.time
notification.message = message.message ?? ""
notification.title = message.title ?? ""
notification.priority = (message.priority != nil && message.priority != 0) ? message.priority! : 3
notification.tags = message.tags?.joined(separator: ",") ?? ""
subscription.addToNotifications(notification)
subscription.lastNotificationId = message.id
try context.save()
@ -177,9 +177,9 @@ class Store: ObservableObject {
extension Store {
static let sampleData = [
"stats": [
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)
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", priority: 4, tags: ["smile", "server123", "de"]),
Message(id: "2", time: 1653058956, message: "201 users/h\n80 IPs", title: "This is a title", priority: 1, tags: []),
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, priority: 5, tags: ["facepalm"])
],
"backups": [],
"announcements": [],
@ -218,6 +218,8 @@ extension Store {
notification.time = message.time
notification.message = message.message
notification.title = message.title
notification.priority = message.priority ?? 3
notification.tags = message.tags?.joined(separator: ",") ?? ""
return notification
}
}

View file

@ -0,0 +1,42 @@
import Foundation
struct Emoji: Decodable {
let emoji: String
let aliases: [String]
let tags: [String]
func getUnicode() -> String {
return emoji
}
}
class EmojiManager {
private static let tag = "EmojiManager"
private static var emojis: Dictionary<String, Emoji> = [:]
static let shared = EmojiManager()
init() {
// emojis.json pulled from https://github.com/github/gemoji/blob/master/db/emoji.json
if let url = Bundle.main.url(forResource: "emojis", withExtension: "json") {
do {
let jsonData = try Data(contentsOf: url)
if let jsonEmojis = try? JSONDecoder().decode([Emoji].self, from: jsonData) {
for emoji in jsonEmojis {
if !emoji.aliases.isEmpty {
EmojiManager.emojis[emoji.aliases.first!] = emoji
}
}
}
} catch {
Log.e(EmojiManager.tag, "Unable to load emojis: \(error.localizedDescription)", error)
}
}
}
func getEmojiByAlias(alias: String) -> Emoji? {
if alias.isEmpty {
return nil
}
return EmojiManager.emojis[alias]
}
}

View file

@ -9,3 +9,23 @@ func topicShortUrl(baseUrl: String, topic: String) -> String {
.replacingOccurrences(of: "http://", with: "")
.replacingOccurrences(of: "https://", with: "")
}
func parseAllTags(_ tags: String?) -> [String] {
return (tags?.components(separatedBy: ",") ?? [])
.filter { $0.trimmingCharacters(in: [" "]) != "" }
}
func parseEmojiTags(_ tags: String?) -> [String] {
var emojiTags: [String] = []
for tag in parseAllTags(tags) {
if let emoji = EmojiManager.shared.getEmojiByAlias(alias: tag) {
emojiTags.append(emoji.getUnicode())
}
}
return emojiTags
}
func parseNonEmojiTags(_ tags: String?) -> [String] {
return parseAllTags(tags)
.filter { EmojiManager.shared.getEmojiByAlias(alias: $0) == nil }
}

View file

@ -150,6 +150,15 @@ struct NotificationListView: View {
.padding(40)
}
})
<<<<<<< HEAD
=======
.refreshable {
subscriptionManager.poll(subscription)
}
.onAppear {
cancelSubscriptionNotifications()
}
>>>>>>> 6d40c441f96e3a6c7331e3883bdf5df999ce8d66
}
private var editButton: some View {
@ -205,6 +214,26 @@ struct NotificationListView: View {
}
editMode = .inactive
}
private func cancelSubscriptionNotifications() {
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.getDeliveredNotifications { notifications in
let ids = notifications
.filter { notification in
if let topic = notification.request.content.userInfo["topic"] as? String {
return topic == subscription.topic // TODO: This is not enough for selfhosted servers
}
return false
}
.map { notification in
notification.request.identifier
}
if !ids.isEmpty {
Log.d(tag, "Cancelling \(ids.count) notification(s) from notification center")
notificationCenter.removeDeliveredNotifications(withIdentifiers: ids)
}
}
}
}
struct NotificationRowView: View {
@ -228,16 +257,29 @@ struct NotificationRowView: View {
private var notificationRow: some View {
VStack(alignment: .leading, spacing: 0) {
Text(notification.shortDateTime())
.font(.subheadline)
.foregroundColor(.gray)
if let title = notification.title, title != "" {
HStack(alignment: .center, spacing: 2) {
Text(notification.shortDateTime())
.font(.subheadline)
.foregroundColor(.gray)
if [1,2,4,5].contains(notification.priority) {
Image("priority-\(notification.priority)")
.resizable()
.scaledToFit()
.frame(width: 16, height: 16)
}
}
if let title = notification.formatTitle(), title != "" {
Text(title)
.font(.headline)
.bold()
}
Text(notification.message ?? "")
Text(notification.formatMessage())
.font(.body)
if !notification.nonEmojiTags().isEmpty {
Text("Tags: " + notification.nonEmojiTags().joined(separator: ", "))
.font(.subheadline)
.foregroundColor(.gray)
}
}
.padding(.all, 4)
}

View file

@ -7,7 +7,7 @@ import CoreData
/// Note that the app extension does not run as part of the main app, so log messages are not printed in the main Xcode window. To debug,
/// select Debug -> Attach to Process by PID or Name, and select the extension. Don't forget to set a breakpoint, or you're not gonna have a good time.
class NotificationService: UNNotificationServiceExtension {
let tag = "NotificationService"
private let tag = "NotificationService"
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
@ -21,18 +21,50 @@ class NotificationService: UNNotificationServiceExtension {
if let bestAttemptContent = bestAttemptContent {
let userInfo = bestAttemptContent.userInfo
// Get all the things
let topic = userInfo["topic"] as? String ?? ""
let title = userInfo["title"] as? String
let priority = userInfo["priority"] as? String ?? "3"
let tags = userInfo["tags"] as? String
// Set notification title to short URL if there is no title. The title is always set
// by the server, but it may be empty.
if let topic = userInfo["topic"] as? String,
let title = userInfo["title"] as? String {
if title == "" {
bestAttemptContent.title = topicShortUrl(baseUrl: Config.appBaseUrl, topic: topic)
if let title = title, title == "" {
bestAttemptContent.title = topicShortUrl(baseUrl: Config.appBaseUrl, topic: topic)
}
// Emojify title or message
let emojiTags = parseEmojiTags(tags)
if !emojiTags.isEmpty {
if let title = title, title != "" {
bestAttemptContent.title = emojiTags.joined(separator: "") + " " + bestAttemptContent.title
} else {
bestAttemptContent.body = emojiTags.joined(separator: "") + " " + bestAttemptContent.body
}
}
// Play a sound, and group by topic
bestAttemptContent.sound = .default
bestAttemptContent.threadIdentifier = userInfo["topic"] as? String ?? ""
bestAttemptContent.threadIdentifier = topic
// Map priorities to interruption level (light up screen, ...) and relevance (order)
switch priority {
case "1":
bestAttemptContent.interruptionLevel = .passive
bestAttemptContent.relevanceScore = 0
case "2":
bestAttemptContent.interruptionLevel = .passive
bestAttemptContent.relevanceScore = 0.25
case "4":
bestAttemptContent.interruptionLevel = .timeSensitive
bestAttemptContent.relevanceScore = 0.75
case "5":
bestAttemptContent.interruptionLevel = .critical
bestAttemptContent.relevanceScore = 1
default:
bestAttemptContent.interruptionLevel = .active
bestAttemptContent.relevanceScore = 0.5
}
// Save notification to store, and display it
Store.shared.save(notificationFromUserInfo: userInfo)