Add support for iOS 14.0

This commit is contained in:
Callum Yarnold 2022-05-24 22:09:26 +01:00
parent 30dd10bb80
commit 573acb561c
No known key found for this signature in database
GPG key ID: 1B309601199B0473
5 changed files with 295 additions and 207 deletions

View file

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
02024E60283D7CBB0064224A /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02024E5F283D7CBB0064224A /* View.swift */; };
9474F1C1282F2AA700CDE4DD /* AppMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F1C0282F2AA700CDE4DD /* AppMain.swift */; };
9474F1C3282F2AA700CDE4DD /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F1C2282F2AA700CDE4DD /* ContentView.swift */; };
9474F1C5282F2AA800CDE4DD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9474F1C4282F2AA800CDE4DD /* Assets.xcassets */; };
@ -62,6 +63,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
02024E5F283D7CBB0064224A /* View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
9474F1BD282F2AA700CDE4DD /* ntfy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ntfy.app; sourceTree = BUILT_PRODUCTS_DIR; };
9474F1C0282F2AA700CDE4DD /* AppMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMain.swift; sourceTree = "<group>"; };
9474F1C2282F2AA700CDE4DD /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@ -108,6 +110,14 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
02024E5E283D7CBB0064224A /* Extensions */ = {
isa = PBXGroup;
children = (
02024E5F283D7CBB0064224A /* View.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
9474F1B4282F2AA700CDE4DD = {
isa = PBXGroup;
children = (
@ -130,6 +140,7 @@
9474F1BF282F2AA700CDE4DD /* ntfy */ = {
isa = PBXGroup;
children = (
02024E5E283D7CBB0064224A /* Extensions */,
9474F210283326E000CDE4DD /* Utils */,
9474F20D2833264F00CDE4DD /* App */,
9474F1C4282F2AA800CDE4DD /* Assets.xcassets */,
@ -318,6 +329,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
02024E60283D7CBB0064224A /* View.swift in Sources */,
9474F1F92830835400CDE4DD /* Store.swift in Sources */,
9474F212283327C200CDE4DD /* Helpers.swift in Sources */,
9474F217283531A300CDE4DD /* Log.swift in Sources */,
@ -413,7 +425,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@ -468,7 +480,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
@ -497,6 +509,7 @@
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -529,6 +542,7 @@
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -553,7 +567,7 @@
INFOPLIST_FILE = ntfyNSE/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ntfyNSE;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 15.4;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -580,7 +594,7 @@
INFOPLIST_FILE = ntfyNSE/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ntfyNSE;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 15.4;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",

View file

@ -0,0 +1,26 @@
//
// View.swift
// ntfy
//
// Created by Callum Yarnold on 24/05/2022.
//
import SwiftUI
struct disableAutoCapitalisationModifier: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 15.0, *) {
content
.textInputAutocapitalization(.never)
} else {
content
.autocapitalization(.none)
}
}
}
extension View {
func disableAutoCapitalisation() -> some View {
modifier(disableAutoCapitalisationModifier())
}
}

View file

@ -23,121 +23,133 @@ struct NotificationListView: View {
}
var body: some View {
List(selection: $selection) {
ForEach(subscription.notificationsSorted(), id: \.self) { notification in
NotificationRowView(notification: notification)
}
}
.listStyle(PlainListStyle())
.navigationBarTitleDisplayMode(.inline)
.environment(\.editMode, self.$editMode)
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
if (self.editMode != .active) {
Button(action: {
// iOS bug (?): We create a custom back button, because when we return using the original
// back button, and the navigation is popped that way, the row stays highlighted for a long
// time, which is weird and feels wrong. This avoids that behavior.
self.delegate.selectedBaseUrl = nil
}){
Image(systemName: "chevron.left")
}
.padding([.top, .bottom, .trailing], 20)
}
}
ToolbarItem(placement: .principal) {
Text(subscription.displayName()).font(.headline)
}
ToolbarItem(placement: .navigationBarTrailing) {
if (self.editMode == .active) {
editButton
} else {
Menu {
if subscription.notificationCount() > 0 {
editButton
}
Button("Send test notification") {
self.sendTestNotification()
}
if subscription.notificationCount() > 0 {
Button("Clear all notifications") {
self.showAlert = true
self.activeAlert = .clear
}
}
Button("Unsubscribe") {
self.showAlert = true
self.activeAlert = .unsubscribe
}
} label: {
Image(systemName: "ellipsis.circle")
.padding([.top, .bottom, .leading], 20)
}
}
}
ToolbarItem(placement: .navigationBarLeading) {
if (self.editMode == .active) {
Button(action: {
self.showAlert = true
self.activeAlert = .selected
}) {
Text("Delete")
.foregroundColor(.red)
}
}
}
}
.alert(isPresented: $showAlert) {
switch activeAlert {
case .clear:
return Alert(
title: Text("Clear notifications"),
message: Text("Do you really want to delete all of the notifications in this topic?"),
primaryButton: .destructive(
Text("Permanently delete"),
action: deleteAll
),
secondaryButton: .cancel())
case .unsubscribe:
return Alert(
title: Text("Unsubscribe"),
message: Text("Do you really want to unsubscribe from this topic and delete all of the notifications you received?"),
primaryButton: .destructive(
Text("Unsubscribe"),
action: unsubscribe
),
secondaryButton: .cancel())
case .selected:
return Alert(
title: Text("Delete"),
message: Text("Do you really want to delete these selected notifications?"),
primaryButton: .destructive(
Text("Delete"),
action: deleteSelected
),
secondaryButton: .cancel())
}
}
.overlay(Group {
if subscription.notificationCount() == 0 {
VStack {
Text("You haven't received any notifications for this topic yet.")
.font(.title2)
.foregroundColor(.gray)
.multilineTextAlignment(.center)
.padding(.bottom)
Text("To send notifications to this topic, simply PUT or POST to the topic URL.\n\nExample:\n`$ curl -d \"hi\" ntfy.sh/\(subscription.topicName())`\n\nDetailed instructions are available on [ntfy.sh](https;//ntfy.sh) and [in the docs](https:ntfy.sh/docs).")
.foregroundColor(.gray)
}
.padding(40)
}
})
.refreshable {
subscriptionManager.poll(subscription)
}
notificationlist
if #available(iOS 15.0, *) {
notificationlist
.refreshable {
subscriptionManager.poll(subscription)
}
} else {
notificationlist
}
}
private var notificationlist: some View {
List(selection: $selection) {
ForEach(subscription.notificationsSorted(), id: \.self) { notification in
NotificationRowView(notification: notification)
}
}
.listStyle(PlainListStyle())
.navigationBarTitleDisplayMode(.inline)
.environment(\.editMode, self.$editMode)
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
if (self.editMode != .active) {
Button(action: {
// iOS bug (?): We create a custom back button, because when we return using the original
// back button, and the navigation is popped that way, the row stays highlighted for a long
// time, which is weird and feels wrong. This avoids that behavior.
self.delegate.selectedBaseUrl = nil
}){
Image(systemName: "chevron.left")
}
.padding([.top, .bottom, .trailing], 20)
}
}
ToolbarItem(placement: .principal) {
Text(subscription.displayName()).font(.headline)
}
ToolbarItem(placement: .navigationBarTrailing) {
if (self.editMode == .active) {
editButton
} else {
Menu {
Button("Refresh") {
subscriptionManager.poll(subscription)
}
if subscription.notificationCount() > 0 {
editButton
}
Button("Send test notification") {
self.sendTestNotification()
}
if subscription.notificationCount() > 0 {
Button("Clear all notifications") {
self.showAlert = true
self.activeAlert = .clear
}
}
Button("Unsubscribe") {
self.showAlert = true
self.activeAlert = .unsubscribe
}
} label: {
Image(systemName: "ellipsis.circle")
.padding([.top, .bottom, .leading], 20)
}
}
}
ToolbarItem(placement: .navigationBarLeading) {
if (self.editMode == .active) {
Button(action: {
self.showAlert = true
self.activeAlert = .selected
}) {
Text("Delete")
.foregroundColor(.red)
}
}
}
}
.alert(isPresented: $showAlert) {
switch activeAlert {
case .clear:
return Alert(
title: Text("Clear notifications"),
message: Text("Do you really want to delete all of the notifications in this topic?"),
primaryButton: .destructive(
Text("Permanently delete"),
action: deleteAll
),
secondaryButton: .cancel())
case .unsubscribe:
return Alert(
title: Text("Unsubscribe"),
message: Text("Do you really want to unsubscribe from this topic and delete all of the notifications you received?"),
primaryButton: .destructive(
Text("Unsubscribe"),
action: unsubscribe
),
secondaryButton: .cancel())
case .selected:
return Alert(
title: Text("Delete"),
message: Text("Do you really want to delete these selected notifications?"),
primaryButton: .destructive(
Text("Delete"),
action: deleteSelected
),
secondaryButton: .cancel())
}
}
.overlay(Group {
if subscription.notificationCount() == 0 {
VStack {
Text("You haven't received any notifications for this topic yet.")
.font(.title2)
.foregroundColor(.gray)
.multilineTextAlignment(.center)
.padding(.bottom)
Text("To send notifications to this topic, simply PUT or POST to the topic URL.\n\nExample:\n`$ curl -d \"hi\" ntfy.sh/\(subscription.topicName())`\n\nDetailed instructions are available on [ntfy.sh](https;//ntfy.sh) and [in the docs](https:ntfy.sh/docs).")
.foregroundColor(.gray)
}
.padding(40)
}
})
}
private var editButton: some View {
if editMode == .inactive {
@ -199,27 +211,35 @@ struct NotificationRowView: View {
@ObservedObject var notification: Notification
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text(notification.shortDateTime())
.font(.subheadline)
.foregroundColor(.gray)
if let title = notification.title, title != "" {
Text(title)
.font(.headline)
.bold()
}
Text(notification.message ?? "")
.font(.body)
}
.padding(.all, 4)
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
store.delete(notification: notification)
} label: {
Label("Delete", systemImage: "trash.circle")
}
}
if #available(iOS 15.0, *) {
notificationRow
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
store.delete(notification: notification)
} label: {
Label("Delete", systemImage: "trash.circle")
}
}
} else {
notificationRow
}
}
private var notificationRow: some View {
VStack(alignment: .leading, spacing: 0) {
Text(notification.shortDateTime())
.font(.subheadline)
.foregroundColor(.gray)
if let title = notification.title, title != "" {
Text(title)
.font(.headline)
.bold()
}
Text(notification.message ?? "")
.font(.body)
}
.padding(.all, 4)
}
}
struct NotificationListView_Previews: PreviewProvider {

View file

@ -20,7 +20,8 @@ struct SubscriptionAddView: View {
footer: Text("Topics may not be password protected, so choose a name that's not easy to guess. Once subscribed, you can PUT/POST notifications")
) {
TextField("Topic name, e.g. phil_alerts", text: $topic)
.textInputAutocapitalization(.never)
.disableAutoCapitalisation()
.disableAutocorrection(true)
}
}
.navigationBarTitleDisplayMode(.inline)

View file

@ -15,42 +15,61 @@ struct SubscriptionListView: View {
var body: some View {
NavigationView {
List {
ForEach(subscriptions) { subscription in
SubscriptionItemNavView(subscription: subscription)
}
}
.listStyle(PlainListStyle())
.navigationTitle("Subscribed topics")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: SubscriptionAddView()) {
Image(systemName: "plus")
}
}
}
.overlay(Group {
if subscriptions.isEmpty {
VStack {
Text("It looks like you don't have any subscriptions yet")
.font(.title2)
.foregroundColor(.gray)
.multilineTextAlignment(.center)
.padding(.bottom)
Text("Click the + to create or subscribe to a topic. Afterwards, you receive notifications on your device when sending messages via PUT or POST.\n\nDetailed instructions are available on [ntfy.sh](https;//ntfy.sh) and [in the docs](https:ntfy.sh/docs).")
.foregroundColor(.gray)
}
.padding(40)
}
})
.refreshable {
subscriptions.forEach { subscription in
subscriptionManager.poll(subscription)
}
}
if #available(iOS 15.0, *) {
subscriptionList
.refreshable {
subscriptions.forEach { subscription in
subscriptionManager.poll(subscription)
}
}
} else {
subscriptionList
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
subscriptions.forEach { subscription in
subscriptionManager.poll(subscription)
}
} label: {
Image(systemName: "arrow.clockwise")
}
}
}
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
private var subscriptionList: some View {
List {
ForEach(subscriptions) { subscription in
SubscriptionItemNavView(subscription: subscription)
}
}
.listStyle(PlainListStyle())
.navigationTitle("Subscribed topics")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: SubscriptionAddView()) {
Image(systemName: "plus")
}
}
}
.overlay(Group {
if subscriptions.isEmpty {
VStack {
Text("It looks like you don't have any subscriptions yet")
.font(.title2)
.foregroundColor(.gray)
.multilineTextAlignment(.center)
.padding(.bottom)
Text("Click the + to create or subscribe to a topic. Afterwards, you receive notifications on your device when sending messages via PUT or POST.\n\nDetailed instructions are available on [ntfy.sh](https;//ntfy.sh) and [in the docs](https:ntfy.sh/docs).")
.foregroundColor(.gray)
}
.padding(40)
}
})
}
}
struct SubscriptionItemNavView: View {
@ -64,41 +83,49 @@ struct SubscriptionItemNavView: View {
}
var body: some View {
ZStack {
NavigationLink(
destination: NotificationListView(subscription: subscription),
tag: subscription.urlString(),
selection: $delegate.selectedBaseUrl
) {
EmptyView()
}
.opacity(0.0)
.buttonStyle(PlainButtonStyle())
SubscriptionItemRowView(subscription: subscription)
}
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
self.unsubscribeAlert = true
} label: {
Label("Delete", systemImage: "trash.circle")
}
}
.alert(isPresented: $unsubscribeAlert) {
Alert(
title: Text("Unsubscribe"),
message: Text("Do you really want to unsubscribe from this topic and delete all of the notifications you received?"),
primaryButton: .destructive(
Text("Unsubscribe"),
action: {
self.subscriptionManager.unsubscribe(subscription)
self.unsubscribeAlert = false
}
),
secondaryButton: .cancel()
)
}
}
if #available(iOS 15.0, *) {
subscriptionRow
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
self.unsubscribeAlert = true
} label: {
Label("Delete", systemImage: "trash.circle")
}
}
} else {
subscriptionRow
}
}
private var subscriptionRow: some View {
ZStack {
NavigationLink(
destination: NotificationListView(subscription: subscription),
tag: subscription.urlString(),
selection: $delegate.selectedBaseUrl
) {
EmptyView()
}
.opacity(0.0)
.buttonStyle(PlainButtonStyle())
SubscriptionItemRowView(subscription: subscription)
}
.alert(isPresented: $unsubscribeAlert) {
Alert(
title: Text("Unsubscribe"),
message: Text("Do you really want to unsubscribe from this topic and delete all of the notifications you received?"),
primaryButton: .destructive(
Text("Unsubscribe"),
action: {
self.subscriptionManager.unsubscribe(subscription)
self.unsubscribeAlert = false
}
),
secondaryButton: .cancel()
)
}
}
}
struct SubscriptionItemRowView: View {