Merge ios-14 into main
|
|
@ -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 */,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 729 B After Width: | Height: | Size: 729 B |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 985 B After Width: | Height: | Size: 985 B |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
12
ntfy/Assets/Assets.xcassets/priority-1.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "priority-1.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
ntfy/Assets/Assets.xcassets/priority-1.imageset/priority-1.svg
vendored
Normal 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 |
12
ntfy/Assets/Assets.xcassets/priority-2.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "priority-2.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
ntfy/Assets/Assets.xcassets/priority-2.imageset/priority-2.svg
vendored
Normal 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 |
12
ntfy/Assets/Assets.xcassets/priority-3.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "priority-3.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
ntfy/Assets/Assets.xcassets/priority-3.imageset/priority-3.svg
vendored
Normal 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 |
12
ntfy/Assets/Assets.xcassets/priority-4.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "priority-4.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
ntfy/Assets/Assets.xcassets/priority-4.imageset/priority-4.svg
vendored
Normal 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 |
12
ntfy/Assets/Assets.xcassets/priority-5.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "priority-5.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
ntfy/Assets/Assets.xcassets/priority-5.imageset/priority-5.svg
vendored
Normal 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
|
|
@ -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]?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
42
ntfy/Utils/EmojiManager.swift
Normal 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]
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||