Remove user auth, refresh subscription view when adding new sub
This commit is contained in:
parent
22fb9748b7
commit
676626998c
13 changed files with 39 additions and 254 deletions
|
|
@ -12,9 +12,11 @@ struct AppMain: App {
|
|||
// Set App Delegate
|
||||
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
|
||||
@StateObject private var subscriptions = NtfySubscriptionList()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
ContentView(subscriptions: subscriptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ class NtfySubscription: ObservableObject, Identifiable {
|
|||
return notifications.first
|
||||
}
|
||||
|
||||
func fetchNewNotifications(user: NtfyUser?, completionHandler: ( ([NtfyNotification]?, Error?) -> Void)?) {
|
||||
func fetchNewNotifications(completionHandler: ( ([NtfyNotification]?, Error?) -> Void)?) {
|
||||
var newNotifications = [NtfyNotification]()
|
||||
ApiService.shared.poll(subscription: self, user: user) { (notifications, error) in
|
||||
ApiService.shared.poll(subscription: self) { (notifications, error) in
|
||||
if let notifications = notifications {
|
||||
for notification in notifications {
|
||||
if (notification.save() != nil) {
|
||||
|
|
@ -96,4 +96,8 @@ class NtfySubscriptionList: ObservableObject {
|
|||
init() {
|
||||
self.subscriptions = Database.current.getSubscriptions()
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
self.subscriptions = Database.current.getSubscriptions()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
//
|
||||
// NtfyUser.swift
|
||||
// ntfy.sh
|
||||
//
|
||||
// Created by Andrew Cope on 2/20/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class NtfyUser: Identifiable {
|
||||
var baseUrl: String
|
||||
var username: String
|
||||
var password: String
|
||||
|
||||
init(baseUrl: String, username: String, password: String) {
|
||||
self.baseUrl = baseUrl
|
||||
self.username = username
|
||||
self.password = password
|
||||
}
|
||||
}
|
||||
|
|
@ -10,22 +10,17 @@ import Foundation
|
|||
class ApiService: NSObject {
|
||||
static let shared = ApiService()
|
||||
|
||||
func poll(subscription: NtfySubscription, user: NtfyUser?, completionHandler: @escaping ([NtfyNotification]?, Error?) -> Void) {
|
||||
func poll(subscription: NtfySubscription, completionHandler: @escaping ([NtfyNotification]?, Error?) -> Void) {
|
||||
let lastNotificationTime = subscription.lastNotification()?.timestamp ?? 0
|
||||
let sinceString = lastNotificationTime > 0 ? String(lastNotificationTime) : "all";
|
||||
let urlString = "\(subscription.urlString())/json?poll=1&since=\(sinceString)"
|
||||
fetchJsonData(urlString: urlString, user: user, completionHandler: completionHandler)
|
||||
fetchJsonData(urlString: urlString, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
func publish(subscription: NtfySubscription, message: String, title: String, priority: Int = 3, tags: [String] = [], user: NtfyUser?, completionHandler: @escaping (NtfyNotification?, Error?) -> Void) {
|
||||
func publish(subscription: NtfySubscription, message: String, title: String, priority: Int = 3, tags: [String] = [], completionHandler: @escaping (NtfyNotification?, Error?) -> Void) {
|
||||
guard let url = URL(string: subscription.urlString()) else { return }
|
||||
var request = URLRequest(url: url)
|
||||
|
||||
if let user = user {
|
||||
let credentials = Credentials.Basic(username: user.username, password: user.password)
|
||||
request.setValue("Basic \(credentials)", forHTTPHeaderField: "Authorization")
|
||||
}
|
||||
|
||||
request.httpMethod = "POST"
|
||||
request.setValue(title, forHTTPHeaderField: "Title")
|
||||
request.setValue(String(priority), forHTTPHeaderField: "Priority")
|
||||
|
|
@ -38,13 +33,9 @@ class ApiService: NSObject {
|
|||
}.resume()
|
||||
}
|
||||
|
||||
func checkAuth(baseUrl: String, topic: String, user: NtfyUser?, completionHandler: @escaping(AuthCheckResponse?, Error?) -> Void) {
|
||||
func checkAuth(baseUrl: String, topic: String, completionHandler: @escaping(AuthCheckResponse?, Error?) -> Void) {
|
||||
guard let url = URL(string: "\(baseUrl)/\(topic)/auth") else { return }
|
||||
var request = URLRequest(url: url)
|
||||
if let user = user {
|
||||
let credentials = Credentials.Basic(username: user.username, password: user.password)
|
||||
request.setValue("Basic \(credentials)", forHTTPHeaderField: "Authorization")
|
||||
}
|
||||
URLSession.shared.dataTask(with: request) { (data, response, error) in
|
||||
if let error = error {
|
||||
print("Error checking auth: \(error)")
|
||||
|
|
@ -63,14 +54,9 @@ class ApiService: NSObject {
|
|||
}.resume()
|
||||
}
|
||||
|
||||
private func fetchJsonData<T: Decodable>(urlString: String, user: NtfyUser?, completionHandler: @escaping ([T]?, Error?) -> ()) {
|
||||
private func fetchJsonData<T: Decodable>(urlString: String, completionHandler: @escaping ([T]?, Error?) -> ()) {
|
||||
guard let url = URL(string: urlString) else { return }
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
if let user = user {
|
||||
let credentials = Credentials.Basic(username: user.username, password: user.password)
|
||||
request.setValue("Basic \(credentials)", forHTTPHeaderField: "Authorization")
|
||||
}
|
||||
|
||||
URLSession.shared.dataTask(with: request) { (data, response, error) in
|
||||
if let error = error {
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// Credentials.swift
|
||||
// ntfy.sh
|
||||
//
|
||||
// Created by Andrew Cope on 2/20/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum Credentials {
|
||||
static func Basic(username: String, password: String) -> String {
|
||||
return String(format: "%@:%@", username, password).data(using: String.Encoding.utf8)!.base64EncodedString()
|
||||
}
|
||||
}
|
||||
|
|
@ -47,12 +47,6 @@ class Database {
|
|||
let attachment_url = Expression<String>("url")
|
||||
let attachment_content_url = Expression<String>("contentUrl")
|
||||
|
||||
// Users Table
|
||||
let users = Table("Users")
|
||||
let user_base_url = Expression<String>("baseUrl")
|
||||
let user_username = Expression<String>("username")
|
||||
let user_password = Expression<String>("password")
|
||||
|
||||
// Initialize
|
||||
init() {
|
||||
do {
|
||||
|
|
@ -60,7 +54,7 @@ class Database {
|
|||
// 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") {
|
||||
// Connect to the database
|
||||
db = try Connection("\(path.path)/ntfy.sh.sqlite3")
|
||||
db = try Connection("\(path.path)/ntfy.sqlite3")
|
||||
|
||||
// Initialize Subscriptions table
|
||||
try db?.run(subscriptions.create(ifNotExists: true) { table in
|
||||
|
|
@ -91,14 +85,6 @@ class Database {
|
|||
table.column(attachment_url)
|
||||
table.column(attachment_content_url)
|
||||
})
|
||||
|
||||
// Initialize Users Table
|
||||
try db?.run(users.create(ifNotExists: true) { table in
|
||||
table.column(user_base_url)
|
||||
table.column(user_username)
|
||||
table.column(user_password)
|
||||
table.primaryKey(user_base_url)
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
|
|
@ -308,45 +294,4 @@ class Database {
|
|||
print("Error updating attachment: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func addUser(user: NtfyUser) {
|
||||
do {
|
||||
try db?.run(users.insert(
|
||||
user_base_url <- user.baseUrl,
|
||||
user_username <- user.username,
|
||||
user_password <- user.password
|
||||
))
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteUser(user: NtfyUser) {
|
||||
do {
|
||||
let line = users.filter(user_base_url == user.baseUrl && user_username == user.username)
|
||||
try db?.run(line.delete())
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
func findUsers(baseUrl: String) -> [NtfyUser] {
|
||||
var ntfyUsers = [NtfyUser]()
|
||||
do {
|
||||
let query = users.filter(user_base_url == baseUrl)
|
||||
if let result = try db?.prepare(query) {
|
||||
for line in result {
|
||||
let user = NtfyUser(
|
||||
baseUrl: try line.get(user_base_url),
|
||||
username: try line.get(user_username),
|
||||
password: try line.get(user_password)
|
||||
)
|
||||
ntfyUsers.append(user)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
return ntfyUsers
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,18 +8,16 @@
|
|||
import SwiftUI
|
||||
|
||||
struct AddSubscriptionView: View {
|
||||
@ObservedObject var subscriptions: NtfySubscriptionList
|
||||
@State private var topic: String = ""
|
||||
@State private var baseUrl: String = Configuration.appBaseUrl
|
||||
@State private var showLogin: Bool = false
|
||||
@State private var username: String = ""
|
||||
@State private var password: String = ""
|
||||
|
||||
|
||||
@State private var showAlert = false
|
||||
@State private var activeAlert: AddSubscriptionView.ActiveAlert = .invalidTopic
|
||||
@State private var authFailureError = ""
|
||||
|
||||
|
||||
@Binding var currentView: CurrentView
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
|
|
@ -30,16 +28,6 @@ struct AddSubscriptionView: View {
|
|||
TextField("Topic name, e.g. server_alerts", text: $topic)
|
||||
.textInputAutocapitalization(.never)
|
||||
}
|
||||
if showLogin {
|
||||
Section(
|
||||
header: Text("Login")
|
||||
) {
|
||||
TextField("Username", text: $username)
|
||||
.textInputAutocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
SecureField("Password", text: $password)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
|
@ -61,7 +49,7 @@ struct AddSubscriptionView: View {
|
|||
Validation function:
|
||||
1. Topic is not empty
|
||||
2. Topic matches regex? Should match Firebase topic regex
|
||||
|
||||
|
||||
Authentication function:
|
||||
1. Get baseUrl
|
||||
2. Get user for baseUrl
|
||||
|
|
@ -69,14 +57,14 @@ struct AddSubscriptionView: View {
|
|||
3. If authorized, continue to subscribe
|
||||
4. Else if user != null, access not allowed to topic but user exists
|
||||
5. Else (user is null), access not allowed, show login view
|
||||
|
||||
|
||||
Login function:
|
||||
1. Login user / pass view
|
||||
2. api.checkAuth(baseUrl, topic, user -> user / pass)
|
||||
3. If authorized, save user to database, continue to subscribe
|
||||
4. Else access not allowed, show login view again
|
||||
|
||||
|
||||
|
||||
|
||||
Subscribe function:
|
||||
1. Create subscription
|
||||
2. Add subscription to database
|
||||
|
|
@ -84,32 +72,18 @@ struct AddSubscriptionView: View {
|
|||
4. Fetch cached messages
|
||||
5. Switch to SubscriptionDetail view
|
||||
*/
|
||||
var user = Database.current.findUsers(baseUrl: baseUrl).first
|
||||
if showLogin {
|
||||
print("Authorization via UI forms")
|
||||
if (user != nil) {
|
||||
user!.username = username
|
||||
user!.password = password
|
||||
} else {
|
||||
user = NtfyUser(baseUrl: baseUrl, username: username, password: password)
|
||||
}
|
||||
}
|
||||
ApiService.shared.checkAuth(baseUrl: baseUrl, topic: sanitizedTopic, user: user) { authResponse, error in
|
||||
ApiService.shared.checkAuth(baseUrl: baseUrl, topic: sanitizedTopic) { authResponse, error in
|
||||
if let authResponse = authResponse {
|
||||
if let success = authResponse.success, success {
|
||||
if user != nil {
|
||||
Database.current.addUser(user: user!)
|
||||
}
|
||||
let subscription = NtfySubscription(id: Int64(arc4random()), baseUrl: baseUrl, topic: sanitizedTopic)
|
||||
subscription.save()
|
||||
if baseUrl == Configuration.appBaseUrl {
|
||||
subscription.subscribe(to: sanitizedTopic)
|
||||
}
|
||||
subscription.fetchNewNotifications(user: user, completionHandler: nil)
|
||||
subscriptions.refresh()
|
||||
subscription.fetchNewNotifications( completionHandler: nil)
|
||||
currentView = .subscriptionList
|
||||
showLogin = false
|
||||
} else {
|
||||
showLogin = true
|
||||
showAlert = true
|
||||
activeAlert = .requiresAuth
|
||||
}
|
||||
|
|
@ -163,11 +137,11 @@ struct AddSubscriptionView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func sanitizeTopic(topic: String) -> String {
|
||||
return topic.trimmingCharacters(in: [" "])
|
||||
}
|
||||
|
||||
|
||||
private func isTopicValid(topic: String) -> Bool {
|
||||
return !topic.isEmpty && (topic.range(of: "^[-_A-Za-z0-9]{1,64}$", options: .regularExpression, range: nil, locale: nil) != nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,22 +8,19 @@
|
|||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@State var addingSubscription = false
|
||||
@State var managingUsers = false
|
||||
@ObservedObject var subscriptions: NtfySubscriptionList
|
||||
@State var currentView = CurrentView.subscriptionList
|
||||
|
||||
|
||||
var body: some View {
|
||||
switch (currentView) {
|
||||
case .managingUsers:
|
||||
UserManagementView(currentView: $currentView)
|
||||
case .addingSubscription:
|
||||
AddSubscriptionView(currentView: $currentView)
|
||||
AddSubscriptionView(subscriptions: subscriptions, currentView: $currentView)
|
||||
case .subscriptionList:
|
||||
SubscriptionsList(currentView: $currentView)
|
||||
SubscriptionsList(subscriptions: subscriptions, currentView: $currentView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CurrentView {
|
||||
case addingSubscription, managingUsers, subscriptionList
|
||||
case addingSubscription, subscriptionList
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ struct SubscriptionDetail: View {
|
|||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
var body: some View {
|
||||
let user = Database.current.findUsers(baseUrl: subscription.baseUrl).first
|
||||
NavigationView {
|
||||
List(selection: $selection) {
|
||||
ForEach(subscription.notifications, id: \.self) { notification in
|
||||
|
|
@ -56,8 +55,7 @@ struct SubscriptionDetail: View {
|
|||
message: "This is a test notification from the Ntfy iOS app. It has a priority of \(priority). If you send another one, it may look different.",
|
||||
title: "Test: You can set a title if you like",
|
||||
priority: priority,
|
||||
tags: tags,
|
||||
user: user
|
||||
tags: tags
|
||||
) { _,_ in
|
||||
print("Success")
|
||||
}
|
||||
|
|
@ -132,7 +130,7 @@ struct SubscriptionDetail: View {
|
|||
}
|
||||
})
|
||||
.refreshable {
|
||||
subscription.fetchNewNotifications(user: user, completionHandler: nil)
|
||||
subscription.fetchNewNotifications(completionHandler: nil)
|
||||
}
|
||||
.onAppear {
|
||||
subscription.loadNotifications()
|
||||
|
|
@ -175,8 +173,8 @@ class SubscriptionDetailViewModel: ObservableObject {
|
|||
notifications = Database.current.getNotifications(subscription: subscription)
|
||||
}
|
||||
|
||||
func fetchNewNotifications(subscription: NtfySubscription, user: NtfyUser?) {
|
||||
subscription.fetchNewNotifications(user: user) { (_, _) in
|
||||
func fetchNewNotifications(subscription: NtfySubscription) {
|
||||
subscription.fetchNewNotifications { (_, _) in
|
||||
self.loadNotifications(subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@
|
|||
import SwiftUI
|
||||
|
||||
struct SubscriptionsList: View {
|
||||
@StateObject var subscriptions = NtfySubscriptionList()
|
||||
|
||||
@ObservedObject var subscriptions: NtfySubscriptionList
|
||||
@Binding var currentView: CurrentView
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -47,13 +46,6 @@ struct SubscriptionsList: View {
|
|||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(action: {
|
||||
currentView = .managingUsers
|
||||
}) {
|
||||
Text("Users")
|
||||
}
|
||||
}
|
||||
}
|
||||
.overlay(Group {
|
||||
if subscriptions.subscriptions.isEmpty {
|
||||
|
|
@ -65,7 +57,7 @@ struct SubscriptionsList: View {
|
|||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.onAppear {
|
||||
self.subscriptions.objectWillChange.send()
|
||||
subscriptions.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
//
|
||||
// UserManagementView.swift
|
||||
// ntfy.sh
|
||||
//
|
||||
// Created by Andrew Cope on 2/27/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct UserManagementView: View {
|
||||
|
||||
@ObservedObject var viewModel = UserManagementViewModel()
|
||||
|
||||
@Binding var currentView: CurrentView
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
Section(header: Text(Configuration.appBaseUrl)) {
|
||||
ForEach(viewModel.users) { user in
|
||||
Text(user.username)
|
||||
.swipeActions {
|
||||
Button(role: .destructive) {
|
||||
viewModel.deleteUser(user: user)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(GroupedListStyle())
|
||||
.navigationTitle("Manage Users")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(action: {
|
||||
currentView = .subscriptionList
|
||||
}) {
|
||||
Text("Topics")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.onAppear {
|
||||
viewModel.loadUsers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UserManagementViewModel: ObservableObject {
|
||||
@Published var users = [NtfyUser]()
|
||||
|
||||
func loadUsers() {
|
||||
self.users = Database.current.findUsers(baseUrl: Configuration.appBaseUrl)
|
||||
}
|
||||
|
||||
func deleteUser(user: NtfyUser) {
|
||||
Database.current.deleteUser(user: user)
|
||||
self.loadUsers()
|
||||
}
|
||||
}
|
||||
|
|
@ -25,15 +25,10 @@
|
|||
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 */; };
|
||||
8079FF5227C2C38700FB3D18 /* NtfyUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8079FF5127C2C38700FB3D18 /* NtfyUser.swift */; };
|
||||
8079FF5327C2C38700FB3D18 /* NtfyUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8079FF5127C2C38700FB3D18 /* NtfyUser.swift */; };
|
||||
8079FF5527C2C49200FB3D18 /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8079FF5427C2C49200FB3D18 /* Credentials.swift */; };
|
||||
8079FF5627C2C49200FB3D18 /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8079FF5427C2C49200FB3D18 /* Credentials.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 */; };
|
||||
8086EB2627946FCE00C3628A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8086EB2527946FCE00C3628A /* ContentView.swift */; };
|
||||
808833B427CC32010098927E /* UserManagementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 808833B327CC32010098927E /* UserManagementView.swift */; };
|
||||
80A313F52793B1CF00F1A639 /* NtfySubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80A313F42793B1CF00F1A639 /* NtfySubscription.swift */; };
|
||||
80A313F72793B56800F1A639 /* NtfyNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80A313F62793B56800F1A639 /* NtfyNotification.swift */; };
|
||||
80A313F92793C0D800F1A639 /* SubscriptionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80A313F82793C0D800F1A639 /* SubscriptionRow.swift */; };
|
||||
|
|
@ -88,13 +83,10 @@
|
|||
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>"; };
|
||||
8079FF5127C2C38700FB3D18 /* NtfyUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NtfyUser.swift; sourceTree = "<group>"; };
|
||||
8079FF5427C2C49200FB3D18 /* Credentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credentials.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>"; };
|
||||
8086EB2527946FCE00C3628A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
808833B327CC32010098927E /* UserManagementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserManagementView.swift; sourceTree = "<group>"; };
|
||||
80910E1127C420B50074B05A /* GETTING_STARTED.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = GETTING_STARTED.md; sourceTree = "<group>"; };
|
||||
80910E1227C422140074B05A /* TECHNICAL_LIMITATIONS.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TECHNICAL_LIMITATIONS.md; sourceTree = "<group>"; };
|
||||
80A313F42793B1CF00F1A639 /* NtfySubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NtfySubscription.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -147,7 +139,6 @@
|
|||
80E8CED627B56DB200FDC5E0 /* EmojiManager.swift */,
|
||||
80ED61BD27BCA36A00FCEA36 /* Configuration.swift */,
|
||||
80856C7E27BDE0A7008AC8B8 /* ApiService.swift */,
|
||||
8079FF5427C2C49200FB3D18 /* Credentials.swift */,
|
||||
);
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -232,7 +223,6 @@
|
|||
8086EB232794630800C3628A /* AddSubscriptionView.swift */,
|
||||
8086EB2527946FCE00C3628A /* ContentView.swift */,
|
||||
8079FF4E27C29D3300FB3D18 /* NotificationAttachmentView.swift */,
|
||||
808833B327CC32010098927E /* UserManagementView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -261,7 +251,6 @@
|
|||
80A313F42793B1CF00F1A639 /* NtfySubscription.swift */,
|
||||
80A313F62793B56800F1A639 /* NtfyNotification.swift */,
|
||||
8079FF4B27C2874A00FB3D18 /* NtfyAttachment.swift */,
|
||||
8079FF5127C2C38700FB3D18 /* NtfyUser.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -404,13 +393,11 @@
|
|||
files = (
|
||||
802D626C27B1A37700DDD3AF /* Database.swift in Sources */,
|
||||
8079FF4D27C2874A00FB3D18 /* NtfyAttachment.swift in Sources */,
|
||||
8079FF5627C2C49200FB3D18 /* Credentials.swift in Sources */,
|
||||
80ED61BF27BCA36A00FCEA36 /* Configuration.swift in Sources */,
|
||||
802D626D27B1A37900DDD3AF /* NtfyNotification.swift in Sources */,
|
||||
800FA49627B19CA0005D05B9 /* NotificationService.swift in Sources */,
|
||||
80E8CED827B56DB200FDC5E0 /* EmojiManager.swift in Sources */,
|
||||
80856C8027BDE0A7008AC8B8 /* ApiService.swift in Sources */,
|
||||
8079FF5327C2C38700FB3D18 /* NtfyUser.swift in Sources */,
|
||||
802D626E27B1A37C00DDD3AF /* NtfySubscription.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -426,13 +413,10 @@
|
|||
8015C3DC2793AB1500E6F001 /* Database.swift in Sources */,
|
||||
80E8CED727B56DB200FDC5E0 /* EmojiManager.swift in Sources */,
|
||||
8086EB242794630800C3628A /* AddSubscriptionView.swift in Sources */,
|
||||
8079FF5227C2C38700FB3D18 /* NtfyUser.swift in Sources */,
|
||||
808833B427CC32010098927E /* UserManagementView.swift in Sources */,
|
||||
80A313FD2793C42000F1A639 /* NotificationRow.swift in Sources */,
|
||||
80A313FB2793C2EA00F1A639 /* SubscriptionDetail.swift in Sources */,
|
||||
8079FF4C27C2874A00FB3D18 /* NtfyAttachment.swift in Sources */,
|
||||
80A313F92793C0D800F1A639 /* SubscriptionRow.swift in Sources */,
|
||||
8079FF5527C2C49200FB3D18 /* Credentials.swift in Sources */,
|
||||
80386E802793585B009B0480 /* AppMain.swift in Sources */,
|
||||
8079FF4F27C29D3300FB3D18 /* NotificationAttachmentView.swift in Sources */,
|
||||
80A313F72793B56800F1A639 /* NtfyNotification.swift in Sources */,
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Add table
Reference in a new issue