FlurSchnaps-iOS/FlurSchnaps/ContentView.swift

327 lines
10 KiB
Swift
Raw Normal View History

2022-06-04 11:45:41 +02:00
import SwiftUI
2022-06-07 11:26:32 +02:00
import APNSwift
import PushAPI
import SFSafeSymbols
2022-06-04 11:45:41 +02:00
struct ContentView: View {
2022-06-07 11:26:32 +02:00
@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
}
}
2022-06-04 11:45:41 +02:00
var body: some View {
2022-06-07 11:26:32 +02:00
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)")
}
2022-06-04 11:45:41 +02:00
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
2022-06-07 11:26:32 +02:00
.previewDevice("iPhone 8")
2022-06-04 11:45:41 +02:00
}
}