2022-06-27 10:28:30 +01:00
//
// N o t i f i c a t i o n s V i e w . s w i f t
// S i m p l e X ( i O S )
//
// C r e a t e d b y E v g e n y o n 2 6 / 0 6 / 2 0 2 2 .
// C o p y r i g h t © 2 0 2 2 S i m p l e X C h a t . A l l r i g h t s r e s e r v e d .
//
import SwiftUI
import SimpleXChat
struct NotificationsView : View {
@ EnvironmentObject var m : ChatModel
2024-07-03 22:42:13 +01:00
@ EnvironmentObject var theme : AppTheme
2023-07-19 16:37:46 +01:00
@ State private var notificationMode : NotificationsMode = ChatModel . shared . notificationMode
2022-06-28 19:03:39 +01:00
@ State private var showAlert : NotificationAlert ?
2022-07-01 20:33:20 +01:00
@ State private var legacyDatabase = dbContainerGroupDefault . get ( ) = = . documents
2024-07-03 21:31:09 +04:00
@ State private var testing = false
@ State private var testedSuccess : Bool ? = nil
2022-06-27 10:28:30 +01:00
var body : some View {
2024-07-03 21:31:09 +04:00
ZStack {
viewBody ( )
if testing {
ProgressView ( ) . scaleEffect ( 2 )
}
}
. alert ( item : $ showAlert ) { alert in
if let token = m . deviceToken {
return notificationAlert ( alert , token )
} else {
return Alert ( title : Text ( " No device token! " ) )
}
}
}
private func viewBody ( ) -> some View {
2022-06-27 10:28:30 +01:00
List {
Section {
NavigationLink {
List {
Section {
2022-07-01 09:49:30 +01:00
SelectionListView ( list : NotificationsMode . values , selection : $ notificationMode ) { mode in
2022-06-28 19:03:39 +01:00
showAlert = . setMode ( mode : mode )
2022-06-27 10:28:30 +01:00
}
} footer : {
VStack ( alignment : . leading ) {
2023-07-19 16:37:46 +01:00
Text ( ntfModeDescription ( notificationMode ) )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2022-06-27 10:28:30 +01:00
}
. font ( . callout )
. padding ( . top , 1 )
}
}
. navigationTitle ( " Send notifications " )
2024-07-03 22:42:13 +01:00
. modifier ( ThemedBackground ( grouped : true ) )
2022-06-27 10:28:30 +01:00
. navigationBarTitleDisplayMode ( . inline )
} label : {
HStack {
Text ( " Send notifications " )
Spacer ( )
Text ( m . notificationMode . label )
}
}
NavigationLink {
List {
Section {
2022-07-06 11:52:10 +01:00
SelectionListView ( list : NotificationPreviewMode . values , selection : $ m . notificationPreview ) { previewMode in
ntfPreviewModeGroupDefault . set ( previewMode )
m . notificationPreview = previewMode
}
2022-06-27 10:28:30 +01:00
} footer : {
2022-07-06 11:52:10 +01:00
VStack ( alignment : . leading , spacing : 1 ) {
Text ( " You can set lock screen notification preview via settings. " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2022-07-06 11:52:10 +01:00
Button ( " Open Settings " ) {
DispatchQueue . main . async {
UIApplication . shared . open ( URL ( string : UIApplication . openSettingsURLString ) ! , options : [ : ] , completionHandler : nil )
}
}
}
2022-06-27 10:28:30 +01:00
}
}
. navigationTitle ( " Show preview " )
2024-07-03 22:42:13 +01:00
. modifier ( ThemedBackground ( grouped : true ) )
2022-06-27 10:28:30 +01:00
. navigationBarTitleDisplayMode ( . inline )
} label : {
HStack {
Text ( " Show preview " )
Spacer ( )
2023-07-19 16:37:46 +01:00
Text ( m . notificationPreview . label )
2022-06-27 10:28:30 +01:00
}
}
2024-02-13 17:58:54 +04:00
if let server = m . notificationServer {
2024-07-03 22:42:13 +01:00
smpServers ( " Push server " , [ server ] , theme . colors . secondary )
2024-07-03 21:31:09 +04:00
testServerButton ( server )
2024-02-13 17:58:54 +04:00
}
2022-06-27 10:28:30 +01:00
} header : {
2022-07-01 20:33:20 +01:00
Text ( " Push notifications " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2022-07-01 20:33:20 +01:00
} footer : {
if legacyDatabase {
Text ( " Please restart the app and migrate the database to enable push notifications. " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2022-07-01 20:33:20 +01:00
. font ( . callout )
. padding ( . top , 1 )
}
2022-06-27 10:28:30 +01:00
}
}
2023-07-19 16:37:46 +01:00
. disabled ( legacyDatabase )
2024-02-13 17:58:54 +04:00
. onAppear {
( m . savedToken , m . tokenStatus , m . notificationMode , m . notificationServer ) = apiGetNtfToken ( )
}
2022-06-27 10:28:30 +01:00
}
2022-06-28 19:03:39 +01:00
private func notificationAlert ( _ alert : NotificationAlert , _ token : DeviceToken ) -> Alert {
switch alert {
case let . setMode ( mode ) :
2022-06-27 10:28:30 +01:00
return Alert (
2022-06-28 19:03:39 +01:00
title : Text ( ntfModeAlertTitle ( mode ) ) ,
2022-06-27 10:28:30 +01:00
message : Text ( ntfModeDescription ( mode ) ) ,
2022-06-28 19:03:39 +01:00
primaryButton : . default ( Text ( mode = = . off ? " Turn off " : " Enable " ) ) {
2022-07-03 19:53:07 +01:00
setNotificationsMode ( token , mode )
2022-06-27 10:28:30 +01:00
} ,
secondaryButton : . cancel ( ) {
notificationMode = m . notificationMode
}
)
2024-07-03 21:31:09 +04:00
case let . testFailure ( testFailure ) :
return Alert (
title : Text ( " Server test failed! " ) ,
message : Text ( testFailure . localizedDescription )
)
2022-06-28 19:03:39 +01:00
case let . error ( title , error ) :
return Alert ( title : Text ( title ) , message : Text ( error ) )
}
}
2022-07-01 09:49:30 +01:00
private func ntfModeAlertTitle ( _ mode : NotificationsMode ) -> LocalizedStringKey {
2022-06-28 19:03:39 +01:00
switch mode {
2023-12-11 12:59:49 +00:00
case . off : return " Use only local notifications? "
2022-06-28 19:03:39 +01:00
case . periodic : return " Enable periodic notifications? "
case . instant : return " Enable instant notifications? "
}
}
2022-07-03 19:53:07 +01:00
private func setNotificationsMode ( _ token : DeviceToken , _ mode : NotificationsMode ) {
2022-06-28 19:03:39 +01:00
Task {
switch mode {
case . off :
do {
try await apiDeleteToken ( token : token )
2022-07-03 19:53:07 +01:00
await MainActor . run {
m . tokenStatus = . new
notificationMode = . off
m . notificationMode = . off
2024-02-13 17:58:54 +04:00
m . notificationServer = nil
2024-07-03 21:31:09 +04:00
testedSuccess = nil
2022-07-03 19:53:07 +01:00
}
2022-07-01 09:49:30 +01:00
} catch let error {
2022-07-03 19:53:07 +01:00
await MainActor . run {
2022-06-28 19:03:39 +01:00
let err = responseError ( error )
logger . error ( " apiDeleteToken error: \( err ) " )
showAlert = . error ( title : " Error deleting token " , error : err )
}
2022-06-27 10:28:30 +01:00
}
2022-06-28 19:03:39 +01:00
default :
do {
2024-02-13 17:58:54 +04:00
let _ = try await apiRegisterToken ( token : token , notificationMode : mode )
let ( _ , tknStatus , ntfMode , ntfServer ) = apiGetNtfToken ( )
2022-07-03 19:53:07 +01:00
await MainActor . run {
2024-02-13 17:58:54 +04:00
m . tokenStatus = tknStatus
notificationMode = ntfMode
m . notificationMode = ntfMode
m . notificationServer = ntfServer
2024-07-03 21:31:09 +04:00
testedSuccess = nil
2022-07-03 19:53:07 +01:00
}
} catch let error {
await MainActor . run {
let err = responseError ( error )
logger . error ( " apiRegisterToken error: \( err ) " )
showAlert = . error ( title : " Error enabling notifications " , error : err )
2022-06-28 19:03:39 +01:00
}
}
}
2022-06-27 10:28:30 +01:00
}
}
2024-07-03 21:31:09 +04:00
private func testServerButton ( _ server : String ) -> some View {
HStack {
Button ( " Test server " ) {
testing = true
Task {
await testServer ( server )
await MainActor . run { testing = false }
}
}
. disabled ( testing )
if ! testing {
Spacer ( )
showTestStatus ( )
}
}
}
@ ViewBuilder func showTestStatus ( ) -> some View {
if testedSuccess = = true {
Image ( systemName : " checkmark " )
. foregroundColor ( . green )
} else if testedSuccess = = false {
Image ( systemName : " multiply " )
. foregroundColor ( . red )
}
}
private func testServer ( _ server : String ) async {
do {
let r = try await testProtoServer ( server : server )
switch r {
case . success :
await MainActor . run {
testedSuccess = true
}
case let . failure ( f ) :
await MainActor . run {
showAlert = . testFailure ( testFailure : f )
testedSuccess = false
}
}
} catch let error {
logger . error ( " testServerConnection \( responseError ( error ) ) " )
}
}
2022-06-27 10:28:30 +01:00
}
2022-07-01 09:49:30 +01:00
func ntfModeDescription ( _ mode : NotificationsMode ) -> LocalizedStringKey {
2022-06-27 10:28:30 +01:00
switch mode {
2024-11-27 19:01:16 +00:00
case . off : return " **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. "
case . periodic : return " **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. "
case . instant : return " **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. "
2022-06-27 10:28:30 +01:00
}
}
2024-11-26 12:26:35 +00:00
func ntfModeShortDescription ( _ mode : NotificationsMode ) -> LocalizedStringKey {
switch mode {
case . off : return " Check messages when allowed. "
case . periodic : return " Check messages every 20 min. "
case . instant : return " E2E encrypted notifications. "
}
}
2022-06-27 10:28:30 +01:00
struct SelectionListView < Item : SelectableItem > : View {
2024-07-03 22:42:13 +01:00
@ EnvironmentObject var theme : AppTheme
2022-06-27 10:28:30 +01:00
var list : [ Item ]
2023-07-19 16:37:46 +01:00
@ Binding var selection : Item
2022-06-27 10:28:30 +01:00
var onSelection : ( ( Item ) -> Void ) ?
@ State private var tapped : Item ? = nil
var body : some View {
ForEach ( list ) { item in
2024-07-03 22:42:13 +01:00
Button {
2022-07-03 19:53:07 +01:00
if selection = = item { return }
2022-06-27 10:28:30 +01:00
if let f = onSelection {
f ( item )
} else {
selection = item
}
2024-07-03 22:42:13 +01:00
} label : {
HStack {
Text ( item . label ) . foregroundColor ( theme . colors . onBackground )
Spacer ( )
if selection = = item {
Image ( systemName : " checkmark " )
. resizable ( ) . scaledToFit ( ) . frame ( width : 16 )
. foregroundColor ( theme . colors . primary )
}
2022-06-27 10:28:30 +01:00
}
2024-07-03 22:42:13 +01:00
}
2022-06-27 10:28:30 +01:00
}
. environment ( \ . editMode , . constant ( . active ) )
}
}
2022-06-28 19:03:39 +01:00
enum NotificationAlert : Identifiable {
2022-07-01 09:49:30 +01:00
case setMode ( mode : NotificationsMode )
2024-07-03 21:31:09 +04:00
case testFailure ( testFailure : ProtocolTestFailure )
2022-06-28 19:03:39 +01:00
case error ( title : LocalizedStringKey , error : String )
var id : String {
switch self {
case let . setMode ( mode ) : return " enable \( mode . rawValue ) "
2024-07-03 21:31:09 +04:00
case let . testFailure ( testFailure ) : return " testFailure \( testFailure . testStep ) \( testFailure . testError ) "
2022-06-28 19:03:39 +01:00
case let . error ( title , error ) : return " error \( title ) : \( error ) "
}
}
}
2022-06-27 10:28:30 +01:00
struct NotificationsView_Previews : PreviewProvider {
static var previews : some View {
NotificationsView ( )
}
}