327 lines
10 KiB
Swift
327 lines
10 KiB
Swift
import SwiftUI
|
|
import APNSwift
|
|
import PushAPI
|
|
import SFSafeSymbols
|
|
|
|
struct ContentView: View {
|
|
|
|
@AppStorage("pushToken")
|
|
var pushToken: PushToken?
|
|
|
|
var hasPushToken: Bool {
|
|
pushToken != nil
|
|
}
|
|
|
|
@AppStorage("authToken")
|
|
var authToken: AuthenticationToken?
|
|
|
|
@State
|
|
var isConfirmed = false
|
|
|
|
@State
|
|
var hasNotificationPermissions: Bool? = nil
|
|
|
|
@State
|
|
var showDeviceList = false
|
|
|
|
@State
|
|
var api = API()
|
|
|
|
@AppStorage("deviceName")
|
|
var deviceName: String = ""
|
|
|
|
@State
|
|
var deviceList: [DeviceRegistration] = []
|
|
|
|
var couldBeRegistered: Bool {
|
|
pushToken != nil && authToken != nil
|
|
}
|
|
|
|
@AppStorage("pushTitle")
|
|
var pushMessageTitle: String = ""
|
|
|
|
@AppStorage("pushBody")
|
|
var pushMessageText: String = ""
|
|
|
|
@State
|
|
var includeOwnDeviceInPush = false
|
|
|
|
var canSendNotification: Bool {
|
|
isConfirmed && (includeOwnDeviceInPush || deviceList.count > 1)
|
|
}
|
|
|
|
func statusView(_ state: Bool?) -> some View {
|
|
let symbol: SFSymbol
|
|
let color: Color
|
|
if let state = state {
|
|
symbol = state ? .checkmarkCircle : .xmarkCircle
|
|
color = state ? .green : .red
|
|
} else {
|
|
symbol = .questionmarkCircle
|
|
color = .gray
|
|
}
|
|
return Image(systemSymbol: symbol)
|
|
.renderingMode(.template)
|
|
.foregroundColor(color)
|
|
}
|
|
|
|
func updateNotificationPermissionState() {
|
|
Task {
|
|
let state = await getPushPermissionState()
|
|
DispatchQueue.main.async {
|
|
hasNotificationPermissions = state
|
|
}
|
|
}
|
|
}
|
|
|
|
private func getPushPermissionState() async -> Bool? {
|
|
let settings = await UNUserNotificationCenter.current().notificationSettings()
|
|
switch settings.authorizationStatus {
|
|
case .authorized, .provisional, .ephemeral:
|
|
return true
|
|
case .denied:
|
|
return false
|
|
case .notDetermined:
|
|
return nil
|
|
@unknown default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationView {
|
|
VStack(spacing: 8) {
|
|
HStack {
|
|
statusView(hasPushToken)
|
|
Text("remote-notifications-title")
|
|
}
|
|
HStack {
|
|
statusView(hasNotificationPermissions)
|
|
Text("notification-permissions-title")
|
|
}
|
|
HStack {
|
|
statusView(authToken != nil)
|
|
Text("push-server-registration-title")
|
|
}
|
|
HStack {
|
|
statusView(deviceList.count > 1)
|
|
Text("other-devices-title")
|
|
}
|
|
if pushToken == nil {
|
|
Text("register-for-remote-notifications-text")
|
|
.padding()
|
|
.multilineTextAlignment(.center)
|
|
Button("register-for-remote-notifications-button", action: registerForRemoteNotifications)
|
|
.padding()
|
|
} else if hasNotificationPermissions == nil {
|
|
Button("request-notification-permission-button", action: requestNotificationPermission)
|
|
.padding()
|
|
} else if hasNotificationPermissions == false {
|
|
Text("no-notification-permissions-text")
|
|
.padding()
|
|
.multilineTextAlignment(.center)
|
|
Button("no-notification-permissions-button", action: openNotificationSettings)
|
|
.padding()
|
|
} else if authToken == nil {
|
|
Text("register-device-text")
|
|
.padding()
|
|
TextEntryField("Server url", placeholder: "register-device-server-placeholder", symbol: .network, showClearButton: true, text: $api.server)
|
|
.padding(.horizontal, 50)
|
|
.padding(.top)
|
|
TextEntryField("Application", placeholder: "register-device-application-placeholder", symbol: .questionmarkApp, showClearButton: true, text: $api.application)
|
|
.padding(.horizontal, 50)
|
|
.padding(.top)
|
|
TextEntryField("Device name", placeholder: "register-device-name-placeholder", symbol: .iphone, text: $deviceName)
|
|
.padding(.horizontal, 50)
|
|
.padding(.top)
|
|
Button("register-device-button", action: register)
|
|
.disabled(pushToken == nil || authToken != nil || deviceName.isEmpty)
|
|
.padding()
|
|
} else {
|
|
Text("push-message-description")
|
|
.padding()
|
|
TextEntryField("Push title", placeholder: "push-message-title-placeholder", symbol: .bubbleLeft, showClearButton: true, text: $pushMessageTitle)
|
|
.padding(.horizontal, 50)
|
|
.disabled(!isConfirmed)
|
|
TextEntryField("Push text", placeholder: "push-message-placeholder", symbol: .textformat, showClearButton: true, text: $pushMessageText)
|
|
.padding(.horizontal, 50)
|
|
.disabled(!isConfirmed)
|
|
Toggle("toggle-include-own-device-text", isOn: $includeOwnDeviceInPush)
|
|
.padding(.horizontal, 50)
|
|
.padding(.top)
|
|
Button("send-notification-button", action: sendPush)
|
|
.disabled(!canSendNotification)
|
|
.padding()
|
|
Button("show-device-list-button", action: showDevices)
|
|
.disabled(!isConfirmed)
|
|
.padding()
|
|
}
|
|
Spacer()
|
|
}
|
|
.navigationTitle("FlurSchnaps")
|
|
}
|
|
.sheet(isPresented: $showDeviceList) {
|
|
if let push = pushToken, let auth = authToken {
|
|
DeviceList(pushToken: push,
|
|
authToken: auth,
|
|
api: api,
|
|
isPresented: $showDeviceList,
|
|
devices: deviceList)
|
|
}
|
|
}.onAppear {
|
|
startPeriodicUpdates()
|
|
}.onDisappear {
|
|
stopPeriodicUpdates()
|
|
}
|
|
}
|
|
|
|
@State
|
|
private var timer: Timer?
|
|
|
|
private func startPeriodicUpdates() {
|
|
guard timer == nil else {
|
|
return
|
|
}
|
|
timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in
|
|
updateState()
|
|
}
|
|
updateState()
|
|
}
|
|
|
|
private func stopPeriodicUpdates() {
|
|
timer?.invalidate()
|
|
timer = nil
|
|
}
|
|
|
|
private func updateState() {
|
|
updateNotificationPermissionState()
|
|
if isConfirmed {
|
|
updateDeviceList()
|
|
} else if couldBeRegistered {
|
|
checkPushRegistrationStatus()
|
|
}
|
|
}
|
|
|
|
func registerForRemoteNotifications() {
|
|
UIApplication.shared.registerForRemoteNotifications()
|
|
}
|
|
|
|
func requestNotificationPermission() {
|
|
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
|
|
UNUserNotificationCenter.current().requestAuthorization(
|
|
options: authOptions,
|
|
completionHandler: {_, _ in
|
|
updateNotificationPermissionState()
|
|
})
|
|
}
|
|
|
|
func openNotificationSettings() {
|
|
if let appSettings = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(appSettings) {
|
|
UIApplication.shared.open(appSettings)
|
|
}
|
|
}
|
|
|
|
func register() {
|
|
guard let token = pushToken else {
|
|
print("No token to register")
|
|
return
|
|
}
|
|
let name = deviceName
|
|
Task {
|
|
print("Registering...")
|
|
guard let auth = await api.register(token: token, name: name) else {
|
|
DispatchQueue.main.async {
|
|
authToken = nil
|
|
isConfirmed = false
|
|
}
|
|
return
|
|
}
|
|
print("Registered")
|
|
DispatchQueue.main.async {
|
|
authToken = auth
|
|
isConfirmed = false
|
|
updateDeviceList()
|
|
}
|
|
}
|
|
}
|
|
|
|
func checkPushRegistrationStatus() {
|
|
guard let token = pushToken, let authToken = authToken else {
|
|
return
|
|
}
|
|
Task {
|
|
let confirmed = await api.isConfirmed(token: token, authentication: authToken)
|
|
if !confirmed {
|
|
print("Not confirmed by server: \(api.url?.path ?? "No server") (\(api.server))")
|
|
print(token.base64EncodedString())
|
|
print(authToken.base64EncodedString())
|
|
}
|
|
DispatchQueue.main.async {
|
|
isConfirmed = confirmed
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateDeviceList() {
|
|
guard let authToken = authToken,
|
|
let pushToken = pushToken else {
|
|
return
|
|
}
|
|
Task {
|
|
let devices = await api.getDeviceList(pushToken: pushToken, authToken: authToken)
|
|
DispatchQueue.main.async {
|
|
self.deviceList = devices
|
|
}
|
|
}
|
|
}
|
|
|
|
func showDevices() {
|
|
showDeviceList = true
|
|
}
|
|
|
|
func sendPush() {
|
|
guard let authToken = authToken else {
|
|
return
|
|
}
|
|
guard let pushToken = pushToken else {
|
|
return
|
|
}
|
|
|
|
var recipients = deviceList.map { $0.pushToken }
|
|
guard recipients.count > 0 else {
|
|
return
|
|
}
|
|
if !includeOwnDeviceInPush {
|
|
recipients = recipients.filter { $0 != pushToken }
|
|
}
|
|
let body = pushMessageText
|
|
let alert = APNSwiftAlert(
|
|
title: pushMessageTitle,
|
|
body: body)
|
|
let payload = APNSwiftPayload(
|
|
alert: alert,
|
|
sound: .normal("default"))
|
|
let content = PushMessage(
|
|
recipients: recipients,
|
|
payload: payload,
|
|
pushType: .alert)
|
|
let sender = DeviceAuthentication(
|
|
pushToken: pushToken,
|
|
authentication: authToken)
|
|
let message = AuthenticatedPushMessage(
|
|
sender: sender,
|
|
message: content)
|
|
Task {
|
|
let sent = await api.send(push: message)
|
|
print("Sent push message: \(sent)")
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ContentView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
ContentView()
|
|
.previewDevice("iPhone 8")
|
|
}
|
|
}
|