2022-01-21 11:09:33 +00:00
|
|
|
//
|
|
|
|
// ContentView.swift
|
|
|
|
// Shared
|
|
|
|
//
|
|
|
|
// Created by Evgeny Poberezkin on 17/01/2022.
|
|
|
|
//
|
|
|
|
|
|
|
|
import SwiftUI
|
2023-03-14 11:12:40 +03:00
|
|
|
import Intents
|
2022-09-07 12:49:41 +01:00
|
|
|
import SimpleXChat
|
2022-01-21 11:09:33 +00:00
|
|
|
|
|
|
|
struct ContentView: View {
|
2022-01-29 11:10:04 +00:00
|
|
|
@EnvironmentObject var chatModel: ChatModel
|
2022-02-12 15:59:43 +00:00
|
|
|
@ObservedObject var alertManager = AlertManager.shared
|
2022-05-24 19:34:27 +01:00
|
|
|
@ObservedObject var callController = CallController.shared
|
2023-03-15 13:21:21 +03:00
|
|
|
@Environment(\.colorScheme) var colorScheme
|
2022-05-28 22:09:46 +04:00
|
|
|
@Binding var doAuthenticate: Bool
|
2022-06-03 12:24:50 +01:00
|
|
|
@Binding var userAuthorized: Bool?
|
2023-03-15 13:21:21 +03:00
|
|
|
@Binding var canConnectCall: Bool
|
2023-03-15 18:32:27 +03:00
|
|
|
@Binding var lastSuccessfulUnlock: TimeInterval?
|
2023-05-22 15:49:46 +02:00
|
|
|
@Binding var showInitializationView: Bool
|
2022-05-28 22:09:46 +04:00
|
|
|
@AppStorage(DEFAULT_SHOW_LA_NOTICE) private var prefShowLANotice = false
|
|
|
|
@AppStorage(DEFAULT_LA_NOTICE_SHOWN) private var prefLANoticeShown = false
|
|
|
|
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
|
2023-01-11 17:09:17 +00:00
|
|
|
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
|
2022-11-25 21:43:10 +00:00
|
|
|
@AppStorage(DEFAULT_NOTIFICATION_ALERT_SHOWN) private var notificationAlertShown = false
|
2023-04-12 12:22:55 +02:00
|
|
|
@State private var showSettings = false
|
2022-12-26 14:08:01 +00:00
|
|
|
@State private var showWhatsNew = false
|
2023-04-12 12:22:55 +02:00
|
|
|
@State private var showChooseLAMode = false
|
|
|
|
@State private var showSetPasscode = false
|
2023-08-08 17:26:56 +04:00
|
|
|
@State private var chatListActionSheet: ChatListActionSheet? = nil
|
|
|
|
|
|
|
|
private enum ChatListActionSheet: Identifiable {
|
|
|
|
case connectViaUrl(action: ConnReqType, link: String)
|
|
|
|
|
|
|
|
var id: String {
|
|
|
|
switch self {
|
|
|
|
case .connectViaUrl: return "connectViaUrl \(link)"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-03 07:16:29 +00:00
|
|
|
|
2022-01-21 11:09:33 +00:00
|
|
|
var body: some View {
|
2022-05-09 09:52:09 +01:00
|
|
|
ZStack {
|
2023-03-16 00:09:33 +03:00
|
|
|
contentView()
|
2023-03-15 13:21:21 +03:00
|
|
|
if chatModel.showCallView, let call = chatModel.activeCall {
|
2023-03-16 00:09:33 +03:00
|
|
|
callView(call)
|
2022-05-09 09:52:09 +01:00
|
|
|
}
|
2023-04-12 12:22:55 +02:00
|
|
|
if !showSettings, let la = chatModel.laRequest {
|
|
|
|
LocalAuthView(authRequest: la)
|
|
|
|
} else if showSetPasscode {
|
|
|
|
SetAppPasscodeView {
|
|
|
|
prefPerformLA = true
|
|
|
|
showSetPasscode = false
|
|
|
|
privacyLocalAuthModeDefault.set(.passcode)
|
|
|
|
alertManager.showAlert(laTurnedOnAlert())
|
|
|
|
} cancel: {
|
|
|
|
prefPerformLA = false
|
|
|
|
showSetPasscode = false
|
|
|
|
alertManager.showAlert(laPasscodeNotSetAlert())
|
|
|
|
}
|
|
|
|
}
|
2022-01-22 17:54:22 +00:00
|
|
|
}
|
2022-06-24 13:52:20 +01:00
|
|
|
.onAppear {
|
2023-03-16 00:09:33 +03:00
|
|
|
if prefPerformLA { requestNtfAuthorization() }
|
|
|
|
initAuthenticate()
|
|
|
|
}
|
|
|
|
.onChange(of: doAuthenticate) { _ in
|
|
|
|
initAuthenticate()
|
2022-06-24 13:52:20 +01:00
|
|
|
}
|
2022-05-09 09:52:09 +01:00
|
|
|
.alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! }
|
2023-04-12 12:22:55 +02:00
|
|
|
.sheet(isPresented: $showSettings) {
|
|
|
|
SettingsView(showSettings: $showSettings)
|
|
|
|
}
|
|
|
|
.confirmationDialog("SimpleX Lock mode", isPresented: $showChooseLAMode, titleVisibility: .visible) {
|
|
|
|
Button("System authentication") { initialEnableLA() }
|
|
|
|
Button("Passcode entry") { showSetPasscode = true }
|
|
|
|
}
|
2022-02-09 22:53:06 +00:00
|
|
|
}
|
2022-02-12 15:59:43 +00:00
|
|
|
|
2023-03-16 00:09:33 +03:00
|
|
|
@ViewBuilder private func contentView() -> some View {
|
|
|
|
if prefPerformLA && userAuthorized != true {
|
|
|
|
lockButton()
|
2023-05-05 15:52:16 +04:00
|
|
|
} else if chatModel.chatDbStatus == nil && showInitializationView {
|
|
|
|
initializationView()
|
2023-03-16 00:09:33 +03:00
|
|
|
} else if let status = chatModel.chatDbStatus, status != .ok {
|
|
|
|
DatabaseErrorView(status: status)
|
|
|
|
} else if !chatModel.v3DBMigration.startChat {
|
|
|
|
MigrateToAppGroupView()
|
|
|
|
} else if let step = chatModel.onboardingStage {
|
|
|
|
if case .onboardingComplete = step,
|
|
|
|
chatModel.currentUser != nil {
|
|
|
|
mainView()
|
2023-08-08 17:26:56 +04:00
|
|
|
.actionSheet(item: $chatListActionSheet) { sheet in
|
|
|
|
switch sheet {
|
|
|
|
case let .connectViaUrl(action, link): return connectViaUrlSheet(action, link)
|
|
|
|
}
|
|
|
|
}
|
2023-03-16 00:09:33 +03:00
|
|
|
} else {
|
|
|
|
OnboardingView(onboarding: step)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@ViewBuilder private func callView(_ call: Call) -> some View {
|
|
|
|
if CallController.useCallKit() {
|
|
|
|
ActiveCallView(call: call, canConnectCall: Binding.constant(true))
|
|
|
|
.onDisappear {
|
|
|
|
if userAuthorized == false && doAuthenticate { runAuthenticate() }
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ActiveCallView(call: call, canConnectCall: $canConnectCall)
|
|
|
|
if prefPerformLA && userAuthorized != true {
|
|
|
|
Rectangle()
|
|
|
|
.fill(colorScheme == .dark ? .black : .white)
|
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
|
lockButton()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func lockButton() -> some View {
|
|
|
|
Button(action: runAuthenticate) { Label("Unlock", systemImage: "lock") }
|
|
|
|
}
|
|
|
|
|
2023-05-05 15:52:16 +04:00
|
|
|
private func initializationView() -> some View {
|
|
|
|
VStack {
|
|
|
|
ProgressView().scaleEffect(2)
|
|
|
|
Text("Opening database…")
|
|
|
|
.padding()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-24 13:52:20 +01:00
|
|
|
private func mainView() -> some View {
|
|
|
|
ZStack(alignment: .top) {
|
2023-04-12 12:22:55 +02:00
|
|
|
ChatListView(showSettings: $showSettings).privacySensitive(protectScreen)
|
2022-06-24 13:52:20 +01:00
|
|
|
.onAppear {
|
2023-03-16 00:09:33 +03:00
|
|
|
if !prefPerformLA { requestNtfAuthorization() }
|
2022-06-24 13:52:20 +01:00
|
|
|
// Local Authentication notice is to be shown on next start after onboarding is complete
|
2022-12-30 20:17:56 +04:00
|
|
|
if (!prefLANoticeShown && prefShowLANotice && !chatModel.chats.isEmpty) {
|
2022-06-24 13:52:20 +01:00
|
|
|
prefLANoticeShown = true
|
|
|
|
alertManager.showAlert(laNoticeAlert())
|
2022-12-26 14:08:01 +00:00
|
|
|
} else if !chatModel.showCallView && CallController.shared.activeCallInvitation == nil {
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
2023-02-02 10:11:11 +00:00
|
|
|
if !showWhatsNew {
|
|
|
|
showWhatsNew = shouldShowWhatsNew()
|
|
|
|
}
|
2022-12-26 14:08:01 +00:00
|
|
|
}
|
2022-06-24 13:52:20 +01:00
|
|
|
}
|
|
|
|
prefShowLANotice = true
|
2023-08-08 17:26:56 +04:00
|
|
|
connectViaUrl()
|
2022-06-24 13:52:20 +01:00
|
|
|
}
|
2023-08-08 17:26:56 +04:00
|
|
|
.onChange(of: chatModel.appOpenUrl) { _ in connectViaUrl() }
|
2022-12-26 14:08:01 +00:00
|
|
|
.sheet(isPresented: $showWhatsNew) {
|
|
|
|
WhatsNewView()
|
|
|
|
}
|
2023-07-13 23:48:25 +01:00
|
|
|
if chatModel.setDeliveryReceipts {
|
|
|
|
SetDeliveryReceiptsView()
|
|
|
|
}
|
2022-06-24 13:52:20 +01:00
|
|
|
IncomingCallView()
|
|
|
|
}
|
2023-03-16 22:08:58 +00:00
|
|
|
.onContinueUserActivity("INStartCallIntent", perform: processUserActivity)
|
|
|
|
.onContinueUserActivity("INStartAudioCallIntent", perform: processUserActivity)
|
|
|
|
.onContinueUserActivity("INStartVideoCallIntent", perform: processUserActivity)
|
2023-03-14 11:12:40 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 22:08:58 +00:00
|
|
|
private func processUserActivity(_ activity: NSUserActivity) {
|
|
|
|
let intent = activity.interaction?.intent
|
|
|
|
if let intent = intent as? INStartCallIntent {
|
|
|
|
callToRecentContact(intent.contacts, intent.callCapability == .videoCall ? .video : .audio)
|
|
|
|
} else if let intent = intent as? INStartAudioCallIntent {
|
|
|
|
callToRecentContact(intent.contacts, .audio)
|
|
|
|
} else if let intent = intent as? INStartVideoCallIntent {
|
|
|
|
callToRecentContact(intent.contacts, .video)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func callToRecentContact(_ contacts: [INPerson]?, _ mediaType: CallMediaType) {
|
|
|
|
logger.debug("callToRecentContact")
|
|
|
|
if let contactId = contacts?.first?.personHandle?.value,
|
|
|
|
let chat = chatModel.getChat(contactId),
|
|
|
|
case let .direct(contact) = chat.chatInfo {
|
|
|
|
logger.debug("callToRecentContact: schedule call")
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
|
|
|
CallController.shared.startCall(contact, mediaType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-24 13:52:20 +01:00
|
|
|
|
2023-03-16 00:09:33 +03:00
|
|
|
private func initAuthenticate() {
|
2023-04-12 12:22:55 +02:00
|
|
|
logger.debug("initAuthenticate")
|
2023-03-16 00:09:33 +03:00
|
|
|
if CallController.useCallKit() && chatModel.showCallView && chatModel.activeCall != nil {
|
|
|
|
userAuthorized = false
|
|
|
|
} else if doAuthenticate {
|
|
|
|
runAuthenticate()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-28 22:09:46 +04:00
|
|
|
private func runAuthenticate() {
|
2023-07-20 12:07:00 +01:00
|
|
|
logger.debug("DEBUGGING: runAuthenticate")
|
2022-05-28 22:09:46 +04:00
|
|
|
if !prefPerformLA {
|
|
|
|
userAuthorized = true
|
2022-06-03 12:24:50 +01:00
|
|
|
} else {
|
2023-07-20 12:07:00 +01:00
|
|
|
logger.debug("DEBUGGING: before dismissAllSheets")
|
2022-08-04 12:41:05 +01:00
|
|
|
dismissAllSheets(animated: false) {
|
2023-07-20 12:07:00 +01:00
|
|
|
logger.debug("DEBUGGING: in dismissAllSheets callback")
|
2022-12-03 21:42:12 +00:00
|
|
|
chatModel.chatId = nil
|
2022-08-04 12:41:05 +01:00
|
|
|
justAuthenticate()
|
|
|
|
}
|
2022-06-03 12:24:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func justAuthenticate() {
|
2022-06-03 13:05:34 +01:00
|
|
|
userAuthorized = false
|
2023-04-12 12:22:55 +02:00
|
|
|
let laMode = privacyLocalAuthModeDefault.get()
|
2023-05-09 10:33:30 +02:00
|
|
|
authenticate(reason: NSLocalizedString("Unlock app", comment: "authentication reason"), selfDestruct: true) { laResult in
|
2023-07-21 08:10:39 +01:00
|
|
|
logger.debug("DEBUGGING: authenticate callback: \(String(describing: laResult))")
|
2022-06-03 12:24:50 +01:00
|
|
|
switch (laResult) {
|
|
|
|
case .success:
|
|
|
|
userAuthorized = true
|
2023-03-15 13:21:21 +03:00
|
|
|
canConnectCall = true
|
2023-03-15 18:32:27 +03:00
|
|
|
lastSuccessfulUnlock = ProcessInfo.processInfo.systemUptime
|
2022-06-03 12:24:50 +01:00
|
|
|
case .failed:
|
2023-04-12 12:22:55 +02:00
|
|
|
if laMode == .passcode {
|
|
|
|
AlertManager.shared.showAlert(laFailedAlert())
|
|
|
|
}
|
2022-06-03 12:24:50 +01:00
|
|
|
case .unavailable:
|
|
|
|
userAuthorized = true
|
|
|
|
prefPerformLA = false
|
2023-03-16 00:09:33 +03:00
|
|
|
canConnectCall = true
|
2022-06-03 12:24:50 +01:00
|
|
|
AlertManager.shared.showAlert(laUnavailableTurningOffAlert())
|
2022-05-28 22:09:46 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 00:09:33 +03:00
|
|
|
func requestNtfAuthorization() {
|
|
|
|
NtfManager.shared.requestAuthorization(
|
|
|
|
onDeny: {
|
|
|
|
if (!notificationAlertShown) {
|
|
|
|
notificationAlertShown = true
|
|
|
|
alertManager.showAlert(notificationAlert())
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onAuthorized: { notificationAlertShown = false }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-05-28 22:09:46 +04:00
|
|
|
func laNoticeAlert() -> Alert {
|
|
|
|
Alert(
|
|
|
|
title: Text("SimpleX Lock"),
|
|
|
|
message: Text("To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled."),
|
2023-04-12 12:22:55 +02:00
|
|
|
primaryButton: .default(Text("Turn on")) { showChooseLAMode = true },
|
2022-05-28 22:09:46 +04:00
|
|
|
secondaryButton: .cancel()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-04-12 12:22:55 +02:00
|
|
|
private func initialEnableLA () {
|
|
|
|
privacyLocalAuthModeDefault.set(.system)
|
|
|
|
authenticate(reason: NSLocalizedString("Enable SimpleX Lock", comment: "authentication reason")) { laResult in
|
|
|
|
switch laResult {
|
|
|
|
case .success:
|
|
|
|
prefPerformLA = true
|
|
|
|
alertManager.showAlert(laTurnedOnAlert())
|
|
|
|
case .failed:
|
|
|
|
prefPerformLA = false
|
|
|
|
alertManager.showAlert(laFailedAlert())
|
|
|
|
case .unavailable:
|
|
|
|
prefPerformLA = false
|
|
|
|
alertManager.showAlert(laUnavailableInstructionAlert())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-12 15:59:43 +00:00
|
|
|
func notificationAlert() -> Alert {
|
|
|
|
Alert(
|
2022-04-16 09:37:01 +01:00
|
|
|
title: Text("Notifications are disabled!"),
|
2022-11-29 12:41:48 +04:00
|
|
|
message: Text("The app can notify you when you receive messages or contact requests - please open settings to enable."),
|
|
|
|
primaryButton: .default(Text("Open Settings")) {
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
secondaryButton: .cancel()
|
|
|
|
)
|
2022-02-12 15:59:43 +00:00
|
|
|
}
|
2022-01-21 11:09:33 +00:00
|
|
|
|
2023-08-08 17:26:56 +04:00
|
|
|
func connectViaUrl() {
|
|
|
|
let m = ChatModel.shared
|
|
|
|
if let url = m.appOpenUrl {
|
|
|
|
m.appOpenUrl = nil
|
|
|
|
var path = url.path
|
|
|
|
logger.debug("ContentView.connectViaUrl path: \(path)")
|
|
|
|
if (path == "/contact" || path == "/invitation") {
|
|
|
|
path.removeFirst()
|
|
|
|
let action: ConnReqType = path == "contact" ? .contact : .invitation
|
|
|
|
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
|
|
|
|
chatListActionSheet = .connectViaUrl(action: action, link: link)
|
|
|
|
} else {
|
|
|
|
AlertManager.shared.showAlert(Alert(title: Text("Error: URL is invalid")))
|
|
|
|
}
|
|
|
|
}
|
2022-05-09 09:52:09 +01:00
|
|
|
}
|
|
|
|
|
2023-08-08 17:26:56 +04:00
|
|
|
private func connectViaUrlSheet(_ action: ConnReqType, _ link: String) -> ActionSheet {
|
2022-05-09 09:52:09 +01:00
|
|
|
let title: LocalizedStringKey
|
2023-08-08 17:26:56 +04:00
|
|
|
switch action {
|
|
|
|
case .contact: title = "Connect via contact link"
|
|
|
|
case .invitation: title = "Connect via one-time link"
|
|
|
|
}
|
|
|
|
return ActionSheet(
|
2022-05-09 09:52:09 +01:00
|
|
|
title: Text(title),
|
2023-08-08 17:26:56 +04:00
|
|
|
buttons: [
|
|
|
|
.default(Text("Use current profile")) { connectViaLink(link, incognito: false) },
|
|
|
|
.default(Text("Use new incognito profile")) { connectViaLink(link, incognito: true) },
|
|
|
|
.cancel()
|
|
|
|
]
|
2022-05-09 09:52:09 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-12 15:59:43 +00:00
|
|
|
final class AlertManager: ObservableObject {
|
|
|
|
static let shared = AlertManager()
|
|
|
|
@Published var presentAlert = false
|
|
|
|
@Published var alertView: Alert?
|
|
|
|
|
|
|
|
func showAlert(_ alert: Alert) {
|
2022-02-14 11:53:44 +00:00
|
|
|
logger.debug("AlertManager.showAlert")
|
2022-04-02 14:35:35 +01:00
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
2022-02-12 15:59:43 +00:00
|
|
|
self.alertView = alert
|
|
|
|
self.presentAlert = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-16 09:37:01 +01:00
|
|
|
func showAlertMsg(title: LocalizedStringKey, message: LocalizedStringKey? = nil) {
|
2022-05-28 14:58:52 +04:00
|
|
|
showAlert(mkAlert(title: title, message: message))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func mkAlert(title: LocalizedStringKey, message: LocalizedStringKey? = nil) -> Alert {
|
|
|
|
if let message = message {
|
|
|
|
return Alert(title: Text(title), message: Text(message))
|
|
|
|
} else {
|
|
|
|
return Alert(title: Text(title))
|
2022-02-12 15:59:43 +00:00
|
|
|
}
|
|
|
|
}
|
2022-01-22 17:54:22 +00:00
|
|
|
|
|
|
|
//struct ContentView_Previews: PreviewProvider {
|
|
|
|
// static var previews: some View {
|
|
|
|
// ContentView(text: "Hello!")
|
|
|
|
// }
|
|
|
|
//}
|